diff --git a/dotCMS/dependencies.gradle b/dotCMS/dependencies.gradle
index 6e044efb1a36..45ec308685d8 100644
--- a/dotCMS/dependencies.gradle
+++ b/dotCMS/dependencies.gradle
@@ -294,30 +294,37 @@ dependencies {
compile group: 'eu.bitwalker', name: 'UserAgentUtils', version:'1.19'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-core', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-metadata', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-bmp', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-jpeg', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-tiff', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-pnm', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-psd', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-iff', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-pcx', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-pict', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-tiff', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-sgi', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-tga', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-icns', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-pcx', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-thumbsdb', version:'3.2.1'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-batik', version:'3.3.2'
- compile group: 'com.twelvemonkeys.imageio', name: 'imageio-clippath', version:'3.2.1'
- compile group: 'com.twelvemonkeys.servlet', name: 'servlet', version:'3.2.1'
- compile 'com.github.ben-manes.caffeine:caffeine:2.4.0'
- compile (group: 'org.apache.pdfbox', name: 'pdfbox', version: '2.0.1'){
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-core', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-metadata', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-bmp', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-jpeg', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-tiff', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-pnm', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-psd', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-iff', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-pcx', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-pdf', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-hdr', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-pict', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-tiff', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-sgi', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-tga', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-icns', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-pcx', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-webp', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-thumbsdb', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-batik', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.imageio', name: 'imageio-clippath', version:'3.7.0'
+ implementation group: 'com.twelvemonkeys.servlet', name: 'servlet', version:'3.7.0'
+
+
+ implementation 'com.github.ben-manes.caffeine:caffeine:2.9.2'
+
+ implementation (group: 'org.apache.pdfbox', name: 'pdfbox', version: '2.0.27'){
exclude(group: 'commons-logging')
}
+
//Don't remove Bouncy Castle, com.dotcms.staticpublish.listener need it, see plugin's README.
compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.56'
compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.56'
@@ -407,5 +414,5 @@ dependencies {
compile 'net.jodah:failsafe:1.1.1'
compile 'com.rainerhahnekamp:sneakythrow:1.1.0'
-
+
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/ImageEngine.java b/dotCMS/src/main/java/com/dotmarketing/image/ImageEngine.java
new file mode 100644
index 000000000000..4555a4ec824e
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotmarketing/image/ImageEngine.java
@@ -0,0 +1,24 @@
+package com.dotmarketing.image;
+
+import com.dotmarketing.image.filter.ImageFilterAPI;
+
+
+
+/**
+ * Single selection point for the active {@link ImageFilterAPI} implementation.
+ *
+ *
Returns the libvips engine when the {@code IMAGE_API_USE_LIBVIPS} feature flag is on and native
+ * libvips is available, otherwise the pure-JVM engine. Every call-site that wants to honour the flag
+ * — the image exporter, metadata generation, the velocity binary view tool — should resolve through
+ * here rather than referencing {@link ImageFilterAPI#apiInstance} directly (which is always the
+ * legacy engine).
+ */
+public final class ImageEngine {
+
+ private ImageEngine() {}
+
+
+ public static ImageFilterAPI resolve() {
+ return ImageFilterAPI.apiInstance.apply();
+ }
+}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/CropImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/CropImageFilter.java
index 005a74482b8d..1f09d8ff5b9c 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/CropImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/CropImageFilter.java
@@ -1,61 +1,97 @@
package com.dotmarketing.image.filter;
+import java.awt.Dimension;
+import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Map;
-
+import java.util.Optional;
import javax.imageio.ImageIO;
import com.dotmarketing.util.Logger;
+/**
+ * Crop a image focusing in a point
+ */
public class CropImageFilter extends ImageFilter {
+ public static final String X_PARAM_KEY = "x";
+ public static final String Y_PARAM_KEY = "y";
+ public static final String WIDTH_PARAM_KEY = "w";
+ public static final String HEIGHT_PARAM_KEY = "h";
+
public String[] getAcceptedParameters(){
return new String[] {
"x (int) for left of crop",
"y (int) for top of crop",
"w (int) for width of crop",
- "h (int) for height of crop"
+ "h (int) for height of crop",
+ "fp (int,int) the focal point of the crop"
};
}
- public File runFilter(File file, Map parameters) {
- int x = parameters.get(getPrefix() + "x") != null ? Integer.parseInt(parameters.get(getPrefix() + "x")[0]) : 0;
- int y = parameters.get(getPrefix() + "y") != null ? Integer.parseInt(parameters.get(getPrefix() + "y")[0]) : 0;
- int w = parameters.get(getPrefix() + "w") != null ? Integer.parseInt(parameters.get(getPrefix() + "w")[0]) : 0;
- int h = parameters.get(getPrefix() + "h") != null ? Integer.parseInt(parameters.get(getPrefix() + "h")[0]) : 0;
- if (w == 0 || h == 0) {
- return file;
- }
-
- File resultFile = getResultsFile(file, parameters);
+ public File runFilter(final File file, final Map parameters) {
+ int x = parameters.get(getPrefix() + X_PARAM_KEY) != null ? Integer.parseInt(parameters.get(getPrefix() + X_PARAM_KEY)[0]) : 0;
+ int y = parameters.get(getPrefix() + Y_PARAM_KEY) != null ? Integer.parseInt(parameters.get(getPrefix() + Y_PARAM_KEY)[0]) : 0;
+ final float widthInput = parameters.get(getPrefix() + WIDTH_PARAM_KEY) != null ? Float.parseFloat(parameters.get(getPrefix() + WIDTH_PARAM_KEY)[0]) : 0f;
+ final float heightInput = parameters.get(getPrefix() + HEIGHT_PARAM_KEY) != null ? Float.parseFloat(parameters.get(getPrefix() + HEIGHT_PARAM_KEY)[0]) : 0f;
+ int width = 0;
+ int height = 0;
+
+ final File resultFile = getResultsFile(file, parameters);
if (!overwrite(resultFile, parameters)) {
return resultFile;
}
- BufferedImage src;
try {
- src = ImageIO.read(file);
- if(x > src.getWidth() || y > src.getHeight()){
- return file;
-
- }
+
+ final BufferedImage src = ImageIO.read(file);
+ final Dimension current = new Dimension(src.getWidth(), src.getHeight());
+
+ if(widthInput ==0 && heightInput >0){
+ height = Math.round(heightInput <=1 ? current.height * heightInput : heightInput);
+ width = Math.round(height * current.width / current.height);
+ }
+ else if(widthInput >0 && heightInput ==0){
+ width = Math.round(widthInput <= 1 ? current.width * widthInput : widthInput);
+ height = Math.round(width * current.height / current.width);
+ }
+ else if(widthInput >0 && heightInput >0){
+ width = Math.round(widthInput <= 1 ? current.width * widthInput : widthInput);
+ height = Math.round(heightInput <= 1 ? current.height * heightInput : heightInput);
+ }
+ else{
+ width = current.width;
+ height = current.height;
+ }
+
+ if(x > current.getWidth() || y > current.getHeight()){
+ return file;
+ }
+
+
+
- if(x + w > src.getWidth()){
- w = src.getWidth()-x -1;
+ if(x + width > current.width){
+ width = src.getWidth()-x -1;
}
- if(y + h > src.getHeight()){
- h = src.getHeight()-y-1;
+ if(y + height > current.height){
+ height = src.getHeight()-y-1;
}
+
+ final BufferedImage out = src.getSubimage(x, y, width, height);
+ final File tempResultFile = new File(resultFile.getAbsoluteFile() + "_" + System.currentTimeMillis() +".tmp");
+
+
- BufferedImage out = src.getSubimage(x, y, w, h);
- ImageIO.write(out, FILE_EXT, resultFile);
-
+ ImageIO.write(out, FILE_EXT, tempResultFile);
+ out.flush();
+ tempResultFile.renameTo(resultFile);
} catch (IOException e) {
Logger.error(this.getClass(), e.getMessage());
}
@@ -64,4 +100,7 @@ public File runFilter(File file, Map parameters) {
return resultFile;
}
+
+
+
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/ExposureImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/ExposureImageFilter.java
index d4c945177b9c..76fb53f3e4c7 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/ExposureImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/ExposureImageFilter.java
@@ -20,7 +20,7 @@ public File runFilter(File file, Map parameters) {
double exp = parameters.get(getPrefix() + "exp") != null ? Double.parseDouble(parameters.get(getPrefix()
+ "exp")[0]) : 0.0;
- float f = new Double(exp).floatValue();
+ float f = Double.valueOf(exp).floatValue();
@@ -38,8 +38,15 @@ public File runFilter(File file, Map parameters) {
try {
BufferedImage src = ImageIO.read(file);
+
+ final File tempResultFile = new File(resultFile.getAbsoluteFile() + "_" + System.currentTimeMillis() +".tmp.jpg");
+
+
BufferedImage dst = ef.filter(src, null);
- ImageIO.write(dst, "png", resultFile);
+ ImageIO.write(dst, "png", tempResultFile);
+ dst.flush();
+
+ tempResultFile.renameTo(resultFile);
} catch (IOException e) {
Logger.error(this.getClass(), e.getMessage());
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/FlipImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/FlipImageFilter.java
index e82ac6528618..a1843590cd29 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/FlipImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/FlipImageFilter.java
@@ -38,9 +38,15 @@ public File runFilter(File file, Map parameters) {
if (flip) {
filter.setOperation(FlipFilter.FLIP_H);
}
+ final File tempResultFile = new File(resultFile.getAbsoluteFile() + "_" + System.currentTimeMillis() +".tmp");
+
+
BufferedImage src = ImageIO.read(file);
BufferedImage dst = filter.filter(src, null);
- ImageIO.write(dst, "png", resultFile);
+ ImageIO.write(dst, "png", tempResultFile);
+ dst.flush();
+ tempResultFile.renameTo(resultFile);
+
} catch (IOException e) {
Logger.error(this.getClass(), e.getMessage());
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/GammaImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/GammaImageFilter.java
index a0946dfe8648..3a5ed825e005 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/GammaImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/GammaImageFilter.java
@@ -6,7 +6,7 @@
import java.util.Map;
import javax.imageio.ImageIO;
-
+import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.util.Logger;
import com.dotcms.repackage.com.dotmarketing.jhlabs.image.GammaFilter;
@@ -18,7 +18,7 @@ public String[] getAcceptedParameters() {
public File runFilter(File file, Map parameters) {
double g = parameters.get(getPrefix() + "g") != null ? Double.parseDouble(parameters.get(getPrefix() + "g")[0])
: 0.0;
- float f = new Double(g).floatValue();
+ float f = Double.valueOf(g).floatValue();
File resultFile = getResultsFile(file, parameters);
@@ -36,9 +36,10 @@ public File runFilter(File file, Map parameters) {
BufferedImage dst = filter.filter(src, null);
ImageIO.write(dst, "png", resultFile);
- } catch (IOException e) {
- Logger.error(this.getClass(), e.getMessage());
- }
+ dst.flush();
+ } catch (Exception e) {
+ throw new DotRuntimeException("unable to convert file:" +file + " : " + e.getMessage(),e);
+ }
return resultFile;
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/GifImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/GifImageFilter.java
index c0c125c2ab5e..a95bf2bafb33 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/GifImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/GifImageFilter.java
@@ -15,8 +15,9 @@
import javax.imageio.ImageWriter;
import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageOutputStream;
-
+import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.util.Logger;
+import com.dotmarketing.util.UtilMethods;
public class GifImageFilter extends ImageFilter {
public String[] getAcceptedParameters(){
@@ -27,7 +28,7 @@ public String[] getAcceptedParameters(){
}
public File runFilter(File file, Map parameters) {
- File resultFile = getResultsFile(file, parameters, "gif");
+ File resultFile = getResultsFile(file, parameters);
if(!overwrite(resultFile,parameters)){
return resultFile;
@@ -45,15 +46,17 @@ public File runFilter(File file, Map parameters) {
Graphics2D graphics = dst.createGraphics();
graphics.setPaint ( new Color ( 255, 255, 255 ) );
+ final File tempResultFile = new File(resultFile.getAbsoluteFile() + "_" + System.currentTimeMillis() +".tmp");
graphics.fillRect(0, 0, src.getWidth(), src.getHeight());
graphics.drawImage(src, 0, 0, src.getWidth(), src.getHeight(),null);
- ImageOutputStream ios = ImageIO.createImageOutputStream(resultFile);
+ ImageOutputStream ios = ImageIO.createImageOutputStream(tempResultFile);
writer.setOutput(ios);
writer.write(null,new IIOImage(dst,null,null),iwp);
ios.flush();
writer.dispose();
ios.close();
+ tempResultFile.renameTo(resultFile);
//writer.setOutput(output);
// IIOImage image = new IIOImage(src, null, null);
@@ -74,5 +77,10 @@ public File runFilter(File file, Map parameters) {
return resultFile;
}
+ @Override
+ public File getResultsFile(final File file, final Map parameters) {
+ return getResultsFile(file, parameters, "gif");
+ }
+
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/GrayscaleImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/GrayscaleImageFilter.java
index 9bd1842b4c48..90fe491d6226 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/GrayscaleImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/GrayscaleImageFilter.java
@@ -6,7 +6,7 @@
import java.util.Map;
import javax.imageio.ImageIO;
-
+import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.util.Logger;
import com.dotcms.repackage.com.dotmarketing.jhlabs.image.GrayscaleFilter;
@@ -30,9 +30,10 @@ public File runFilter(File file, Map parameters) {
BufferedImage dst = filter.filter(src, null);
ImageIO.write(dst, "png", resultFile);
- } catch (IOException e) {
- Logger.error(this.getClass(), e.getMessage());
- }
+ dst.flush();
+ } catch (Exception e) {
+ throw new DotRuntimeException("unable to convert file:" +file + " : " + e.getMessage(),e);
+ }
return resultFile;
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/HsbImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/HsbImageFilter.java
index 6c1699e781a2..539ec138524e 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/HsbImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/HsbImageFilter.java
@@ -6,7 +6,7 @@
import java.util.Map;
import javax.imageio.ImageIO;
-
+import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.util.Logger;
import com.dotcms.repackage.com.dotmarketing.jhlabs.image.HSBAdjustFilter;
@@ -49,9 +49,10 @@ public File runFilter(File file, Map parameters) {
BufferedImage dst = filter.filter(src, null);
ImageIO.write(dst, "png", resultFile);
- } catch (IOException e) {
- Logger.error(this.getClass(), e.getMessage());
- }
+ dst.flush();
+ } catch (Exception e) {
+ throw new DotRuntimeException("unable to convert file:" +file + " : " + e.getMessage(),e);
+ }
return resultFile;
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilter.java
index b989db569667..ddc4b25cf58c 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilter.java
@@ -1,5 +1,15 @@
package com.dotmarketing.image.filter;
+
+import com.dotmarketing.business.DotStateException;
+import com.dotmarketing.exception.DotRuntimeException;
+import com.dotmarketing.util.Config;
+import com.dotmarketing.util.ConfigUtils;
+import com.dotmarketing.util.Logger;
+import com.dotmarketing.util.RegEX;
+import com.dotmarketing.util.WebKeys;
+import io.vavr.Lazy;
+import io.vavr.control.Try;
import java.io.File;
import java.io.IOException;
import java.security.MessageDigest;
@@ -10,17 +20,13 @@
import java.util.Map;
import java.util.Map.Entry;
-import com.dotmarketing.business.APILocator;
-import com.dotmarketing.business.DotStateException;
-import com.dotmarketing.exception.DotRuntimeException;
-import com.dotmarketing.util.Config;
-import com.dotmarketing.util.Logger;
-import com.dotmarketing.util.RegEX;
-import com.dotmarketing.util.WebKeys;
-
public abstract class ImageFilter implements ImageFilterIf {
- protected final static String FILE_EXT = "png";
+ protected static final String FILE_EXT = "png";
+ public static final String CROP = "crop";
+
+
+
/**
* the value of this field is used to insure that the generated cache files
* 1) do not overwrite each other.
@@ -35,9 +41,11 @@ public abstract class ImageFilter implements ImageFilterIf {
*/
private String getUniqueFileName(File file, Map parameters, String inode) {
try {
+
+
StringBuilder sb = new StringBuilder();
Iterator> it = parameters.entrySet().iterator();
- List acceptFilter = new ArrayList();
+ List acceptFilter = new ArrayList<>();
String thisFilter="";
if(parameters.get("filter")!=null && parameters.get("filter").length>0){
String[] filters = parameters.get("filter");
@@ -55,12 +63,12 @@ private String getUniqueFileName(File file, Map parameters, St
}
while (it.hasNext()) {
- Map.Entry pairs = (Map.Entry) it.next();
- String key = (String) pairs.getKey();
- String val = ((String[]) pairs.getValue())[0];
+ Map.Entry pairs = it.next();
+ String key = pairs.getKey();
+ String val = pairs.getValue()[0];
- for (String x : acceptFilter) {
- if (key.startsWith(x)) {
+ for (String filterName : acceptFilter) {
+ if (key.startsWith(filterName)) {
sb.append(key + ":" + val);
}
if (key.equalsIgnoreCase("fieldVarName")) {//DOTMCS-5674
@@ -68,10 +76,14 @@ private String getUniqueFileName(File file, Map parameters, St
}
}
}
+
+
+
+
MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
- digest.update((inode + sb.toString() + this.getClass()).getBytes());
+ digest.update((inode + sb.toString() + this.getClass() + file.getName()).getBytes());
StringBuilder ret = new StringBuilder();
ret.append( WebKeys.GENERATED_FILE);
@@ -80,13 +92,13 @@ private String getUniqueFileName(File file, Map parameters, St
- Logger.debug(this.getClass(), "");
- Logger.debug(this.getClass(), "------------------------------------------------------------------");
- Logger.debug(this.getClass(), " for : " + file.getAbsolutePath()+" " + sb);
- Logger.debug(this.getClass(), " with vars: + " + sb);
- Logger.debug(this.getClass(), " unique key: " + ret.toString());
- Logger.debug(this.getClass(), "------------------------------------------------------------------");
- Logger.debug(this.getClass(), "");
+ Logger.debug(this.getClass(), ()->"");
+ Logger.debug(this.getClass(), ()->"------------------------------------------------------------------");
+ Logger.debug(this.getClass(), ()->" for : " + file.getAbsolutePath()+" " + sb);
+ Logger.debug(this.getClass(), ()->" with vars: + " + sb);
+ Logger.debug(this.getClass(), ()->" unique key: " + ret.toString());
+ Logger.debug(this.getClass(), ()->"------------------------------------------------------------------");
+ Logger.debug(this.getClass(), ()->"");
return ret.toString();
} catch (NoSuchAlgorithmException e) {
@@ -96,7 +108,7 @@ private String getUniqueFileName(File file, Map parameters, St
}
private static String convertToHex(byte[] data) {
- StringBuffer buf = new StringBuffer();
+ StringBuilder buf = new StringBuilder();
for (int i = 0; i < data.length; i++) {
int halfbyte = (data[i] >>> 4) & 0x0F;
int two_halfs = 0;
@@ -119,15 +131,11 @@ protected String getPrefix() {
return getFilterName() + "_";
}
+
+
+
protected boolean overwrite(File resultFile, Map parameters){
- boolean overwrite = false;
- long test = resultFile.length();
- if (!resultFile.exists())
- overwrite = true;
- else if (test < 50)
- overwrite = true;
- else if (parameters.get("overwrite") != null)
- overwrite = true;
+ boolean overwrite = !resultFile.exists() || resultFile.length() < 50 || parameters.get("overwrite") != null;
return overwrite;
}
@@ -142,10 +150,16 @@ else if (parameters.get("overwrite") != null)
* @throws IOException
* @throws DotRuntimeException
*/
- protected File getResultsFile(File file, Map parameters) throws DotRuntimeException{
+ public File getResultsFile(File file, Map parameters) {
return getResultsFile(file, parameters, FILE_EXT);
}
+ static Lazy dotGeneratedPath =Lazy.of(() ->
+ "LOCAL".equalsIgnoreCase(Config.getStringProperty("DOTGENERATED_DEFAULT_PATH", "SHARED"))
+ ? ConfigUtils.getDynamicContentPath()
+ : ConfigUtils.getAbsoluteAssetsRootPath());
+
+
/**
* returns the file that can be used to store resutlts.
@@ -156,39 +170,40 @@ protected File getResultsFile(File file, Map parameters) throw
* @throws IOException
* @throws DotRuntimeException
*/
- protected File getResultsFile(File file, Map parameters, String fileExt) throws DotRuntimeException{
+ protected final File getResultsFile(File file, Map parameters, String fileExt) {
String fileFolderPath = file.getParent();
String inode =null;
- try{
- if(file.getName().startsWith(WebKeys.GENERATED_FILE)){
- inode = file.getName();
- String fileNameNoExt = this.getUniqueFileName(file, parameters, inode);
- String resultFilePath =fileFolderPath+ File.separator + fileNameNoExt + "." + fileExt;
- return new File(resultFilePath);
- }
- else{
- try{
- inode = RegEX.find(file.getCanonicalPath(), "[\\w]{8}(-[\\w]{4}){3}-[\\w]{12}").get(0).getMatch();
- }
- catch (Exception e){
- inode = parameters.get("assetInodeOrIdentifier")[0];
- }
- String realAssetPath = APILocator.getFileAssetAPI().getRealAssetsRootPath();
- File dirs = new File(realAssetPath + File.separator + "dotGenerated" + File.separator + inode.charAt(0) + File.separator + inode.charAt(1));
- dirs.mkdirs();
- String fileNameNoExt = this.getUniqueFileName(file, parameters, inode);
- String resultFilePath = dirs.getCanonicalPath() + File.separator + fileNameNoExt + "." + fileExt;
- return new File(resultFilePath);
- }
-
-
+ if (file.getName().startsWith(WebKeys.GENERATED_FILE)) {
+ inode = file.getName();
+ String fileNameNoExt = this.getUniqueFileName(file, parameters, inode);
+ String resultFilePath = fileFolderPath + File.separator + fileNameNoExt + "." + fileExt;
+ return new File(resultFilePath);
+ } else {
+ try {
+ inode = RegEX.find(file.getCanonicalPath(), "[\\w]{8}(-[\\w]{4}){3}-[\\w]{12}").get(0).getMatch();
+ } catch (Exception e) {
+ inode = parameters.get("assetInodeOrIdentifier")[0];
+ }
+
+ File dirs = new File(dotGeneratedPath.get() + File.separator + inode.charAt(0) + File.separator
+ + inode.charAt(1));
+ if (!dirs.exists()) {
+ dirs.mkdirs();
+ }
+ String fileNameNoExt = this.getUniqueFileName(file, parameters, inode);
+ String finalPath = Try.of(dirs::getCanonicalPath).getOrElseThrow(DotRuntimeException::new);
+ String resultFilePath = finalPath + File.separator + fileNameNoExt + "." + fileExt;
+ return new File(resultFilePath);
+ }
- }
- catch(Exception e){
- throw new DotRuntimeException("Cannot find the inode of the file : " + e.getMessage(),e);
- }
}
+
+ public String[] getAcceptedParameters() {
+ return new String[] {
+ };
+ }
+
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilterAPI.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilterAPI.java
index df52016d70ff..cb21de393c21 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilterAPI.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilterAPI.java
@@ -1,9 +1,118 @@
package com.dotmarketing.image.filter;
+import java.awt.Dimension;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.Map;
+import javax.imageio.ImageIO;
+import io.vavr.Function0;
+
public interface ImageFilterAPI {
-
-
-
- public java.io.File getImageFileFromUri(String uri);
+ String CROP = "crop";
+ String EXPOSURE = "exposure";
+ String FLIP = "flip";
+ String FOCAL_POINT = "focalpoint";
+ String GAMMA = "gamma";
+ String GIF = "gif";
+ String GRAY_SCALE = "grayscale";
+ String HSB = "hsb";
+ String JPEG = "jpeg";
+ String JPG = "jpg";
+ String PDF = "pdf";
+ String PNG = "png";
+ String RESIZE = "resize";
+ String ROTATE = "rotate";
+ String SCALE = "scale";
+ String THUMBNAIL = "thumbnail";
+ String THUMB = "thumb";
+ String WEBP = "webp";
+
+ Function0 apiInstance = Function0.of(ImageFilterApiImpl::new).memoized();
+
+ default ImageFilterAPI getInstance() {
+ return apiInstance.apply();
+ }
+
+ /**
+ * returns the filters that have been specified in the filter parameter or in the the arguements
+ * passed in
+ *
+ * @param parameters the parameters passed in
+ * @return a map of the filters that have been specified
+ */
+ Map> resolveFilters(Map parameters);
+
+
+
+ /**
+ * returns an image dimensions
+ *
+ * @param image the image to get the dimensions of
+ * @return the dimensions of the image
+ */
+ Dimension getWidthHeight(File image);
+
+ /**
+ * resizing an image is a slower, more memory intensive operation than subsampling but produces
+ * better looking thumbnails and results in a scaled image that also maintains the aspect ratio..
+ * Resizing should only be done on smaller images (say less than 2000px) as very large images can
+ * cause garbage collections and OOM exceptions. This is because the entire image needs to be
+ * decompressed into heap memory before the resizing operation can take place.
+ *
+ * @param image the image to resize
+ * @param width the width to resize to
+ * @param height the height to resize to
+ * @return the resized image
+ */
+ BufferedImage resizeImage(File image, int width, int height);
+
+
+
+ /**
+ * resizing an image is a slower, more memory intensive operation than subsampling but produces
+ * better looking thumbnails and results in a scaled image that also maintains the aspect ratio..
+ * Resizing should only be done on smaller images (say less than 2000px) as very large images can
+ * cause garbage collections and OOM exceptions. This is because the entire image needs to be
+ * decompressed into heap memory before the resizing operation can take place.
+ *
+ * @param srcImage the image to resize
+ * @param width the width to resize to
+ * @param height the height to resize to
+ * @return the resized image
+ */
+ BufferedImage resizeImage(BufferedImage srcImage, int width, int height);
+
+
+ /**
+ * This method allows you to resize an image and specify what resizing filter should be used to
+ * produce the image. You can see the available filters
+ * @param srcImage the image to resize
+ * @param width the width to resize to
+ * @param height the height to resize to
+ * @param resampleOption the resample option to use
+ * @return the resized image
+ */
+ BufferedImage resizeImage(BufferedImage srcImage, int width, int height, int resampleOption);
+
+
+ BufferedImage resizeImage(File imageFile, int width, int height, int resampleOption);
+
+ /**
+ * subsampling resizes an image by streaming the larger image and collecting every X pixel resulting
+ * in a scaled image that also maintains the aspect ratio. Because it is a stream, subsampling can
+ * resize very large images without causing the memory pressures of resizing. If the width and/or
+ * height is greater than the original image, the original image will be returned.
+ *
+ * @param image the image to subsample
+ * @param width the width to subsample to
+ * @param height the height to subsample to
+ * @return the subsampled image
+ */
+ BufferedImage subsampleImage(File image, int width, int height);
+
+ BufferedImage intelligentResize(File incomingImage, int width, int height, int resampleOption);
+
+ BufferedImage intelligentResize(File incomingImage, int width, int height);
+
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilterApiImpl.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilterApiImpl.java
index fc169dc44e3d..4be87de6175a 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilterApiImpl.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilterApiImpl.java
@@ -1,12 +1,343 @@
package com.dotmarketing.image.filter;
+
+import com.dotmarketing.exception.DotRuntimeException;
+import com.dotmarketing.util.Config;
+import com.dotmarketing.util.Logger;
+import com.dotmarketing.util.UtilMethods;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+import com.liferay.util.StringPool;
+import com.twelvemonkeys.image.ResampleOp;
+import io.vavr.control.Try;
+import java.awt.Dimension;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.spi.ServiceRegistry;
+import javax.imageio.stream.ImageInputStream;
+import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
+import org.apache.batik.util.XMLResourceDescriptor;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.w3c.dom.Document;
public class ImageFilterApiImpl implements ImageFilterAPI {
- public File getImageFileFromUri(String uri) {
- // TODO Auto-generated method stub
- return null;
- }
+
+ ImageFilterApiImpl(){
+ ImageIO.scanForPlugins();
+ deregisterProviders();
+ }
+
+
+
+
+ /**
+ * List of image filter classes accessible by a case-insensitive key
+ */
+ protected static final Map> filterClasses = new ImmutableMap.Builder>()
+ .put(CROP, CropImageFilter.class)
+ .put(EXPOSURE, ExposureImageFilter.class)
+ .put(FLIP, FlipImageFilter.class)
+
+ .put(GAMMA, GammaImageFilter.class)
+ .put(GIF, GifImageFilter.class)
+ .put(GRAY_SCALE, GrayscaleImageFilter.class)
+ .put(HSB, HsbImageFilter.class)
+ .put(JPEG, JpegImageFilter.class)
+ .put(JPG, JpegImageFilter.class)
+ .put(PDF, PDFImageFilter.class)
+ .put(PNG, PngImageFilter.class)
+ .put(RESIZE, ResizeImageFilter.class)
+ .put(SCALE, ScaleImageFilter.class)
+ .put(ROTATE, RotateImageFilter.class)
+ .put(THUMBNAIL, ThumbnailImageFilter.class)
+ .put(THUMB, ThumbnailImageFilter.class)
+
+ .build();
+
+ /**
+ * Anything w or h greater than this pixel size will be shrunk down to this
+ */
+ private static final int MAX_SIZE =
+ Try.of(() -> Config.getIntProperty("IMAGE_MAX_PIXEL_SIZE", 5000)).getOrElse(5000);
+ public static final int DEFAULT_RESAMPLE_OPT =
+ Try.of(() -> Config.getIntProperty("IMAGE_DEFAULT_RESAMPLE_OPT", ResampleOp.FILTER_TRIANGLE))
+ .getOrElse(ResampleOp.FILTER_TRIANGLE);
+ public static final String FILTER = "filter";
+ public static final String FILTERS = "filters";
+
+ @Override
+ public Map> resolveFilters(final Map parameters) {
+ final List filters = new ArrayList<>();
+
+ if (parameters.containsKey(FILTER)) {
+ filters.addAll(Arrays.asList(parameters.get(FILTER)[0].toLowerCase().split(StringPool.COMMA)));
+ } else if (parameters.get(FILTERS) != null) {
+ filters.addAll(Arrays.asList(parameters.get(FILTERS)[0].toLowerCase().split(StringPool.COMMA)));
+ }
+
+ parameters.forEach((key, value) -> {
+ if (key.contains(StringPool.UNDERLINE)) {
+ final String filter = key.substring(0, key.indexOf(StringPool.UNDERLINE));
+ if (!filters.contains(filter)) {
+ filters.add(filter);
+ }
+ }
+ });
+
+ final Map> classes = new LinkedHashMap<>();
+ filters.forEach(s -> {
+ final String filter = s.toLowerCase();
+ if (!classes.containsKey(filter) && filterClasses.containsKey(filter)) {
+ classes.put(s.toLowerCase(), filterClasses.get(filter));
+ }
+ });
+
+ return classes;
+ }
+
+
+ @Override
+ public Dimension getWidthHeight(final File imageFile) {
+
+ if (imageFile == null) {
+ throw new DotRuntimeException("imageFile is null");
+ }
+
+ if (imageFile.getName().toLowerCase().endsWith(".svg")) {
+ try {
+ return getWidthHeightofSVG(imageFile);
+ } catch (Exception e){
+ //If invoking the getWidthHeightofSVG method fails, we will try to get dimensions using the inputStream
+ Logger.debug(this, "Error getting dimensions of SVG file: " + imageFile.getName(), e);
+ }
+ }
+
+
+ try (ImageInputStream inputStream = ImageIO.createImageInputStream(imageFile)) {
+ final ImageReader reader = getReader(imageFile, inputStream);
+ try {
+ reader.setInput(inputStream, true, true);
+ return new Dimension(reader.getWidth(0), reader.getHeight(0));
+ } finally {
+ Try.run(reader::dispose);
+
+ }
+ } catch (Exception e) {
+ throw new DotRuntimeException("error:" + imageFile.getName() + " : " + e, e);
+ }
+
+ }
+
+ @VisibleForTesting
+ Dimension getWidthHeightofSVG(@Nonnull final File imageFile) {
+ SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(
+ XMLResourceDescriptor.getXMLParserClassName());
+
+ try (InputStream is = Files.newInputStream(imageFile.toPath())) {
+ Document document = factory.createDocument(imageFile.toURI().toString(), is);
+
+ String viewBox = document.getDocumentElement().getAttribute("viewBox");
+ String[] viewBoxValues = viewBox.split(" ");
+
+ float x = Float.parseFloat(viewBoxValues[2]);
+ float y = Float.parseFloat(viewBoxValues[3]);
+
+
+ return new Dimension(Math.round(x), Math.round(y));
+
+ } catch (Exception e) {
+ throw new DotRuntimeException("unable to parse svg file: " + imageFile.getAbsolutePath() + " : " + e.getMessage(), e);
+ }
+ }
+
+ private T lookupProviderByName(final ServiceRegistry registry, final String providerClassName) {
+ try {
+ return (T) registry.getServiceProviderByClass(Class.forName(providerClassName));
+ }
+ catch (ClassNotFoundException ignore) {
+ return null;
+ }
+ }
+
+ private String[] providersToIgnore = Config.getStringArrayProperty("IMAGE_READER_SPIS_TO_DEREGISTER", new String[]{"net.sf.javavp8decoder.imageio.WebPImageReaderSpi"});
+
+
+ private void deregisterProviders() {
+
+ IIORegistry registry = IIORegistry.getDefaultInstance();
+
+ for(String providerClazz: providersToIgnore) {
+ ImageReaderSpi provider= lookupProviderByName(registry, providerClazz);
+ registry.deregisterServiceProvider(provider);
+ }
+
+ }
+
+
+
+
+ /**
+ * gets the reader based on both the input stream and the file extension
+ *
+ * @param imageFile the image file
+ * @param inputStream the input stream
+ * @return the reader
+ */
+ ImageReader getReader(File imageFile, ImageInputStream inputStream) {
+ Set readers = new LinkedHashSet<>();
+ ImageIO.getImageReaders(inputStream).forEachRemaining(readers::add);
+ ImageIO.getImageReadersBySuffix(UtilMethods.getFileExtension(imageFile.getName()))
+ .forEachRemaining(readers::add);
+
+
+
+ return readers.stream().findFirst().orElseThrow(()->new DotRuntimeException("Unable to find reader for image:" + imageFile));
+
+ }
+
+ @Override
+ public BufferedImage resizeImage(final BufferedImage srcImage, int width, int height) {
+
+ return this.resizeImage(srcImage, width, height, DEFAULT_RESAMPLE_OPT);
+
+ }
+
+ @Override
+ public BufferedImage resizeImage(final BufferedImage srcImage, int width, int height, int resampleOption) {
+
+ width = Math.min(MAX_SIZE, width);
+ height = Math.min(MAX_SIZE, height);
+ resampleOption = Math.max(resampleOption, 0);
+ resampleOption = Math.min(resampleOption, 15);
+
+ BufferedImageOp resampler = new ResampleOp(width, height, resampleOption);
+ return resampler.filter(srcImage, null);
+
+ }
+
+ @Override
+ public BufferedImage resizeImage(final File imageFile, final int width, final int height) {
+ return resizeImage(imageFile, width, height, DEFAULT_RESAMPLE_OPT);
+ }
+
+
+ @Override
+ public BufferedImage resizeImage(final File imageFile, final int width, final int height, int resampleOption) {
+ final Dimension sourceSize = getWidthHeight(imageFile);
+
+ try (ImageInputStream inputStream = ImageIO.createImageInputStream(imageFile)) {
+ final ImageReader reader = getReader(imageFile, inputStream);
+ try {
+ reader.setInput(inputStream, true, true);
+ if (sourceSize.getWidth() == width && sourceSize.getHeight() == height) {
+ return reader.read(0);
+ }
+
+ return this.resizeImage(reader.read(0), width, height, resampleOption);
+
+ } finally {
+ reader.dispose();
+ }
+
+ } catch (Exception e) {
+ throw new DotRuntimeException(e);
+ }
+ }
+
+ @Override
+ public BufferedImage intelligentResize(File incomingImage, int width, int height) {
+
+ return intelligentResize(incomingImage, width, height, DEFAULT_RESAMPLE_OPT);
+
+ }
+
+
+
+ @Override
+ public BufferedImage intelligentResize(File incomingImage, int width, int height, int resampleOption) {
+
+
+ final String hash = DigestUtils.sha256Hex(incomingImage.getAbsolutePath());
+ Dimension originalSize = getWidthHeight(incomingImage);
+
+
+ width = Math.min(MAX_SIZE, width);
+ height = Math.min(MAX_SIZE, height);
+
+ // resample huge images to a maxSize (prevents OOM)
+ if ((originalSize.width > MAX_SIZE || originalSize.height > MAX_SIZE)) {
+ final Map params = ImmutableMap.of(
+ "subsample_w", new String[] {String.valueOf(MAX_SIZE)},
+ "subsample_h", new String[] {String.valueOf(MAX_SIZE)},
+ "subsample_hash", new String[] {hash},
+ FILTER, new String[] {"subsample"}
+ );
+ incomingImage = new SubSampleImageFilter().runFilter(incomingImage, params);
+ }
+
+ return this.resizeImage(incomingImage, width, height, resampleOption);
+
+ }
+
+ @Override
+ public BufferedImage subsampleImage(final File image, final int width, final int height) {
+
+ try (ImageInputStream inputStream = ImageIO.createImageInputStream(image)) {
+ final ImageReader reader = getReader(image, inputStream);
+
+ try {
+ return this.subsampleImage(inputStream, reader, width, height);
+ } finally {
+ Try.run(reader::dispose);
+ }
+ } catch (Exception e) {
+ throw new DotRuntimeException(e);
+ }
+ }
+
+ BufferedImage subsampleImage(final ImageInputStream inputStream, final ImageReader reader, final int width,
+ final int height) throws IOException {
+
+ final ImageReadParam imageReaderParams = reader.getDefaultReadParam();
+
+ reader.setInput(inputStream, true, true);
+ final Dimension sourceSize = new Dimension(reader.getWidth(0), reader.getHeight(0));
+ final Dimension targetSize = new Dimension(width, height);
+ final int subsampling = (int) scaleSubsamplingMaintainAspectRatio(sourceSize, targetSize);
+
+ imageReaderParams.setSourceSubsampling(subsampling, subsampling, 0, 0);
+
+ return reader.read(0, imageReaderParams);
+
+ }
+
+ private long scaleSubsamplingMaintainAspectRatio(final Dimension sourceSize, final Dimension targetSize) {
+
+ if (sourceSize.getWidth() > targetSize.getWidth()) {
+ return (long) Math.floor(sourceSize.getWidth() / targetSize.getWidth());
+ } else if (sourceSize.getHeight() > targetSize.getHeight()) {
+ return (long) Math.floor(sourceSize.getHeight() / targetSize.getHeight());
+ }
+
+ return 1;
+ }
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilterIf.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilterIf.java
index 51c33ef6a199..45eb292bd895 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilterIf.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/ImageFilterIf.java
@@ -5,12 +5,24 @@
import com.dotmarketing.business.DotStateException;
+/**
+ * Encapsulates the basic interface to do a filter over image.
+ */
public interface ImageFilterIf {
-
+ /**
+ * Applies the filter over a file based on the parameters
+ * @param file {@link File} original file
+ * @param parameters {@link Map} parameters
+ * @return File with the filter applied
+ * @throws DotStateException
+ */
public File runFilter(File file, Map parameters) throws DotStateException;
-
+ /**
+ * Return an array with guide or help
+ * @return String
+ */
public String[] getAcceptedParameters();
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/JpegImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/JpegImageFilter.java
index c45b26b9c4ec..513cdb90946a 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/JpegImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/JpegImageFilter.java
@@ -14,7 +14,7 @@
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
-
+import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.util.Logger;
public class JpegImageFilter extends ImageFilter {
@@ -30,7 +30,7 @@ public File runFilter(File file, Map parameters) {
- Double q = new Double(quality);
+ Double q = Double.valueOf(quality);
q = q/100;
File resultFile = getResultsFile(file, parameters, "jpg");
@@ -59,23 +59,23 @@ public File runFilter(File file, Map parameters) {
graphics.fillRect(0, 0, src.getWidth(), src.getHeight());
graphics.drawImage(src, 0, 0, src.getWidth(), src.getHeight(),null);
- ImageOutputStream ios = ImageIO.createImageOutputStream(resultFile);
- writer.setOutput(ios);
- writer.write(null,new IIOImage(dst,null,null),iwp);
- ios.flush();
- writer.dispose();
- ios.close();
- //writer.setOutput(output);
+
+
+ final File tempResultFile = new File(resultFile.getAbsoluteFile() + "_" + System.currentTimeMillis() +".tmp.jpg");
- // IIOImage image = new IIOImage(src, null, null);
- // writer.write(null, image, iwp);
- // writer.dispose();
+
+ try(ImageOutputStream ios = ImageIO.createImageOutputStream(tempResultFile)){
+ writer.setOutput(ios);
+ writer.write(null,new IIOImage(dst,null,null),iwp);
+ ios.flush();
+ writer.dispose();
+ }
+ tempResultFile.renameTo(resultFile);
+
- } catch (FileNotFoundException e) {
- Logger.error(this.getClass(), e.getMessage());
- } catch (IOException e) {
- Logger.error(this.getClass(), e.getMessage());
+ } catch (Exception e) {
+ throw new DotRuntimeException("unable to convert file:" +file + " : " + e.getMessage(),e);
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/PDFImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/PDFImageFilter.java
index 0cd00a95f011..0dfc34d5f51f 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/PDFImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/PDFImageFilter.java
@@ -1,85 +1,57 @@
package com.dotmarketing.image.filter;
-import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Iterator;
import java.util.Map;
-
-import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
-import javax.imageio.ImageWriteParam;
-import javax.imageio.ImageWriter;
-import javax.imageio.stream.ImageOutputStream;
-
+import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.pdmodel.PDDocument;
-import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
-
+import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.util.Config;
-import com.dotmarketing.util.Logger;
+
public class PDFImageFilter extends ImageFilter {
- public String[] getAcceptedParameters() {
- return new String[] {
- };
- }
+ static final long PDF_RENDERER_MAX_MEMORY_BYTES = Config.getLongProperty("PDF_RENDERER_MAX_MEMORY_BYTES", 1024L * 1024 * 50);
- public File runFilter(File file, Map parameters) {
+ public File runFilter(File file, Map parameters) {
- File resultFile = getResultsFile(file, parameters);
- if (!overwrite(resultFile, parameters)) {
- return resultFile;
- }
- int page = parameters.get(getPrefix() + "page") != null ? Integer.parseInt(parameters.get(getPrefix() + "page")[0]) : 1;
+ File resultFile = getResultsFile(file, parameters);
- int dpi = parameters.get(getPrefix() + "dpi") != null ? Integer.parseInt(parameters.get(getPrefix() + "dpi")[0]) : 72;
+ if (!overwrite(resultFile, parameters)) {
+ return resultFile;
+ }
+ int page = parameters.get(getPrefix() + "page") != null ? Integer.parseInt(parameters.get(getPrefix() + "page")[0]) : 1;
+ float dpi = parameters.get(getPrefix() + "dpi") != null ? Float.parseFloat(parameters.get(getPrefix() + "dpi")[0]) : 72f;
+
+ float scale = dpi / 72f;
-
-
- resultFile.delete();
- try {
-
- System.setProperty("sun.java2d.cmm", Config.getStringProperty("IMAGE_COLOR_MANAGEMENT_SYSTEM", "sun.java2d.cmm.kcms.KcmsServiceProvider"));
- PDDocument document = PDDocument.load(file);
- PDFRenderer pdfRenderer = new PDFRenderer(document);
-
- BufferedImage bim = pdfRenderer.renderImageWithDPI(--page, dpi, ImageType.RGB);
- Iterator iter = ImageIO.getImageWritersByFormatName("png");
- ImageWriter writer = iter.next();
- ImageWriteParam iwp = writer.getDefaultWriteParam();
- BufferedImage dst = new BufferedImage(bim.getWidth(), bim.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
- Graphics2D graphics = dst.createGraphics();
-
- // graphics.fillRect(0, 0, src.getWidth(), src.getHeight());
- graphics.drawImage(bim, 0, 0, bim.getWidth(), bim.getHeight(), null);
- ImageOutputStream ios = ImageIO.createImageOutputStream(resultFile);
- writer.setOutput(ios);
- writer.write(null, new IIOImage(dst, null, null), iwp);
- ios.flush();
- writer.dispose();
- ios.close();
-
- document.close();
-
-
- } catch (FileNotFoundException e) {
- Logger.error(this.getClass(), e.getMessage());
- } catch (IOException e) {
- Logger.error(this.getClass(), e.getMessage());
- }
+ final File tempResultFile = new File(resultFile.getAbsoluteFile() + "_" + System.currentTimeMillis() + ".tmp.png");
+
+ try (PDDocument document = PDDocument.load(file, MemoryUsageSetting.setupMixed(PDF_RENDERER_MAX_MEMORY_BYTES))) {
+ PDFRenderer pdfRenderer = new PDFRenderer(document);
+ pdfRenderer.setSubsamplingAllowed(true);
+ BufferedImage bim = pdfRenderer.renderImage(--page, scale);
+ ImageIO.write(bim, "PNG", tempResultFile);
+ if (!tempResultFile.renameTo(resultFile)) {
+ throw new DotRuntimeException("unable to create file:" + resultFile);
+ }
+ return resultFile;
+ } catch (Exception e) {
+ throw new DotRuntimeException("unable to convert file:" + file + " : " + e.getMessage(), e);
+ }
- return resultFile;
- }
+
+ }
+
+
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/PngImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/PngImageFilter.java
index b791f747d269..b8c0eb382674 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/PngImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/PngImageFilter.java
@@ -1,30 +1,19 @@
package com.dotmarketing.image.filter;
-import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
-
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
-import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageOutputStream;
-
-import com.dotmarketing.util.Logger;
+import com.dotmarketing.exception.DotRuntimeException;
public class PngImageFilter extends ImageFilter {
- public String[] getAcceptedParameters(){
- return new String[] {
-
- };
- }
public File runFilter(File file, Map parameters) {
File resultFile = getResultsFile(file, parameters);
@@ -33,8 +22,10 @@ public File runFilter(File file, Map parameters) {
return resultFile;
}
- resultFile.delete();
+
try{
+ final File tempResultFile = new File(resultFile.getAbsoluteFile() + "_" + System.currentTimeMillis() + ".tmp.png");
+
BufferedImage src = ImageIO.read(file);
Iterator iter = ImageIO.getImageWritersByFormatName("png");
ImageWriter writer = iter.next();
@@ -42,25 +33,24 @@ public File runFilter(File file, Map parameters) {
BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D graphics = dst.createGraphics();
- //graphics.fillRect(0, 0, src.getWidth(), src.getHeight());
+
graphics.drawImage(src, 0, 0, src.getWidth(), src.getHeight(),null);
- ImageOutputStream ios = ImageIO.createImageOutputStream(resultFile);
- writer.setOutput(ios);
- writer.write(null,new IIOImage(dst,null,null),iwp);
- ios.flush();
- writer.dispose();
- ios.close();
- } catch (FileNotFoundException e) {
- Logger.error(this.getClass(), e.getMessage());
- } catch (IOException e) {
- Logger.error(this.getClass(), e.getMessage());
- }
-
-
-
-
+ try(ImageOutputStream ios = ImageIO.createImageOutputStream(tempResultFile)){
+ writer.setOutput(ios);
+ writer.write(null,new IIOImage(dst,null,null),iwp);
+ ios.flush();
+ writer.dispose();
+ }
+ if (!tempResultFile.renameTo(resultFile)) {
+ throw new DotRuntimeException("unable to create file:" + resultFile);
+ }
+ return resultFile;
+
+
+ } catch (Exception e) {
+ throw new DotRuntimeException("unable to convert file:" +file + " : " + e.getMessage(),e);
+ }
- return resultFile;
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/ResizeCalc.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/ResizeCalc.java
new file mode 100644
index 000000000000..0b3c3c35b9a2
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/ResizeCalc.java
@@ -0,0 +1,202 @@
+package com.dotmarketing.image.filter;
+
+import java.awt.Dimension;
+import javax.annotation.Nonnull;
+
+
+public class ResizeCalc {
+
+ private final int originalWidth;
+ private final int originalHeight;
+ private final int desiredWidth;
+ private final int desiredHeight;
+ private final int maxWidth;
+ private final int maxHeight;
+ private final int minWidth;
+ private final int minHeight;
+
+ private ResizeCalc(Builder builder) {
+ this.originalWidth = builder.originalWidth;
+ this.originalHeight = builder.originalHeight;
+ this.desiredWidth = builder.desiredWidth;
+ this.desiredHeight = builder.desiredHeight;
+ this.maxWidth = builder.maxWidth;
+ this.maxHeight = builder.maxHeight;
+ this.minWidth = builder.minWidth;
+ this.minHeight = builder.minHeight;
+
+ }
+
+
+ public Dimension getDim() {
+
+
+
+ // if we have a width and/or height, respect it and ignore maxw and maxh
+ if (desiredWidth > 0 || desiredHeight > 0) {
+ return doResize();
+ }
+
+ if (maxWidth <= 0 && maxHeight <= 0 && minWidth <= 0 && minHeight <= 0) {
+ return doNothing();
+ }
+
+ // if the source is smaller than maxw && maxh, ignore
+ if (maxWidth >= originalWidth && maxHeight >= originalHeight) {
+ return doNothing();
+ }
+
+ // if both maxw and maxh are set, figure out which to respect
+ if (maxWidth > 0 && maxHeight > 0) {
+ return doMaxWidthAndHeight();
+ }
+
+ // only max width
+ if (maxWidth > 0) {
+ return doMaxWidth();
+ }
+
+ // only max height
+ if (maxHeight > 0) {
+ return doMaxHeight();
+ }
+
+ // if the source is smaller than minw && minh, ignore
+ if (minWidth <= originalWidth && minHeight <= originalHeight) {
+ return doNothing();
+ }
+
+ // if both minw and minh are set, figure out which to respect
+ if (minWidth > 0 && minHeight > 0) {
+ return doMinWidthAndHeight();
+ }
+
+ // only max width
+ if (minWidth > 0) {
+ return doMinWidth();
+ }
+
+ // only max height
+ if (minHeight > 0) {
+ return doMinHeight();
+ }
+
+
+ return doNothing();
+ }
+
+
+ private Dimension doResize() {
+ int finalWidth = desiredWidth == 0 && desiredHeight > 0 ? desiredHeight * originalWidth / originalHeight : desiredWidth;
+ int finalHeight = desiredWidth > 0 && desiredHeight == 0 ? desiredWidth * originalHeight / originalWidth : desiredHeight;
+ return new Dimension(finalWidth, finalHeight);
+ }
+
+ private Dimension doNothing() {
+
+ return new Dimension(originalWidth, originalHeight);
+ }
+
+ private Dimension doMaxWidthAndHeight() {
+
+ int testHeight = (maxWidth * originalHeight) / originalWidth;
+ int testWidth = ((maxHeight * originalWidth) / originalHeight);
+ int finalWidth = maxWidth > testWidth ? testWidth : maxWidth;
+ int finalHeight = maxHeight > testHeight ? testHeight : maxHeight;
+ return new Dimension(finalWidth, finalHeight);
+ }
+
+ private Dimension doMinWidthAndHeight() {
+
+ int testHeight = (minWidth * originalHeight) / originalWidth;
+ int testWidth = ((minHeight * originalWidth) / originalHeight);
+ int finalWidth = minWidth < testWidth ? testWidth : minWidth;
+ int finalHeight = minHeight < testHeight ? testHeight : minHeight;
+ return new Dimension(finalWidth, finalHeight);
+ }
+
+ private Dimension doMinWidth() {
+
+ int finalWidth = minWidth > originalWidth ? minWidth : originalWidth;
+ int finalHeight = (finalWidth * originalHeight) / originalWidth;
+ return new Dimension(finalWidth, finalHeight);
+ }
+
+ private Dimension doMinHeight() {
+
+ int finalHeight = minHeight > originalHeight ? minHeight : originalHeight ;
+ int finalWidth = (finalHeight * originalWidth) / originalHeight;
+ return new Dimension(finalWidth, finalHeight);
+ }
+
+ private Dimension doMaxWidth() {
+
+ int finalWidth = maxWidth > originalWidth ? originalWidth : maxWidth;
+ int finalHeight = (finalWidth * originalHeight) / originalWidth;
+ return new Dimension(finalWidth, finalHeight);
+ }
+
+ private Dimension doMaxHeight() {
+
+ int finalHeight = maxHeight > originalHeight ? originalHeight : maxHeight;
+ int finalWidth = (finalHeight * originalWidth) / originalHeight;
+ return new Dimension(finalWidth, finalHeight);
+ }
+
+ public static final class Builder {
+ private int originalWidth;
+ private int originalHeight;
+ private int desiredWidth;
+ private int desiredHeight;
+ private int maxWidth;
+ private int maxHeight;
+ private int minWidth;
+ private int minHeight;
+
+ public Builder(int originalWidth, int originalHeight) {
+ this.originalWidth = originalWidth;
+ this.originalHeight = originalHeight;
+ }
+
+ public Builder(Dimension dim) {
+ this.originalWidth = dim.width;
+ this.originalHeight = dim.height;
+ }
+
+
+ public Builder desiredWidth(@Nonnull int desiredWidth) {
+ this.desiredWidth = desiredWidth;
+ return this;
+ }
+
+ public Builder desiredHeight(@Nonnull int desiredHeight) {
+ this.desiredHeight = desiredHeight;
+ return this;
+ }
+
+ public Builder maxWidth(@Nonnull int maxWidth) {
+ this.maxWidth = maxWidth;
+ return this;
+ }
+
+ public Builder maxHeight(@Nonnull int maxHeight) {
+ this.maxHeight = maxHeight;
+ return this;
+ }
+ public Builder minWidth(@Nonnull int minWidth) {
+ this.minWidth = minWidth;
+ return this;
+ }
+
+ public Builder minHeight(@Nonnull int minHeight) {
+ this.minHeight = minHeight;
+ return this;
+ }
+ public ResizeCalc build() {
+ return new ResizeCalc(this);
+ }
+ }
+
+
+
+}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/ResizeGifImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/ResizeGifImageFilter.java
new file mode 100644
index 000000000000..9b7b7a9a012b
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/ResizeGifImageFilter.java
@@ -0,0 +1,103 @@
+package com.dotmarketing.image.filter;
+
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+import com.dotmarketing.exception.DotRuntimeException;
+import com.dotmarketing.image.gif.AnimatedGifEncoder;
+import com.dotmarketing.image.gif.GifDecoder;
+import com.dotmarketing.util.Logger;
+import com.twelvemonkeys.image.ResampleOp;
+
+import io.vavr.control.Try;
+
+public class ResizeGifImageFilter extends ImageFilter {
+ public String[] getAcceptedParameters() {
+ return new String[] {"w (int) specifies width", "h (int) specifies height, loop=true|false, maxFrames (int)",};
+ }
+
+ @Override
+ protected String getPrefix() {
+
+ return "resize_";
+ }
+
+ public File runFilter(final File file, final Map parameters) {
+ double w = Try.of(() -> Integer.parseInt(parameters.get(getPrefix() + "w")[0])).getOrElse(0);
+ double h = Try.of(() -> Integer.parseInt(parameters.get(getPrefix() + "h")[0])).getOrElse(0);
+
+ final int loop = Try.of(() -> Integer.parseInt(parameters.get(getPrefix() + "loop")[0])).getOrElse(0);
+
+ final int maxFrames = Try.of(() -> Integer.parseInt(parameters.get(getPrefix() + "frames")[0])).getOrElse(Integer.MAX_VALUE);
+
+ final File resultFile = getResultsFile(file, parameters);
+
+ if (!overwrite(resultFile, parameters)) {
+ return resultFile;
+ }
+ resultFile.delete();
+
+ try {
+ BufferedImage src = ImageIO.read(file);
+ if (w == 0 && h == 0) {
+ return file;
+ } else if (w == 0 && h > 0) {
+ w = Math.round(h * src.getWidth() / src.getHeight());
+ } else if (w > 0 && h == 0) {
+ h = Math.round(w * src.getHeight() / src.getWidth());
+ }
+
+ final int width = (int) w;
+ final int height = (int) h;
+
+ File tempResultFile = new File(resultFile.getAbsoluteFile() + "_" + System.currentTimeMillis() + ".tmp");
+ readWriteGIF(file, tempResultFile, maxFrames, loop, width, height);
+ tempResultFile.renameTo(resultFile);
+ return resultFile;
+ } catch (Exception e) {
+ throw new DotRuntimeException("unable to convert file:" +file + " : " + e.getMessage(),e);
+ }
+
+ }
+
+ private void readWriteGIF(File inputFile, File outputFile, final int maxFrames, final int loop, int width, int height)
+ throws IOException {
+ final BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_TRIANGLE);
+ final GifDecoder decoder = new GifDecoder();
+ decoder.read(inputFile.getAbsolutePath());
+ if(decoder.getFrameCount()==1) {
+ BufferedImage dst = resampler.filter(ImageIO.read(inputFile), null);
+ ImageIO.write(dst, "png", outputFile);
+ dst.flush();
+ return;
+ }
+
+
+
+
+ int frames = Math.min(maxFrames, decoder.getFrameCount());
+
+ AnimatedGifEncoder animatedGif = new AnimatedGifEncoder();
+ animatedGif.start(outputFile.getAbsolutePath());
+ animatedGif.setDelay(decoder.getDelay(0));
+ animatedGif.setRepeat(loop);
+ animatedGif.setSize(width, height);
+ animatedGif.setQuality(20);
+ //animatedGif.setTransparent(Color.WHITE, false);
+ for (int i = 0; i < frames; i++) {
+
+ BufferedImage frame = decoder.getFrame(i); // frame i
+
+ animatedGif.addFrame(resampler.filter(frame, null));
+ // animatedGif.addFrame(frame);
+ }
+ animatedGif.finish();
+
+ }
+
+}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/ResizeImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/ResizeImageFilter.java
index 876491d2a8fd..a0b69d2eece3 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/ResizeImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/ResizeImageFilter.java
@@ -1,25 +1,45 @@
package com.dotmarketing.image.filter;
-import com.dotmarketing.util.Logger;
-import com.twelvemonkeys.image.ResampleOp;
+import java.awt.Dimension;
import java.awt.image.BufferedImage;
-import java.awt.image.BufferedImageOp;
import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
import java.util.Map;
import javax.imageio.ImageIO;
+import com.dotmarketing.exception.DotRuntimeException;
+import io.vavr.control.Try;
public class ResizeImageFilter extends ImageFilter {
public String[] getAcceptedParameters(){
return new String[] {
"w (int) specifies width",
"h (int) specifies height",
+ "i (int) interpolation",
+ "maxw (int) specifies maxWidth",
+ "maxh (int) specifies maxHeight",
+ "minw (int) specifies minWidth",
+ "minh (int) specifies minHeight"
};
}
- public File runFilter(File file, Map parameters) {
- double w = parameters.get(getPrefix() +"w") != null?Integer.parseInt(parameters.get(getPrefix() +"w")[0]):0;
- double h = parameters.get(getPrefix() +"h") != null?Integer.parseInt(parameters.get(getPrefix() +"h")[0]):0;
+ public File runFilter(final File file, Map parameters) {
+
+ final int w = Try.of(()-> Integer.parseInt(parameters.getOrDefault(getPrefix() + "w", new String[]{"0"})[0])).getOrElse(0);
+ final int h = Try.of(()-> Integer.parseInt(parameters.getOrDefault(getPrefix() + "h", new String[]{"0"})[0])).getOrElse(0);
+ final int resampleOpts = Try.of(()-> Integer.parseInt(parameters.get(getPrefix() +"ro")[0])).getOrElse(ImageFilterApiImpl.DEFAULT_RESAMPLE_OPT);
+ final int mxw = Try.of(()-> Integer.parseInt(parameters.getOrDefault(getPrefix() + "maxw", new String[]{"0"})[0])).getOrElse(0);
+ final int mxh = Try.of(()-> Integer.parseInt(parameters.getOrDefault(getPrefix() + "maxh", new String[]{"0"})[0])).getOrElse(0);
+ final int mnw = Try.of(()-> Integer.parseInt(parameters.getOrDefault(getPrefix() + "minw", new String[]{"0"})[0])).getOrElse(0);
+ final int mnh = Try.of(()-> Integer.parseInt(parameters.getOrDefault(getPrefix() + "minh", new String[]{"0"})[0])).getOrElse(0);
+
+
+
+ if(file.getName().endsWith(".gif")) {
+ return new ResizeGifImageFilter().runFilter(file, parameters);
+ }
+
+
+ if (w == 0 && h == 0 && mxh == 0 && mxw == 0&& mnh == 0 && mnw == 0) {
+ return file;
+ }
File resultFile = getResultsFile(file, parameters);
@@ -28,37 +48,47 @@ public File runFilter(File file, Map parameters) {
}
resultFile.delete();
- try {
-
- BufferedImage src = ImageIO.read(file);
- if(w ==0 && h ==0){
- return file;
- }
+
+ final Dimension originalSize = ImageFilterAPI.apiInstance.get().getWidthHeight(file);
+
+ final Dimension newSize = new ResizeCalc.Builder(originalSize)
+ .desiredHeight(h)
+ .desiredWidth(w)
+ .maxWidth(mxw)
+ .maxHeight(mxh)
+ .minHeight(mnh)
+ .minWidth(mnw)
+ .build()
+ .getDim();
+
+
+ if(originalSize.equals(newSize)) {
+ return file;
+ }
-
- if(w ==0 && h >0){
- w = Math.round(h * src.getWidth() / src.getHeight());
- }
- if(w >0 && h ==0){
- h = Math.round(w * src.getHeight() / src.getWidth());
- }
-
- int width = (int) w;
- int hieght = (int) h;
+ try {
+ File tempResultFile = new File(resultFile.getAbsoluteFile() + "_" + System.currentTimeMillis() + ".tmp");
+ // resample from stream
+ BufferedImage srcImage = ImageFilterAPI.apiInstance.get().intelligentResize(file, newSize.width, newSize.height,resampleOpts);
+ ImageIO.write(srcImage, "png", tempResultFile);
+ srcImage.flush();
+ srcImage = null;
+ if(tempResultFile.renameTo(resultFile)) {
+ return resultFile;
+ }
+ throw new DotRuntimeException("unable to create tmp file :" + resultFile);
+ } catch (Exception e) {
+ throw new DotRuntimeException("unable to convert file:" +file + " : " + e.getMessage(),e);
+ }
- BufferedImageOp resampler = new ResampleOp(width, hieght, ResampleOp.FILTER_LANCZOS); // A good default filter, see class documentation for more info
- BufferedImage output = resampler.filter(ImageIO.read(file), null);
- ImageIO.write(output, "png", resultFile);
- return resultFile;
+ }
+
+
+
+
+
+
- } catch (FileNotFoundException e) {
- Logger.error(this.getClass(), e.getMessage());
- } catch (IOException e) {
- Logger.error(this.getClass(), e.getMessage());
- }
-
- return resultFile;
- }
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/RotateImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/RotateImageFilter.java
index 9346be2d526e..f143836ed300 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/RotateImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/RotateImageFilter.java
@@ -6,7 +6,7 @@
import java.util.Map;
import javax.imageio.ImageIO;
-
+import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.util.Logger;
import com.dotcms.repackage.com.dotmarketing.jhlabs.image.RotateFilter;
@@ -27,7 +27,7 @@ public File runFilter(File file, Map parameters) {
return resultFile;
}
- float x = new Double(java.lang.Math.toRadians(a)).floatValue();
+ float x = Double.valueOf(java.lang.Math.toRadians(a)).floatValue();
RotateFilter filter = new RotateFilter(x, true);
filter.setEdgeAction(RotateFilter.ZERO);
@@ -52,9 +52,10 @@ public File runFilter(File file, Map parameters) {
* }
*/
ImageIO.write(dst, "png", resultFile);
- } catch (IOException e) {
- Logger.error(this.getClass(), e.getMessage());
- }
+ dst.flush();
+ } catch (Exception e) {
+ throw new DotRuntimeException("unable to convert file:" +file + " : " + e.getMessage(),e);
+ }
return resultFile;
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/ScaleImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/ScaleImageFilter.java
index 97d8b1fc908e..825d42adf703 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/ScaleImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/ScaleImageFilter.java
@@ -1,78 +1,9 @@
package com.dotmarketing.image.filter;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Map;
-
-import javax.imageio.ImageIO;
-
-import com.dotmarketing.util.Logger;
-import com.dotcms.repackage.com.dotmarketing.jhlabs.image.ScaleFilter;
-
-public class ScaleImageFilter extends ImageFilter {
- public String[] getAcceptedParameters(){
- return new String[] {
- "w (int) specifies width",
- "h (int) specifies height",
- };
- }
- public File runFilter(File file, Map parameters) {
- int w = parameters.get(getPrefix() +"w") != null?Integer.parseInt(parameters.get(getPrefix() +"w")[0]):0;
- int h = parameters.get(getPrefix() +"h") != null?Integer.parseInt(parameters.get(getPrefix() +"h")[0]):0;
-
- File resultFile = getResultsFile(file, parameters);
-
-
-
- if(!overwrite(resultFile,parameters)){
- return resultFile;
- }
- resultFile.delete();
-
-
-
- try {
-
- BufferedImage src = ImageIO.read(file);
- if(w ==0 && h ==0){
- return file;
- }
- if(w ==0 && h >0){
- w = h * src.getWidth() / src.getHeight();
- }
- if(w >0 && h ==0){
- h =w * src.getHeight() / src.getWidth();
- }
-
-
-
-
-
-
-
- ScaleFilter filter = new ScaleFilter(w,h);
-
- BufferedImage dst = new BufferedImage(w, h,
- BufferedImage.TYPE_INT_ARGB);
-
- dst = filter.filter(src, dst);
- ImageIO.write(dst, "png", resultFile);
- return resultFile;
-
- } catch (FileNotFoundException e) {
- Logger.error(this.getClass(), e.getMessage());
- } catch (IOException e) {
- Logger.error(this.getClass(), e.getMessage());
- }
-
-
-
-
-
- return resultFile;
- }
-
+/**
+ * Empty class for backwards compatibility of the scale_w/800/scale_h/500 filter
+ *
+ */
+public class ScaleImageFilter extends ResizeImageFilter {
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/SubSampleImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/SubSampleImageFilter.java
new file mode 100644
index 000000000000..d0e3e54256d0
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/SubSampleImageFilter.java
@@ -0,0 +1,63 @@
+package com.dotmarketing.image.filter;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.Map;
+import javax.imageio.ImageIO;
+import com.dotmarketing.exception.DotRuntimeException;
+
+public class SubSampleImageFilter extends ImageFilter {
+
+
+ public String[] getAcceptedParameters() {
+ return new String[] {"w (int) specifies width", "h (int) specifies height",};
+ }
+
+ public File runFilter(final File file, Map parameters) {
+ final String[] widthParam = parameters.get(getPrefix() + "w");
+ int width = widthParam != null ? Integer.parseInt(widthParam[0])
+ : 0;
+ final String[] heightParam = parameters.get(getPrefix() + "h");
+ int height = heightParam != null ? Integer.parseInt(heightParam[0])
+ : 0;
+
+
+
+ File resultFile = this.getResultsFile(file, parameters);
+
+ if (!overwrite(resultFile, parameters)) {
+ return resultFile;
+ }
+ resultFile.delete();
+
+ // subsample from stream
+ BufferedImage srcImage = ImageFilterAPI.apiInstance.get().subsampleImage(file, width, height);
+
+
+ File tempResultFile = new File(resultFile.getAbsoluteFile() + "_" + System.currentTimeMillis() +".tmp");
+
+ try{
+ ImageIO.write(srcImage, "png", tempResultFile);
+ tempResultFile.renameTo(resultFile);
+ } catch (Exception e) {
+ throw new DotRuntimeException("unable to convert file:" +file + " : " + e.getMessage(),e);
+ }
+
+ return resultFile;
+
+ }
+
+
+ @Override
+ public File getResultsFile(File file, Map parameters) {
+ try {
+ return super.getResultsFile(file, parameters, "png");
+ }
+ catch(Exception e) {
+ return new File(System.getProperty("java.io.tmpdir") + file.separator + System.currentTimeMillis() + "." + "png");
+ }
+
+ }
+
+
+}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/filter/ThumbnailImageFilter.java b/dotCMS/src/main/java/com/dotmarketing/image/filter/ThumbnailImageFilter.java
index a3b718f0063c..9b7eebce86a4 100644
--- a/dotCMS/src/main/java/com/dotmarketing/image/filter/ThumbnailImageFilter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/image/filter/ThumbnailImageFilter.java
@@ -1,14 +1,9 @@
package com.dotmarketing.image.filter;
-import com.dotmarketing.util.Config;
-import com.dotmarketing.util.ImageResizeUtils;
-import com.dotmarketing.util.Logger;
-import com.twelvemonkeys.image.ResampleOp;
import java.awt.Color;
+import java.awt.Dimension;
import java.awt.Graphics2D;
-import java.awt.Image;
import java.awt.image.BufferedImage;
-import java.awt.image.BufferedImageOp;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
@@ -16,113 +11,113 @@
import java.nio.file.Files;
import java.util.Map;
import javax.imageio.ImageIO;
+import com.dotmarketing.exception.DotRuntimeException;
+import com.dotmarketing.util.Config;
+
+import com.dotmarketing.util.Logger;
public class ThumbnailImageFilter extends ImageFilter {
- public String[] getAcceptedParameters() {
- return new String[] { "w (int) specifies width", "h (int) specifies height",
- "bg (int) must be 9 digits of rgb (000000000=black, 255255255=white) for background color"
-
- };
- }
- public static final int DEFAULT_HEIGHT = Config.getIntProperty("DEFAULT_HEIGHT",100);
- public static final int DEFAULT_WIDTH = Config.getIntProperty("DEFAULT_WIDTH",100);
- public static final Color DEFAULT_BG_COLOR = new Color(Config.getIntProperty("DEFAULT_BG_R_COLOR"), Config.getIntProperty("DEFAULT_BG_G_COLOR"), Config.getIntProperty("DEFAULT_BG_B_COLOR"));
-
- public File runFilter(File file, Map parameters) {
-
- int height = parameters.get(getPrefix() + "h") != null ? Integer.parseInt(parameters.get(getPrefix() + "h")[0])
- : 0;
- int width = parameters.get(getPrefix() + "w") != null ? Integer.parseInt(parameters.get(getPrefix() + "w")[0])
- : 0;
- String rgb = parameters.get(getPrefix() + "bg") != null ? parameters.get(getPrefix() + "bg")[0] : "255255255";
- Color color = new Color(Integer.parseInt(rgb.substring(0, 3)), Integer.parseInt(rgb.substring(3, 6)),
- Integer.parseInt(rgb.substring(6)));
-
- File resultFile = getResultsFile(file, parameters);
-
- if (!overwrite(resultFile, parameters)) {
- return resultFile;
- }
-
- try {
- resultFile.delete();
- if (height <= 0 && width <= 0) {
- height = DEFAULT_HEIGHT;
- width = DEFAULT_WIDTH;
- }
-
- if (color == null){
- color = DEFAULT_BG_COLOR;
- }
-
-
- Image image = ImageIO.read(file);
-
-
-
- // determine thumbnail size from WIDTH and HEIGHT
- int imageWidth = image.getWidth(null);
- int imageHeight = image.getHeight(null);
- double imageRatio = (double) imageWidth / (double) imageHeight;
-
- int thumbWidth = width;
- int thumbHeight = height;
- if (thumbWidth <= 0)
- thumbWidth = (int) (thumbHeight * imageRatio);
- if (thumbHeight <= 0)
- thumbHeight = (int) (thumbWidth / imageRatio);
- double thumbRatio = (double) thumbWidth / (double) thumbHeight;
-
- if (thumbRatio < imageRatio) {
- thumbHeight = (int) Math.ceil((thumbWidth / imageRatio));
- } else {
- thumbWidth = (int) Math.ceil((thumbHeight * imageRatio));
- }
-
- if (thumbWidth == 0)
- thumbWidth = 1;
- if (thumbHeight == 0)
- thumbHeight = 1;
-
- if (width <= 0)
- width = (int) Math.ceil(height * imageRatio);
- if (height <= 0)
- height = (int) Math.ceil(width / imageRatio);
-
- // draw original image to thumbnail image object and
- // scale it to the new size on-the-fly
- BufferedImage bgImage = new BufferedImage(width, height, java.awt.image.BufferedImage.TYPE_INT_RGB);
- Graphics2D resultGraphics = bgImage.createGraphics();
- resultGraphics.setColor(color);
- resultGraphics.fillRect(0, 0, width, height);
-
-
-
- BufferedImageOp resampler = new ResampleOp(thumbWidth, thumbHeight, ResampleOp.FILTER_LANCZOS); // A good default filter, see class documentation for more info
- BufferedImage thumbImage = resampler.filter(ImageIO.read(file), null);
-
-
- // compute offsets to center image in its space
- int offsetX = (width - thumbImage.getWidth()) / 2;
- int offsetY = (height - thumbImage.getHeight()) / 2;
-
- resultGraphics.drawImage(thumbImage, null, offsetX, offsetY);
- resultGraphics.dispose();
-
- // save thumbnail image to OUTFILE
- final BufferedOutputStream out = new BufferedOutputStream(Files.newOutputStream(resultFile.toPath()));
- ImageIO.write(bgImage, "png", out);
- out.close();
-
- Logger.debug(ImageResizeUtils.class, "Done.");
- } catch (FileNotFoundException e) {
- Logger.error(this.getClass(), e.getMessage());
- } catch (IOException e) {
- Logger.error(this.getClass(), e.getMessage());
- }
-
- return resultFile;
-
- }
+ public String[] getAcceptedParameters() {
+ return new String[] {"w (int) specifies width", "h (int) specifies height",
+ "bg (int) must be 9 digits of rgb (000000000=black, 255255255=white) for background color"
+
+ };
+ }
+
+ public static final int DEFAULT_HEIGHT = Config.getIntProperty("DEFAULT_HEIGHT", 100);
+ public static final int DEFAULT_WIDTH = Config.getIntProperty("DEFAULT_WIDTH", 100);
+ public static final Color DEFAULT_BG_COLOR = new Color(Config.getIntProperty("DEFAULT_BG_R_COLOR"),
+ Config.getIntProperty("DEFAULT_BG_G_COLOR"), Config.getIntProperty("DEFAULT_BG_B_COLOR"));
+
+ public File runFilter(File file, Map parameters) {
+
+ int height = parameters.get(getPrefix() + "h") != null ? Integer.parseInt(parameters.get(getPrefix() + "h")[0])
+ : 0;
+ int width = parameters.get(getPrefix() + "w") != null ? Integer.parseInt(parameters.get(getPrefix() + "w")[0])
+ : 0;
+ String rgb = parameters.get(getPrefix() + "bg") != null ? parameters.get(getPrefix() + "bg")[0] : "255255255";
+ Color color = new Color(Integer.parseInt(rgb.substring(0, 3)), Integer.parseInt(rgb.substring(3, 6)),
+ Integer.parseInt(rgb.substring(6)));
+
+ File resultFile = getResultsFile(file, parameters);
+
+ if (!overwrite(resultFile, parameters)) {
+ return resultFile;
+ }
+
+ resultFile.delete();
+ try {
+
+ if (height <= 0 && width <= 0) {
+ height = DEFAULT_HEIGHT;
+ width = DEFAULT_WIDTH;
+ }
+
+ if (color == null) {
+ color = DEFAULT_BG_COLOR;
+ }
+
+ Dimension widthHeight = ImageFilterAPI.apiInstance.get().getWidthHeight(file);
+
+ // determine thumbnail size from WIDTH and HEIGHT
+ int imageWidth = widthHeight.width;
+ int imageHeight = widthHeight.height;
+ double imageRatio = (double) imageWidth / (double) imageHeight;
+
+ int thumbWidth = width;
+ int thumbHeight = height;
+ if (thumbWidth <= 0)
+ thumbWidth = (int) (thumbHeight * imageRatio);
+ if (thumbHeight <= 0)
+ thumbHeight = (int) (thumbWidth / imageRatio);
+ double thumbRatio = (double) thumbWidth / (double) thumbHeight;
+
+ if (thumbRatio < imageRatio) {
+ thumbHeight = (int) Math.ceil((thumbWidth / imageRatio));
+ } else {
+ thumbWidth = (int) Math.ceil((thumbHeight * imageRatio));
+ }
+
+ if (thumbWidth == 0)
+ thumbWidth = 1;
+ if (thumbHeight == 0)
+ thumbHeight = 1;
+
+ if (width <= 0)
+ width = (int) Math.ceil(height * imageRatio);
+ if (height <= 0)
+ height = (int) Math.ceil(width / imageRatio);
+
+ // draw original image to thumbnail image object and
+ // scale it to the new size on-the-fly
+ BufferedImage bgImage = new BufferedImage(width, height, java.awt.image.BufferedImage.TYPE_INT_RGB);
+ Graphics2D resultGraphics = bgImage.createGraphics();
+ resultGraphics.setColor(color);
+ resultGraphics.fillRect(0, 0, width, height);
+
+ BufferedImage thumbImage =
+ ImageFilterAPI.apiInstance.get().intelligentResize(file, thumbWidth, thumbHeight);
+
+ // compute offsets to center image in its space
+ int offsetX = (width - thumbImage.getWidth()) / 2;
+ int offsetY = (height - thumbImage.getHeight()) / 2;
+
+ resultGraphics.drawImage(thumbImage, null, offsetX, offsetY);
+ resultGraphics.dispose();
+
+ final File tempResultFile =
+ new File(resultFile.getAbsoluteFile() + "_" + System.currentTimeMillis() + ".tmp");
+
+ ImageIO.write(bgImage, "png", tempResultFile);
+ bgImage.flush();
+
+ tempResultFile.renameTo(resultFile);
+ } catch (Exception e) {
+ throw new DotRuntimeException("unable to convert file:" + file + " : " + e.getMessage(), e);
+ }
+
+ return resultFile;
+
+ }
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/gif/AnimatedGifEncoder.java b/dotCMS/src/main/java/com/dotmarketing/image/gif/AnimatedGifEncoder.java
new file mode 100755
index 000000000000..0840024f68c4
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotmarketing/image/gif/AnimatedGifEncoder.java
@@ -0,0 +1,544 @@
+package com.dotmarketing.image.gif;
+
+import java.io.*;
+import java.awt.*;
+import java.awt.image.*;
+
+/**
+ * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or
+ * more frames.
+ *
+ * Example:
+ * AnimatedGifEncoder e = new AnimatedGifEncoder();
+ * e.start(outputFileName);
+ * e.setDelay(1000); // 1 frame per sec
+ * e.addFrame(image1);
+ * e.addFrame(image2);
+ * e.finish();
+ *
+ * No copyright asserted on the source code of this class. May be used
+ * for any purpose, however, refer to the Unisys LZW patent for restrictions
+ * on use of the associated LZWEncoder class. Please forward any corrections
+ * to questions at fmsware.com.
+ *
+ * @author Kevin Weiner, FM Software
+ * @version 1.03 November 2003
+ *
+ */
+
+public class AnimatedGifEncoder {
+
+ protected int width; // image size
+ protected int height;
+ protected Color transparent = null; // transparent color if given
+ protected boolean transparentExactMatch = false; // transparent color will be found by looking for the closest color
+ // or for the exact color if transparentExactMatch == true
+ protected Color background = null; // background color if given
+ protected int transIndex; // transparent index in color table
+ protected int repeat = -1; // no repeat
+ protected int delay = 0; // frame delay (hundredths)
+ protected boolean started = false; // ready to output frames
+ protected OutputStream out;
+ protected BufferedImage image; // current frame
+ protected byte[] pixels; // BGR byte array from frame
+ protected byte[] indexedPixels; // converted frame indexed to palette
+ protected int colorDepth; // number of bit planes
+ protected byte[] colorTab; // RGB palette
+ protected boolean[] usedEntry = new boolean[256]; // active palette entries
+ protected int palSize = 7; // color table size (bits-1)
+ protected int dispose = -1; // disposal code (-1 = use default)
+ protected boolean closeStream = false; // close stream when finished
+ protected boolean firstFrame = true;
+ protected boolean sizeSet = false; // if false, get size from first frame
+ protected int sample = 10; // default sample interval for quantizer
+
+ /**
+ * Sets the delay time between each frame, or changes it
+ * for subsequent frames (applies to last frame added).
+ *
+ * @param ms int delay time in milliseconds
+ */
+ public void setDelay(int ms) {
+ delay = Math.round(ms / 10.0f);
+ }
+
+ /**
+ * Sets the GIF frame disposal code for the last added frame
+ * and any subsequent frames. Default is 0 if no transparent
+ * color has been set, otherwise 2.
+ * @param code int disposal code.
+ */
+ public void setDispose(int code) {
+ if (code >= 0) {
+ dispose = code;
+ }
+ }
+
+ /**
+ * Sets the number of times the set of GIF frames
+ * should be played. Default is 1; 0 means play
+ * indefinitely. Must be invoked before the first
+ * image is added.
+ *
+ * @param iter int number of iterations.
+ */
+ public void setRepeat(int iter) {
+ if (iter >= 0) {
+ repeat = iter;
+ }
+ }
+
+ /**
+ * Sets the transparent color for the last added frame
+ * and any subsequent frames.
+ * Since all colors are subject to modification
+ * in the quantization process, the color in the final
+ * palette for each frame closest to the given color
+ * becomes the transparent color for that frame.
+ * May be set to null to indicate no transparent color.
+ *
+ * @param c Color to be treated as transparent on display.
+ */
+ public void setTransparent(Color c) {
+ setTransparent (c, false);
+ }
+
+ /**
+ * Sets the transparent color for the last added frame
+ * and any subsequent frames.
+ * Since all colors are subject to modification
+ * in the quantization process, the color in the final
+ * palette for each frame closest to the given color
+ * becomes the transparent color for that frame.
+ * If exactMatch is set to true, transparent color index
+ * is search with exact match, and not looking for the
+ * closest one.
+ * May be set to null to indicate no transparent color.
+ *
+ * @param c Color to be treated as transparent on display.
+ */
+ public void setTransparent(Color c, boolean exactMatch) {
+ transparent = c;
+ transparentExactMatch = exactMatch;
+ }
+
+
+ /**
+ * Sets the background color for the last added frame
+ * and any subsequent frames.
+ * Since all colors are subject to modification
+ * in the quantization process, the color in the final
+ * palette for each frame closest to the given color
+ * becomes the background color for that frame.
+ * May be set to null to indicate no background color
+ * which will default to black.
+ *
+ * @param c Color to be treated as background on display.
+ */
+ public void setBackground(Color c) {
+ background = c;
+ }
+
+ /**
+ * Adds next GIF frame. The frame is not written immediately, but is
+ * actually deferred until the next frame is received so that timing
+ * data can be inserted. Invoking finish() flushes all
+ * frames. If setSize was not invoked, the size of the
+ * first image is used for all subsequent frames.
+ *
+ * @param im BufferedImage containing frame to write.
+ * @return true if successful.
+ */
+ public boolean addFrame(BufferedImage im) {
+ if ((im == null) || !started) {
+ return false;
+ }
+ boolean ok = true;
+ try {
+ if (!sizeSet) {
+ // use first frame's size
+ setSize(im.getWidth(), im.getHeight());
+ }
+ image = im;
+ getImagePixels(); // convert to correct format if necessary
+ analyzePixels(); // build color table & map pixels
+ if (firstFrame) {
+ writeLSD(); // logical screen descriptior
+ writePalette(); // global color table
+ if (repeat >= 0) {
+ // use NS app extension to indicate reps
+ writeNetscapeExt();
+ }
+ }
+ writeGraphicCtrlExt(); // write graphic control extension
+ writeImageDesc(); // image descriptor
+ if (!firstFrame) {
+ writePalette(); // local color table
+ }
+ writePixels(); // encode and write pixel data
+ firstFrame = false;
+ } catch (IOException e) {
+ ok = false;
+ }
+
+ return ok;
+ }
+
+ /**
+ * Flushes any pending data and closes output file.
+ * If writing to an OutputStream, the stream is not
+ * closed.
+ */
+ public boolean finish() {
+ if (!started) return false;
+ boolean ok = true;
+ started = false;
+ try {
+ out.write(0x3b); // gif trailer
+ out.flush();
+ if (closeStream) {
+ out.close();
+ }
+ } catch (IOException e) {
+ ok = false;
+ }
+
+ // reset for subsequent use
+ transIndex = 0;
+ out = null;
+ image = null;
+ pixels = null;
+ indexedPixels = null;
+ colorTab = null;
+ closeStream = false;
+ firstFrame = true;
+
+ return ok;
+ }
+
+ /**
+ * Sets frame rate in frames per second. Equivalent to
+ * setDelay(1000/fps).
+ *
+ * @param fps float frame rate (frames per second)
+ */
+ public void setFrameRate(float fps) {
+ if (fps != 0f) {
+ delay = Math.round(100f / fps);
+ }
+ }
+
+ /**
+ * Sets quality of color quantization (conversion of images
+ * to the maximum 256 colors allowed by the GIF specification).
+ * Lower values (minimum = 1) produce better colors, but slow
+ * processing significantly. 10 is the default, and produces
+ * good color mapping at reasonable speeds. Values greater
+ * than 20 do not yield significant improvements in speed.
+ *
+ * @param quality int greater than 0.
+ */
+ public void setQuality(int quality) {
+ if (quality < 1) quality = 1;
+ sample = quality;
+ }
+
+ /**
+ * Sets the GIF frame size. The default size is the
+ * size of the first frame added if this method is
+ * not invoked.
+ *
+ * @param w int frame width.
+ * @param h int frame width.
+ */
+ public void setSize(int w, int h) {
+ if (started && !firstFrame) return;
+ width = w;
+ height = h;
+ if (width < 1) width = 320;
+ if (height < 1) height = 240;
+ sizeSet = true;
+ }
+
+ /**
+ * Initiates GIF file creation on the given stream. The stream
+ * is not closed automatically.
+ *
+ * @param os OutputStream on which GIF images are written.
+ * @return false if initial write failed.
+ */
+ public boolean start(OutputStream os) {
+ if (os == null) return false;
+ boolean ok = true;
+ closeStream = false;
+ out = os;
+ try {
+ writeString("GIF89a"); // header
+ } catch (IOException e) {
+ ok = false;
+ }
+ return started = ok;
+ }
+
+
+ /**
+ * Initiates writing of a GIF file with the specified name.
+ *
+ * @param file String containing output file name.
+ * @return false if open or initial write failed.
+ */
+ public boolean start(String file) {
+ boolean ok = true;
+ try {
+ out = new BufferedOutputStream(new FileOutputStream(file));
+ ok = start(out);
+ closeStream = true;
+ } catch (IOException e) {
+ ok = false;
+ }
+ return started = ok;
+ }
+
+ public boolean isStarted() {
+ return started;
+ }
+
+ /**
+ * Analyzes image colors and creates color map.
+ */
+ protected void analyzePixels() {
+ int len = pixels.length;
+ int nPix = len / 3;
+ indexedPixels = new byte[nPix];
+ NeuQuant nq = new NeuQuant(pixels, len, sample);
+ // initialize quantizer
+ colorTab = nq.process(); // create reduced palette
+ // convert map from BGR to RGB
+ for (int i = 0; i < colorTab.length; i += 3) {
+ byte temp = colorTab[i];
+ colorTab[i] = colorTab[i + 2];
+ colorTab[i + 2] = temp;
+ usedEntry[i / 3] = false;
+ }
+ // map image pixels to new palette
+ int k = 0;
+ for (int i = 0; i < nPix; i++) {
+ int index =
+ nq.map(pixels[k++] & 0xff,
+ pixels[k++] & 0xff,
+ pixels[k++] & 0xff);
+ usedEntry[index] = true;
+ indexedPixels[i] = (byte) index;
+ }
+ pixels = null;
+ colorDepth = 8;
+ palSize = 7;
+ // get closest match to transparent color if specified
+ if (transparent != null) {
+ transIndex = transparentExactMatch ? findExact(transparent) : findClosest(transparent);
+ }
+ }
+
+ /**
+ * Returns index of palette color closest to c
+ *
+ */
+ protected int findClosest(Color c) {
+ if (colorTab == null) return -1;
+ int r = c.getRed();
+ int g = c.getGreen();
+ int b = c.getBlue();
+ int minpos = 0;
+ int dmin = 256 * 256 * 256;
+ int len = colorTab.length;
+ for (int i = 0; i < len;) {
+ int dr = r - (colorTab[i++] & 0xff);
+ int dg = g - (colorTab[i++] & 0xff);
+ int db = b - (colorTab[i] & 0xff);
+ int d = dr * dr + dg * dg + db * db;
+ int index = i / 3;
+ if (usedEntry[index] && (d < dmin)) {
+ dmin = d;
+ minpos = index;
+ }
+ i++;
+ }
+ return minpos;
+ }
+
+ /*
+ * Returns true if the exact matching color is existing, and used in the color palette, otherwise, return false. This method has to be called before
+ * finishing the image, because after finished the palette is destroyed and it will always return false.
+ */
+ boolean isColorUsed(Color c) {
+ return findExact(c) != -1;
+ }
+
+ /**
+ * Returns index of palette exactly matching to color c or -1 if there is no exact matching.
+ *
+ */
+ protected int findExact(Color c) {
+ if (colorTab == null) {
+ return -1;
+ }
+
+ int r = c.getRed();
+ int g = c.getGreen();
+ int b = c.getBlue();
+ int len = colorTab.length / 3;
+ for (int index = 0; index < len; ++index) {
+ int i = index * 3;
+ // If the entry is used in colorTab, then check if it is the same exact color we're looking for
+ if (usedEntry[index] && r == (colorTab[i] & 0xff) && g == (colorTab[i+1] & 0xff) && b == (colorTab[i+2] & 0xff)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Extracts image pixels into byte array "pixels"
+ */
+ protected void getImagePixels() {
+ int w = image.getWidth();
+ int h = image.getHeight();
+ int type = image.getType();
+ if ((w != width)
+ || (h != height)
+ || (type != BufferedImage.TYPE_3BYTE_BGR)) {
+ // create new image with right size/format
+ BufferedImage temp =
+ new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
+ Graphics2D g = temp.createGraphics();
+ g.setColor(background);
+ g.fillRect(0, 0, width, height);
+ g.drawImage(image, 0, 0, null);
+ image = temp;
+ }
+ pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
+ }
+
+ /**
+ * Writes Graphic Control Extension
+ */
+ protected void writeGraphicCtrlExt() throws IOException {
+ out.write(0x21); // extension introducer
+ out.write(0xf9); // GCE label
+ out.write(4); // data block size
+ int transp, disp;
+ if (transparent == null) {
+ transp = 0;
+ disp = 0; // dispose = no action
+ } else {
+ transp = 1;
+ disp = 2; // force clear if using transparent color
+ }
+ if (dispose >= 0) {
+ disp = dispose & 7; // user override
+ }
+ disp <<= 2;
+
+ // packed fields
+ out.write(0 | // 1:3 reserved
+ disp | // 4:6 disposal
+ 0 | // 7 user input - 0 = none
+ transp); // 8 transparency flag
+
+ writeShort(delay); // delay x 1/100 sec
+ out.write(transIndex); // transparent color index
+ out.write(0); // block terminator
+ }
+
+ /**
+ * Writes Image Descriptor
+ */
+ protected void writeImageDesc() throws IOException {
+ out.write(0x2c); // image separator
+ writeShort(0); // image position x,y = 0,0
+ writeShort(0);
+ writeShort(width); // image size
+ writeShort(height);
+ // packed fields
+ if (firstFrame) {
+ // no LCT - GCT is used for first (or only) frame
+ out.write(0);
+ } else {
+ // specify normal LCT
+ out.write(0x80 | // 1 local color table 1=yes
+ 0 | // 2 interlace - 0=no
+ 0 | // 3 sorted - 0=no
+ 0 | // 4-5 reserved
+ palSize); // 6-8 size of color table
+ }
+ }
+
+ /**
+ * Writes Logical Screen Descriptor
+ */
+ protected void writeLSD() throws IOException {
+ // logical screen size
+ writeShort(width);
+ writeShort(height);
+ // packed fields
+ out.write((0x80 | // 1 : global color table flag = 1 (gct used)
+ 0x70 | // 2-4 : color resolution = 7
+ 0x00 | // 5 : gct sort flag = 0
+ palSize)); // 6-8 : gct size
+
+ out.write(0); // background color index
+ out.write(0); // pixel aspect ratio - assume 1:1
+ }
+
+ /**
+ * Writes Netscape application extension to define
+ * repeat count.
+ */
+ protected void writeNetscapeExt() throws IOException {
+ out.write(0x21); // extension introducer
+ out.write(0xff); // app extension label
+ out.write(11); // block size
+ writeString("NETSCAPE" + "2.0"); // app id + auth code
+ out.write(3); // sub-block size
+ out.write(1); // loop sub-block id
+ writeShort(repeat); // loop count (extra iterations, 0=repeat forever)
+ out.write(0); // block terminator
+ }
+
+ /**
+ * Writes color table
+ */
+ protected void writePalette() throws IOException {
+ out.write(colorTab, 0, colorTab.length);
+ int n = (3 * 256) - colorTab.length;
+ for (int i = 0; i < n; i++) {
+ out.write(0);
+ }
+ }
+
+ /**
+ * Encodes and writes pixel data
+ */
+ protected void writePixels() throws IOException {
+ LZWEncoder encoder =
+ new LZWEncoder(width, height, indexedPixels, colorDepth);
+ encoder.encode(out);
+ }
+
+ /**
+ * Write 16-bit value to output stream, LSB first
+ */
+ protected void writeShort(int value) throws IOException {
+ out.write(value & 0xff);
+ out.write((value >> 8) & 0xff);
+ }
+
+ /**
+ * Writes string to output stream
+ */
+ protected void writeString(String s) throws IOException {
+ for (int i = 0; i < s.length(); i++) {
+ out.write((byte) s.charAt(i));
+ }
+ }
+}
\ No newline at end of file
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/gif/GifDecoder.java b/dotCMS/src/main/java/com/dotmarketing/image/gif/GifDecoder.java
new file mode 100755
index 000000000000..55512b78bef1
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotmarketing/image/gif/GifDecoder.java
@@ -0,0 +1,783 @@
+package com.dotmarketing.image.gif;
+
+import java.net.*;
+import java.io.*;
+import java.util.*;
+import java.awt.*;
+import java.awt.image.*;
+
+/**
+ * Class GifDecoder - Decodes a GIF file into one or more frames.
+ *
+ * Example:
+ *
+ *
+ * {@code
+ * GifDecoder d = new GifDecoder();
+ * d.read("sample.gif");
+ * int n = d.getFrameCount();
+ * for (int i = 0; i < n; i++) {
+ * BufferedImage frame = d.getFrame(i); // frame i
+ * int t = d.getDelay(i); // display duration of frame in milliseconds
+ * // do something with frame
+ * }
+ * }
+ *
+ * No copyright asserted on the source code of this class. May be used for
+ * any purpose, however, refer to the Unisys LZW patent for any additional
+ * restrictions. Please forward any corrections to questions at fmsware.com.
+ *
+ * @author Kevin Weiner, FM Software; LZW decoder adapted from John Cristy's ImageMagick.
+ * @version 1.03 November 2003
+ *
+ */
+
+public class GifDecoder {
+
+ /**
+ * File read status: No errors.
+ */
+ public static final int STATUS_OK = 0;
+
+ /**
+ * File read status: Error decoding file (may be partially decoded)
+ */
+ public static final int STATUS_FORMAT_ERROR = 1;
+
+ /**
+ * File read status: Unable to open source.
+ */
+ public static final int STATUS_OPEN_ERROR = 2;
+
+ protected BufferedInputStream in;
+ protected int status;
+
+ protected int width; // full image width
+ protected int height; // full image height
+ protected boolean gctFlag; // global color table used
+ protected int gctSize; // size of global color table
+ protected int loopCount = 1; // iterations; 0 = repeat forever
+
+ protected int[] gct; // global color table
+ protected int[] lct; // local color table
+ protected int[] act; // active color table
+
+ protected int bgIndex; // background color index
+ protected int bgColor; // background color
+ protected int lastBgColor; // previous bg color
+ protected int pixelAspect; // pixel aspect ratio
+
+ protected boolean lctFlag; // local color table flag
+ protected boolean interlace; // interlace flag
+ protected int lctSize; // local color table size
+
+ protected int ix, iy, iw, ih; // current image rectangle
+ protected Rectangle lastRect; // last image rect
+ protected BufferedImage image; // current frame
+ protected BufferedImage lastImage; // previous frame
+
+ protected byte[] block = new byte[256]; // current data block
+ protected int blockSize = 0; // block size
+
+ // last graphic control extension info
+ protected int dispose = 0;
+ // 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev
+ protected int lastDispose = 0;
+ protected boolean transparency = false; // use transparent color
+ protected int delay = 0; // delay in milliseconds
+ protected int transIndex; // transparent color index
+
+ protected static final int MaxStackSize = 4096;
+ // max decoder pixel stack size
+
+ // LZW decoder working arrays
+ protected short[] prefix;
+ protected byte[] suffix;
+ protected byte[] pixelStack;
+ protected byte[] pixels;
+
+ protected ArrayList frames; // frames read from current file
+ protected int frameCount;
+
+ static class GifFrame {
+ public GifFrame(BufferedImage im, int del) {
+ image = im;
+ delay = del;
+ }
+ public BufferedImage image;
+ public int delay;
+ }
+
+ /**
+ * Gets display duration for specified frame.
+ *
+ * @param n int index of frame
+ * @return delay in milliseconds
+ */
+ public int getDelay(int n) {
+ //
+ delay = -1;
+ if ((n >= 0) && (n < frameCount)) {
+ delay = ((GifFrame) frames.get(n)).delay;
+ }
+ return delay;
+ }
+
+ /**
+ * Gets the number of frames read from file.
+ * @return frame count
+ */
+ public int getFrameCount() {
+ return frameCount;
+ }
+
+ /**
+ * Gets the first (or only) image read.
+ *
+ * @return BufferedImage containing first frame, or null if none.
+ */
+ public BufferedImage getImage() {
+ return getFrame(0);
+ }
+
+ /**
+ * Gets the "Netscape" iteration count, if any.
+ * A count of 0 means repeat indefinitiely.
+ *
+ * @return iteration count if one was specified, else 1.
+ */
+ public int getLoopCount() {
+ return loopCount;
+ }
+
+ /**
+ * Creates new frame image from current data (and previous
+ * frames as specified by their disposition codes).
+ */
+ protected void setPixels() {
+ // expose destination image's pixels as int array
+ int[] dest =
+ ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
+
+ // fill in starting image contents based on last image's dispose code
+ if (lastDispose > 0) {
+ if (lastDispose == 3) {
+ // use image before last
+ int n = frameCount - 2;
+ if (n > 0) {
+ lastImage = getFrame(n - 1);
+ } else {
+ lastImage = null;
+ }
+ }
+
+ if (lastImage != null) {
+ int[] prev =
+ ((DataBufferInt) lastImage.getRaster().getDataBuffer()).getData();
+ System.arraycopy(prev, 0, dest, 0, width * height);
+ // copy pixels
+
+ if (lastDispose == 2) {
+ // fill last image rect area with background color
+ Graphics2D g = image.createGraphics();
+ Color c = null;
+ if (transparency) {
+ c = new Color(0, 0, 0, 0); // assume background is transparent
+ } else {
+ c = new Color(lastBgColor); // use given background color
+ }
+ g.setColor(c);
+ g.setComposite(AlphaComposite.Src); // replace area
+ g.fill(lastRect);
+ g.dispose();
+ }
+ }
+ }
+
+ // copy each source line to the appropriate place in the destination
+ int pass = 1;
+ int inc = 8;
+ int iline = 0;
+ for (int i = 0; i < ih; i++) {
+ int line = i;
+ if (interlace) {
+ if (iline >= ih) {
+ pass++;
+ switch (pass) {
+ case 2 :
+ iline = 4;
+ break;
+ case 3 :
+ iline = 2;
+ inc = 4;
+ break;
+ case 4 :
+ iline = 1;
+ inc = 2;
+ }
+ }
+ line = iline;
+ iline += inc;
+ }
+ line += iy;
+ if (line < height) {
+ int k = line * width;
+ int dx = k + ix; // start of line in dest
+ int dlim = dx + iw; // end of dest line
+ if ((k + width) < dlim) {
+ dlim = k + width; // past dest edge
+ }
+ int sx = i * iw; // start of line in source
+ while (dx < dlim) {
+ // map color and insert in destination
+ int index = ((int) pixels[sx++]) & 0xff;
+ int c = act[index];
+ if (c != 0) {
+ dest[dx] = c;
+ }
+ dx++;
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the image contents of frame n.
+ *
+ * @return BufferedImage representation of frame, or null if n is invalid.
+ */
+ public BufferedImage getFrame(int n) {
+ BufferedImage im = null;
+ if ((n >= 0) && (n < frameCount)) {
+ im = ((GifFrame) frames.get(n)).image;
+ }
+ return im;
+ }
+
+ /**
+ * Gets image size.
+ *
+ * @return GIF image dimensions
+ */
+ public Dimension getFrameSize() {
+ return new Dimension(width, height);
+ }
+
+ /**
+ * Reads GIF image from stream
+ *
+ * @param is BufferedInputStream containing GIF file.
+ * @return read status code (0 = no errors)
+ */
+ public int read(BufferedInputStream is) {
+ init();
+ if (is != null) {
+ in = is;
+ readHeader();
+ if (!err()) {
+ readContents();
+ if (frameCount < 0) {
+ status = STATUS_FORMAT_ERROR;
+ }
+ }
+ } else {
+ status = STATUS_OPEN_ERROR;
+ }
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ return status;
+ }
+
+ /**
+ * Reads GIF image from stream
+ *
+ * @param is InputStream containing GIF file.
+ * @return read status code (0 = no errors)
+ */
+ public int read(InputStream is) {
+ init();
+ if (is != null) {
+ if (!(is instanceof BufferedInputStream))
+ is = new BufferedInputStream(is);
+ in = (BufferedInputStream) is;
+ readHeader();
+ if (!err()) {
+ readContents();
+ if (frameCount < 0) {
+ status = STATUS_FORMAT_ERROR;
+ }
+ }
+ } else {
+ status = STATUS_OPEN_ERROR;
+ }
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ return status;
+ }
+
+ /**
+ * Reads GIF file from specified file/URL source
+ * (URL assumed if name contains ":/" or "file:")
+ *
+ * @param name String containing source
+ * @return read status code (0 = no errors)
+ */
+ public int read(String name) {
+ status = STATUS_OK;
+ try {
+ name = name.trim();
+ if ((name.indexOf("file:") >= 0) ||
+ (name.indexOf(":/") > 0)) {
+ URL url = new URL(name);
+ in = new BufferedInputStream(url.openStream());
+ } else {
+ in = new BufferedInputStream(new FileInputStream(name));
+ }
+ status = read(in);
+ } catch (IOException e) {
+ status = STATUS_OPEN_ERROR;
+ }
+
+ return status;
+ }
+
+ /**
+ * Decodes LZW image data into pixel array.
+ * Adapted from John Cristy's ImageMagick.
+ */
+ protected void decodeImageData() {
+ int NullCode = -1;
+ int npix = iw * ih;
+ int available,
+ clear,
+ code_mask,
+ code_size,
+ end_of_information,
+ in_code,
+ old_code,
+ bits,
+ code,
+ count,
+ i,
+ datum,
+ data_size,
+ first,
+ top,
+ bi,
+ pi;
+
+ if ((pixels == null) || (pixels.length < npix)) {
+ pixels = new byte[npix]; // allocate new pixel array
+ }
+ if (prefix == null) prefix = new short[MaxStackSize];
+ if (suffix == null) suffix = new byte[MaxStackSize];
+ if (pixelStack == null) pixelStack = new byte[MaxStackSize + 1];
+
+ // Initialize GIF data stream decoder.
+
+ data_size = read();
+ clear = 1 << data_size;
+ end_of_information = clear + 1;
+ available = clear + 2;
+ old_code = NullCode;
+ code_size = data_size + 1;
+ code_mask = (1 << code_size) - 1;
+ for (code = 0; code < clear; code++) {
+ prefix[code] = 0;
+ suffix[code] = (byte) code;
+ }
+
+ // Decode GIF pixel stream.
+
+ datum = bits = count = first = top = pi = bi = 0;
+
+ for (i = 0; i < npix;) {
+ if (top == 0) {
+ if (bits < code_size) {
+ // Load bytes until there are enough bits for a code.
+ if (count == 0) {
+ // Read a new data block.
+ count = readBlock();
+ if (count <= 0)
+ break;
+ bi = 0;
+ }
+ datum += (((int) block[bi]) & 0xff) << bits;
+ bits += 8;
+ bi++;
+ count--;
+ continue;
+ }
+
+ // Get the next code.
+
+ code = datum & code_mask;
+ datum >>= code_size;
+ bits -= code_size;
+
+ // Interpret the code
+
+ if ((code > available) || (code == end_of_information))
+ break;
+ if (code == clear) {
+ // Reset decoder.
+ code_size = data_size + 1;
+ code_mask = (1 << code_size) - 1;
+ available = clear + 2;
+ old_code = NullCode;
+ continue;
+ }
+ if (old_code == NullCode) {
+ pixelStack[top++] = suffix[code];
+ old_code = code;
+ first = code;
+ continue;
+ }
+ in_code = code;
+ if (code == available) {
+ pixelStack[top++] = (byte) first;
+ code = old_code;
+ }
+ while (code > clear) {
+ pixelStack[top++] = suffix[code];
+ code = prefix[code];
+ }
+ first = ((int) suffix[code]) & 0xff;
+
+ // Add a new string to the string table,
+
+ if (available >= MaxStackSize) {
+ pixelStack[top++] = (byte) first;
+ continue;
+ }
+ pixelStack[top++] = (byte) first;
+ prefix[available] = (short) old_code;
+ suffix[available] = (byte) first;
+ available++;
+ if (((available & code_mask) == 0)
+ && (available < MaxStackSize)) {
+ code_size++;
+ code_mask += available;
+ }
+ old_code = in_code;
+ }
+
+ // Pop a pixel off the pixel stack.
+
+ top--;
+ pixels[pi++] = pixelStack[top];
+ i++;
+ }
+
+ for (i = pi; i < npix; i++) {
+ pixels[i] = 0; // clear missing pixels
+ }
+
+ }
+
+ /**
+ * Returns true if an error was encountered during reading/decoding
+ */
+ protected boolean err() {
+ return status != STATUS_OK;
+ }
+
+ /**
+ * Initializes or re-initializes reader
+ */
+ protected void init() {
+ status = STATUS_OK;
+ frameCount = 0;
+ frames = new ArrayList();
+ gct = null;
+ lct = null;
+ }
+
+ /**
+ * Reads a single byte from the input stream.
+ */
+ protected int read() {
+ int curByte = 0;
+ try {
+ curByte = in.read();
+ } catch (IOException e) {
+ status = STATUS_FORMAT_ERROR;
+ }
+ return curByte;
+ }
+
+ /**
+ * Reads next variable length block from input.
+ *
+ * @return number of bytes stored in "buffer"
+ */
+ protected int readBlock() {
+ blockSize = read();
+ int n = 0;
+ if (blockSize > 0) {
+ try {
+ int count = 0;
+ while (n < blockSize) {
+ count = in.read(block, n, blockSize - n);
+ if (count == -1)
+ break;
+ n += count;
+ }
+ } catch (IOException e) {
+ }
+
+ if (n < blockSize) {
+ status = STATUS_FORMAT_ERROR;
+ }
+ }
+ return n;
+ }
+
+ /**
+ * Reads color table as 256 RGB integer values
+ *
+ * @param ncolors int number of colors to read
+ * @return int array containing 256 colors (packed ARGB with full alpha)
+ */
+ protected int[] readColorTable(int ncolors) {
+ int nbytes = 3 * ncolors;
+ int[] tab = null;
+ byte[] c = new byte[nbytes];
+ int n = 0;
+ try {
+ n = in.read(c);
+ } catch (IOException e) {
+ }
+ if (n < nbytes) {
+ status = STATUS_FORMAT_ERROR;
+ } else {
+ tab = new int[256]; // max size to avoid bounds checks
+ int i = 0;
+ int j = 0;
+ while (i < ncolors) {
+ int r = ((int) c[j++]) & 0xff;
+ int g = ((int) c[j++]) & 0xff;
+ int b = ((int) c[j++]) & 0xff;
+ tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
+ }
+ }
+ return tab;
+ }
+
+ /**
+ * Main file parser. Reads GIF content blocks.
+ */
+ protected void readContents() {
+ // read GIF file content blocks
+ boolean done = false;
+ while (!(done || err())) {
+ int code = read();
+ switch (code) {
+
+ case 0x2C : // image separator
+ readImage();
+ break;
+
+ case 0x21 : // extension
+ code = read();
+ switch (code) {
+ case 0xf9 : // graphics control extension
+ readGraphicControlExt();
+ break;
+
+ case 0xff : // application extension
+ readBlock();
+ String app = "";
+ for (int i = 0; i < 11; i++) {
+ app += (char) block[i];
+ }
+ if (app.equals("NETSCAPE2.0")) {
+ readNetscapeExt();
+ }
+ else
+ skip(); // don't care
+ break;
+
+ default : // uninteresting extension
+ skip();
+ }
+ break;
+
+ case 0x3b : // terminator
+ done = true;
+ break;
+
+ case 0x00 : // bad byte, but keep going and see what happens
+ break;
+
+ default :
+ status = STATUS_FORMAT_ERROR;
+ }
+ }
+ }
+
+ /**
+ * Reads Graphics Control Extension values
+ */
+ protected void readGraphicControlExt() {
+ read(); // block size
+ int packed = read(); // packed fields
+ dispose = (packed & 0x1c) >> 2; // disposal method
+ if (dispose == 0) {
+ dispose = 1; // elect to keep old image if discretionary
+ }
+ transparency = (packed & 1) != 0;
+ delay = readShort() * 10; // delay in milliseconds
+ transIndex = read(); // transparent color index
+ read(); // block terminator
+ }
+
+ /**
+ * Reads GIF file header information.
+ */
+ protected void readHeader() {
+ String id = "";
+ for (int i = 0; i < 6; i++) {
+ id += (char) read();
+ }
+ if (!id.startsWith("GIF")) {
+ status = STATUS_FORMAT_ERROR;
+ return;
+ }
+
+ readLSD();
+ if (gctFlag && !err()) {
+ gct = readColorTable(gctSize);
+ bgColor = gct[bgIndex];
+ }
+ }
+
+ /**
+ * Reads next frame image
+ */
+ protected void readImage() {
+ ix = readShort(); // (sub)image position & size
+ iy = readShort();
+ iw = readShort();
+ ih = readShort();
+
+ int packed = read();
+ lctFlag = (packed & 0x80) != 0; // 1 - local color table flag
+ interlace = (packed & 0x40) != 0; // 2 - interlace flag
+ // 3 - sort flag
+ // 4-5 - reserved
+ lctSize = 2 << (packed & 7); // 6-8 - local color table size
+
+ if (lctFlag) {
+ lct = readColorTable(lctSize); // read table
+ act = lct; // make local table active
+ } else {
+ act = gct; // make global table active
+ if (bgIndex == transIndex)
+ bgColor = 0;
+ }
+ int save = 0;
+ if (transparency) {
+ save = act[transIndex];
+ act[transIndex] = 0; // set transparent color if specified
+ }
+
+ if (act == null) {
+ status = STATUS_FORMAT_ERROR; // no color table defined
+ }
+
+ if (err()) return;
+
+ decodeImageData(); // decode pixel data
+ skip();
+
+ if (err()) return;
+
+ frameCount++;
+
+ // create new image to receive frame data
+ image =
+ new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
+
+ setPixels(); // transfer pixel data to image
+
+ frames.add(new GifFrame(image, delay)); // add image to frame list
+
+ if (transparency) {
+ act[transIndex] = save;
+ }
+ resetFrame();
+
+ }
+
+ /**
+ * Reads Logical Screen Descriptor
+ */
+ protected void readLSD() {
+
+ // logical screen size
+ width = readShort();
+ height = readShort();
+
+ // packed fields
+ int packed = read();
+ gctFlag = (packed & 0x80) != 0; // 1 : global color table flag
+ // 2-4 : color resolution
+ // 5 : gct sort flag
+ gctSize = 2 << (packed & 7); // 6-8 : gct size
+
+ bgIndex = read(); // background color index
+ pixelAspect = read(); // pixel aspect ratio
+ }
+
+ /**
+ * Reads Netscape extenstion to obtain iteration count
+ */
+ protected void readNetscapeExt() {
+ do {
+ readBlock();
+ if (block[0] == 1) {
+ // loop count sub-block
+ int b1 = ((int) block[1]) & 0xff;
+ int b2 = ((int) block[2]) & 0xff;
+ loopCount = (b2 << 8) | b1;
+ }
+ } while ((blockSize > 0) && !err());
+ }
+
+ /**
+ * Reads next 16-bit value, LSB first
+ */
+ protected int readShort() {
+ // read 16-bit value, LSB first
+ return read() | (read() << 8);
+ }
+
+ /**
+ * Resets frame state for reading next image.
+ */
+ protected void resetFrame() {
+ lastDispose = dispose;
+ lastRect = new Rectangle(ix, iy, iw, ih);
+ lastImage = image;
+ lastBgColor = bgColor;
+ int dispose = 0;
+ boolean transparency = false;
+ int delay = 0;
+ lct = null;
+ }
+
+ /**
+ * Skips variable length blocks up to and including
+ * next zero length block.
+ */
+ protected void skip() {
+ do {
+ readBlock();
+ } while ((blockSize > 0) && !err());
+ }
+}
\ No newline at end of file
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/gif/LZWEncoder.java b/dotCMS/src/main/java/com/dotmarketing/image/gif/LZWEncoder.java
new file mode 100755
index 000000000000..619a94fd0471
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotmarketing/image/gif/LZWEncoder.java
@@ -0,0 +1,303 @@
+package com.dotmarketing.image.gif;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+//==============================================================================
+// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.
+// K Weiner 12/00
+
+class LZWEncoder {
+
+ private static final int EOF = -1;
+
+ private int imgW, imgH;
+ private byte[] pixAry;
+ private int initCodeSize;
+ private int remaining;
+ private int curPixel;
+
+ // GIFCOMPR.C - GIF Image compression routines
+ //
+ // Lempel-Ziv compression based on 'compress'. GIF modifications by
+ // David Rowley (mgardi@watdcsu.waterloo.edu)
+
+ // General DEFINEs
+
+ static final int BITS = 12;
+
+ static final int HSIZE = 5003; // 80% occupancy
+
+ // GIF Image compression - modified 'compress'
+ //
+ // Based on: compress.c - File compression ala IEEE Computer, June 1984.
+ //
+ // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
+ // Jim McKie (decvax!mcvax!jim)
+ // Steve Davies (decvax!vax135!petsd!peora!srd)
+ // Ken Turkowski (decvax!decwrl!turtlevax!ken)
+ // James A. Woods (decvax!ihnp4!ames!jaw)
+ // Joe Orost (decvax!vax135!petsd!joe)
+
+ int n_bits; // number of bits/code
+ int maxbits = BITS; // user settable max # bits/code
+ int maxcode; // maximum code, given n_bits
+ int maxmaxcode = 1 << BITS; // should NEVER generate this code
+
+ int[] htab = new int[HSIZE];
+ int[] codetab = new int[HSIZE];
+
+ int hsize = HSIZE; // for dynamic table sizing
+
+ int free_ent = 0; // first unused entry
+
+ // block compression parameters -- after all codes are used up,
+ // and compression rate changes, start over.
+ boolean clear_flg = false;
+
+ // Algorithm: use open addressing double hashing (no chaining) on the
+ // prefix code / next character combination. We do a variant of Knuth's
+ // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
+ // secondary probe. Here, the modular division first probe is gives way
+ // to a faster exclusive-or manipulation. Also do block compression with
+ // an adaptive reset, whereby the code table is cleared when the compression
+ // ratio decreases, but after the table fills. The variable-length output
+ // codes are re-sized at this point, and a special CLEAR code is generated
+ // for the decompressor. Late addition: construct the table according to
+ // file size for noticeable speed improvement on small files. Please direct
+ // questions about this implementation to ames!jaw.
+
+ int g_init_bits;
+
+ int ClearCode;
+ int EOFCode;
+
+ // output
+ //
+ // Output the given code.
+ // Inputs:
+ // code: A n_bits-bit integer. If == -1, then EOF. This assumes
+ // that n_bits =< wordsize - 1.
+ // Outputs:
+ // Outputs code to the file.
+ // Assumptions:
+ // Chars are 8 bits long.
+ // Algorithm:
+ // Maintain a BITS character long buffer (so that 8 codes will
+ // fit in it exactly). Use the VAX insv instruction to insert each
+ // code in turn. When the buffer fills up empty it and start over.
+
+ int cur_accum = 0;
+ int cur_bits = 0;
+
+ int masks[] =
+ {
+ 0x0000,
+ 0x0001,
+ 0x0003,
+ 0x0007,
+ 0x000F,
+ 0x001F,
+ 0x003F,
+ 0x007F,
+ 0x00FF,
+ 0x01FF,
+ 0x03FF,
+ 0x07FF,
+ 0x0FFF,
+ 0x1FFF,
+ 0x3FFF,
+ 0x7FFF,
+ 0xFFFF };
+
+ // Number of characters so far in this 'packet'
+ int a_count;
+
+ // Define the storage for the packet accumulator
+ byte[] accum = new byte[256];
+
+ //----------------------------------------------------------------------------
+ LZWEncoder(int width, int height, byte[] pixels, int color_depth) {
+ imgW = width;
+ imgH = height;
+ pixAry = pixels;
+ initCodeSize = Math.max(2, color_depth);
+ }
+
+ // Add a character to the end of the current packet, and if it is 254
+ // characters, flush the packet to disk.
+ void char_out(byte c, OutputStream outs) throws IOException {
+ accum[a_count++] = c;
+ if (a_count >= 254)
+ flush_char(outs);
+ }
+
+ // Clear out the hash table
+
+ // table clear for block compress
+ void cl_block(OutputStream outs) throws IOException {
+ cl_hash(hsize);
+ free_ent = ClearCode + 2;
+ clear_flg = true;
+
+ output(ClearCode, outs);
+ }
+
+ // reset code table
+ void cl_hash(int hsize) {
+ for (int i = 0; i < hsize; ++i)
+ htab[i] = -1;
+ }
+
+ void compress(int init_bits, OutputStream outs) throws IOException {
+ int fcode;
+ int i /* = 0 */;
+ int c;
+ int ent;
+ int disp;
+ int hsize_reg;
+ int hshift;
+
+ // Set up the globals: g_init_bits - initial number of bits
+ g_init_bits = init_bits;
+
+ // Set up the necessary values
+ clear_flg = false;
+ n_bits = g_init_bits;
+ maxcode = MAXCODE(n_bits);
+
+ ClearCode = 1 << (init_bits - 1);
+ EOFCode = ClearCode + 1;
+ free_ent = ClearCode + 2;
+
+ a_count = 0; // clear packet
+
+ ent = nextPixel();
+
+ hshift = 0;
+ for (fcode = hsize; fcode < 65536; fcode *= 2)
+ ++hshift;
+ hshift = 8 - hshift; // set hash code range bound
+
+ hsize_reg = hsize;
+ cl_hash(hsize_reg); // clear hash table
+
+ output(ClearCode, outs);
+
+ outer_loop : while ((c = nextPixel()) != EOF) {
+ fcode = (c << maxbits) + ent;
+ i = (c << hshift) ^ ent; // xor hashing
+
+ if (htab[i] == fcode) {
+ ent = codetab[i];
+ continue;
+ } else if (htab[i] >= 0) // non-empty slot
+ {
+ disp = hsize_reg - i; // secondary hash (after G. Knott)
+ if (i == 0)
+ disp = 1;
+ do {
+ if ((i -= disp) < 0)
+ i += hsize_reg;
+
+ if (htab[i] == fcode) {
+ ent = codetab[i];
+ continue outer_loop;
+ }
+ } while (htab[i] >= 0);
+ }
+ output(ent, outs);
+ ent = c;
+ if (free_ent < maxmaxcode) {
+ codetab[i] = free_ent++; // code -> hashtable
+ htab[i] = fcode;
+ } else
+ cl_block(outs);
+ }
+ // Put out the final code.
+ output(ent, outs);
+ output(EOFCode, outs);
+ }
+
+ //----------------------------------------------------------------------------
+ void encode(OutputStream os) throws IOException {
+ os.write(initCodeSize); // write "initial code size" byte
+
+ remaining = imgW * imgH; // reset navigation variables
+ curPixel = 0;
+
+ compress(initCodeSize + 1, os); // compress and write the pixel data
+
+ os.write(0); // write block terminator
+ }
+
+ // Flush the packet to disk, and reset the accumulator
+ void flush_char(OutputStream outs) throws IOException {
+ if (a_count > 0) {
+ outs.write(a_count);
+ outs.write(accum, 0, a_count);
+ a_count = 0;
+ }
+ }
+
+ final int MAXCODE(int n_bits) {
+ return (1 << n_bits) - 1;
+ }
+
+ //----------------------------------------------------------------------------
+ // Return the next pixel from the image
+ //----------------------------------------------------------------------------
+ private int nextPixel() {
+ if (remaining == 0)
+ return EOF;
+
+ --remaining;
+
+ byte pix = pixAry[curPixel++];
+
+ return pix & 0xff;
+ }
+
+ void output(int code, OutputStream outs) throws IOException {
+ cur_accum &= masks[cur_bits];
+
+ if (cur_bits > 0)
+ cur_accum |= (code << cur_bits);
+ else
+ cur_accum = code;
+
+ cur_bits += n_bits;
+
+ while (cur_bits >= 8) {
+ char_out((byte) (cur_accum & 0xff), outs);
+ cur_accum >>= 8;
+ cur_bits -= 8;
+ }
+
+ // If the next entry is going to be too big for the code size,
+ // then increase it, if possible.
+ if (free_ent > maxcode || clear_flg) {
+ if (clear_flg) {
+ maxcode = MAXCODE(n_bits = g_init_bits);
+ clear_flg = false;
+ } else {
+ ++n_bits;
+ if (n_bits == maxbits)
+ maxcode = maxmaxcode;
+ else
+ maxcode = MAXCODE(n_bits);
+ }
+ }
+
+ if (code == EOFCode) {
+ // At EOF, write the rest of the buffer.
+ while (cur_bits > 0) {
+ char_out((byte) (cur_accum & 0xff), outs);
+ cur_accum >>= 8;
+ cur_bits -= 8;
+ }
+
+ flush_char(outs);
+ }
+ }
+}
diff --git a/dotCMS/src/main/java/com/dotmarketing/image/gif/NeuQuant.java b/dotCMS/src/main/java/com/dotmarketing/image/gif/NeuQuant.java
new file mode 100755
index 000000000000..324e348f2682
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotmarketing/image/gif/NeuQuant.java
@@ -0,0 +1,459 @@
+package com.dotmarketing.image.gif;
+
+/* NeuQuant Neural-Net Quantization Algorithm
+ * ------------------------------------------
+ *
+ * Copyright (c) 1994 Anthony Dekker
+ *
+ * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
+ * See "Kohonen neural networks for optimal colour quantization"
+ * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
+ * for a discussion of the algorithm.
+ *
+ * Any party obtaining a copy of these files from the author, directly or
+ * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
+ * world-wide, paid up, royalty-free, nonexclusive right and license to deal
+ * in this software and documentation files (the "Software"), including without
+ * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons who receive
+ * copies from any such party to do so, with the only requirement being
+ * that this copyright notice remain intact.
+ */
+
+// Ported to Java 12/00 K Weiner
+
+public class NeuQuant {
+
+ protected static final int netsize = 256; /* number of colours used */
+
+ /* four primes near 500 - assume no image has a length so large */
+ /* that it is divisible by all four primes */
+ protected static final int prime1 = 499;
+ protected static final int prime2 = 491;
+ protected static final int prime3 = 487;
+ protected static final int prime4 = 503;
+
+ protected static final int minpicturebytes = (3 * prime4);
+ /* minimum size for input image */
+
+ /* Program Skeleton
+ ----------------
+ [select samplefac in range 1..30]
+ [read image from input file]
+ pic = (unsigned char*) malloc(3*width*height);
+ initnet(pic,3*width*height,samplefac);
+ learn();
+ unbiasnet();
+ [write output image header, using writecolourmap(f)]
+ inxbuild();
+ write output image using inxsearch(b,g,r) */
+
+ /* Network Definitions
+ ------------------- */
+
+ protected static final int maxnetpos = (netsize - 1);
+ protected static final int netbiasshift = 4; /* bias for colour values */
+ protected static final int ncycles = 100; /* no. of learning cycles */
+
+ /* defs for freq and bias */
+ protected static final int intbiasshift = 16; /* bias for fractions */
+ protected static final int intbias = (((int) 1) << intbiasshift);
+ protected static final int gammashift = 10; /* gamma = 1024 */
+ protected static final int gamma = (((int) 1) << gammashift);
+ protected static final int betashift = 10;
+ protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */
+ protected static final int betagamma =
+ (intbias << (gammashift - betashift));
+
+ /* defs for decreasing radius factor */
+ protected static final int initrad = (netsize >> 3); /* for 256 cols, radius starts */
+ protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */
+ protected static final int radiusbias = (((int) 1) << radiusbiasshift);
+ protected static final int initradius = (initrad * radiusbias); /* and decreases by a */
+ protected static final int radiusdec = 30; /* factor of 1/30 each cycle */
+
+ /* defs for decreasing alpha factor */
+ protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */
+ protected static final int initalpha = (((int) 1) << alphabiasshift);
+
+ protected int alphadec; /* biased by 10 bits */
+
+ /* radbias and alpharadbias used for radpower calculation */
+ protected static final int radbiasshift = 8;
+ protected static final int radbias = (((int) 1) << radbiasshift);
+ protected static final int alpharadbshift = (alphabiasshift + radbiasshift);
+ protected static final int alpharadbias = (((int) 1) << alpharadbshift);
+
+ /* Types and Global Variables
+ -------------------------- */
+
+ protected byte[] thepicture; /* the input image itself */
+ protected int lengthcount; /* lengthcount = H*W*3 */
+
+ protected int samplefac; /* sampling factor 1..30 */
+
+ // typedef int pixel[4]; /* BGRc */
+ protected int[][] network; /* the network itself - [netsize][4] */
+
+ protected int[] netindex = new int[256];
+ /* for network lookup - really 256 */
+
+ protected int[] bias = new int[netsize];
+ /* bias and freq arrays for learning */
+ protected int[] freq = new int[netsize];
+ protected int[] radpower = new int[initrad];
+ /* radpower for precomputation */
+
+ /* Initialise network in range (0,0,0) to (255,255,255) and set parameters
+ ----------------------------------------------------------------------- */
+ public NeuQuant(byte[] thepic, int len, int sample) {
+
+ int i;
+ int[] p;
+
+ thepicture = thepic;
+ lengthcount = len;
+ samplefac = sample;
+
+ network = new int[netsize][];
+ for (i = 0; i < netsize; i++) {
+ network[i] = new int[4];
+ p = network[i];
+ p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;
+ freq[i] = intbias / netsize; /* 1/netsize */
+ bias[i] = 0;
+ }
+ }
+
+ public byte[] colorMap() {
+ byte[] map = new byte[3 * netsize];
+ int[] index = new int[netsize];
+ for (int i = 0; i < netsize; i++)
+ index[network[i][3]] = i;
+ int k = 0;
+ for (int i = 0; i < netsize; i++) {
+ int j = index[i];
+ map[k++] = (byte) (network[j][0]);
+ map[k++] = (byte) (network[j][1]);
+ map[k++] = (byte) (network[j][2]);
+ }
+ return map;
+ }
+
+ /* Insertion sort of network and building of netindex[0..255] (to do after unbias)
+ ------------------------------------------------------------------------------- */
+ public void inxbuild() {
+
+ int i, j, smallpos, smallval;
+ int[] p;
+ int[] q;
+ int previouscol, startpos;
+
+ previouscol = 0;
+ startpos = 0;
+ for (i = 0; i < netsize; i++) {
+ p = network[i];
+ smallpos = i;
+ smallval = p[1]; /* index on g */
+ /* find smallest in i..netsize-1 */
+ for (j = i + 1; j < netsize; j++) {
+ q = network[j];
+ if (q[1] < smallval) { /* index on g */
+ smallpos = j;
+ smallval = q[1]; /* index on g */
+ }
+ }
+ q = network[smallpos];
+ /* swap p (i) and q (smallpos) entries */
+ if (i != smallpos) {
+ j = q[0];
+ q[0] = p[0];
+ p[0] = j;
+ j = q[1];
+ q[1] = p[1];
+ p[1] = j;
+ j = q[2];
+ q[2] = p[2];
+ p[2] = j;
+ j = q[3];
+ q[3] = p[3];
+ p[3] = j;
+ }
+ /* smallval entry is now in position i */
+ if (smallval != previouscol) {
+ netindex[previouscol] = (startpos + i) >> 1;
+ for (j = previouscol + 1; j < smallval; j++)
+ netindex[j] = i;
+ previouscol = smallval;
+ startpos = i;
+ }
+ }
+ netindex[previouscol] = (startpos + maxnetpos) >> 1;
+ for (j = previouscol + 1; j < 256; j++)
+ netindex[j] = maxnetpos; /* really 256 */
+ }
+
+ /* Main Learning Loop
+ ------------------ */
+ public void learn() {
+
+ int i, j, b, g, r;
+ int radius, rad, alpha, step, delta, samplepixels;
+ byte[] p;
+ int pix, lim;
+
+ if (lengthcount < minpicturebytes)
+ samplefac = 1;
+ alphadec = 30 + ((samplefac - 1) / 3);
+ p = thepicture;
+ pix = 0;
+ lim = lengthcount;
+ samplepixels = lengthcount / (3 * samplefac);
+ delta = samplepixels / ncycles;
+ alpha = initalpha;
+ radius = initradius;
+
+ rad = radius >> radiusbiasshift;
+ if (rad <= 1)
+ rad = 0;
+ for (i = 0; i < rad; i++)
+ radpower[i] =
+ alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
+
+ //fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad);
+
+ if (lengthcount < minpicturebytes)
+ step = 3;
+ else if ((lengthcount % prime1) != 0)
+ step = 3 * prime1;
+ else {
+ if ((lengthcount % prime2) != 0)
+ step = 3 * prime2;
+ else {
+ if ((lengthcount % prime3) != 0)
+ step = 3 * prime3;
+ else
+ step = 3 * prime4;
+ }
+ }
+
+ i = 0;
+ while (i < samplepixels) {
+ b = (p[pix + 0] & 0xff) << netbiasshift;
+ g = (p[pix + 1] & 0xff) << netbiasshift;
+ r = (p[pix + 2] & 0xff) << netbiasshift;
+ j = contest(b, g, r);
+
+ altersingle(alpha, j, b, g, r);
+ if (rad != 0)
+ alterneigh(rad, j, b, g, r); /* alter neighbours */
+
+ pix += step;
+ if (pix >= lim)
+ pix -= lengthcount;
+
+ i++;
+ if (delta == 0)
+ delta = 1;
+ if (i % delta == 0) {
+ alpha -= alpha / alphadec;
+ radius -= radius / radiusdec;
+ rad = radius >> radiusbiasshift;
+ if (rad <= 1)
+ rad = 0;
+ for (j = 0; j < rad; j++)
+ radpower[j] =
+ alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
+ }
+ }
+ //fprintf(stderr,"finished 1D learning: final alpha=%f !\n",((float)alpha)/initalpha);
+ }
+
+ /* Search for BGR values 0..255 (after net is unbiased) and return colour index
+ ---------------------------------------------------------------------------- */
+ public int map(int b, int g, int r) {
+
+ int i, j, dist, a, bestd;
+ int[] p;
+ int best;
+
+ bestd = 1000; /* biggest possible dist is 256*3 */
+ best = -1;
+ i = netindex[g]; /* index on g */
+ j = i - 1; /* start at netindex[g] and work outwards */
+
+ while ((i < netsize) || (j >= 0)) {
+ if (i < netsize) {
+ p = network[i];
+ dist = p[1] - g; /* inx key */
+ if (dist >= bestd)
+ i = netsize; /* stop iter */
+ else {
+ i++;
+ if (dist < 0)
+ dist = -dist;
+ a = p[0] - b;
+ if (a < 0)
+ a = -a;
+ dist += a;
+ if (dist < bestd) {
+ a = p[2] - r;
+ if (a < 0)
+ a = -a;
+ dist += a;
+ if (dist < bestd) {
+ bestd = dist;
+ best = p[3];
+ }
+ }
+ }
+ }
+ if (j >= 0) {
+ p = network[j];
+ dist = g - p[1]; /* inx key - reverse dif */
+ if (dist >= bestd)
+ j = -1; /* stop iter */
+ else {
+ j--;
+ if (dist < 0)
+ dist = -dist;
+ a = p[0] - b;
+ if (a < 0)
+ a = -a;
+ dist += a;
+ if (dist < bestd) {
+ a = p[2] - r;
+ if (a < 0)
+ a = -a;
+ dist += a;
+ if (dist < bestd) {
+ bestd = dist;
+ best = p[3];
+ }
+ }
+ }
+ }
+ }
+ return (best);
+ }
+ public byte[] process() {
+ learn();
+ unbiasnet();
+ inxbuild();
+ return colorMap();
+ }
+
+ /* Unbias network to give byte values 0..255 and record position i to prepare for sort
+ ----------------------------------------------------------------------------------- */
+ public void unbiasnet() {
+
+ int i, j;
+
+ for (i = 0; i < netsize; i++) {
+ network[i][0] >>= netbiasshift;
+ network[i][1] >>= netbiasshift;
+ network[i][2] >>= netbiasshift;
+ network[i][3] = i; /* record colour no */
+ }
+ }
+
+ /* Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]
+ --------------------------------------------------------------------------------- */
+ protected void alterneigh(int rad, int i, int b, int g, int r) {
+
+ int j, k, lo, hi, a, m;
+ int[] p;
+
+ lo = i - rad;
+ if (lo < -1)
+ lo = -1;
+ hi = i + rad;
+ if (hi > netsize)
+ hi = netsize;
+
+ j = i + 1;
+ k = i - 1;
+ m = 1;
+ while ((j < hi) || (k > lo)) {
+ a = radpower[m++];
+ if (j < hi) {
+ p = network[j++];
+ try {
+ p[0] -= (a * (p[0] - b)) / alpharadbias;
+ p[1] -= (a * (p[1] - g)) / alpharadbias;
+ p[2] -= (a * (p[2] - r)) / alpharadbias;
+ } catch (Exception e) {
+ } // prevents 1.3 miscompilation
+ }
+ if (k > lo) {
+ p = network[k--];
+ try {
+ p[0] -= (a * (p[0] - b)) / alpharadbias;
+ p[1] -= (a * (p[1] - g)) / alpharadbias;
+ p[2] -= (a * (p[2] - r)) / alpharadbias;
+ } catch (Exception e) {
+ }
+ }
+ }
+ }
+
+ /* Move neuron i towards biased (b,g,r) by factor alpha
+ ---------------------------------------------------- */
+ protected void altersingle(int alpha, int i, int b, int g, int r) {
+
+ /* alter hit neuron */
+ int[] n = network[i];
+ n[0] -= (alpha * (n[0] - b)) / initalpha;
+ n[1] -= (alpha * (n[1] - g)) / initalpha;
+ n[2] -= (alpha * (n[2] - r)) / initalpha;
+ }
+
+ /* Search for biased BGR values
+ ---------------------------- */
+ protected int contest(int b, int g, int r) {
+
+ /* finds closest neuron (min dist) and updates freq */
+ /* finds best neuron (min dist-bias) and returns position */
+ /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
+ /* bias[i] = gamma*((1/netsize)-freq[i]) */
+
+ int i, dist, a, biasdist, betafreq;
+ int bestpos, bestbiaspos, bestd, bestbiasd;
+ int[] n;
+
+ bestd = ~(((int) 1) << 31);
+ bestbiasd = bestd;
+ bestpos = -1;
+ bestbiaspos = bestpos;
+
+ for (i = 0; i < netsize; i++) {
+ n = network[i];
+ dist = n[0] - b;
+ if (dist < 0)
+ dist = -dist;
+ a = n[1] - g;
+ if (a < 0)
+ a = -a;
+ dist += a;
+ a = n[2] - r;
+ if (a < 0)
+ a = -a;
+ dist += a;
+ if (dist < bestd) {
+ bestd = dist;
+ bestpos = i;
+ }
+ biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
+ if (biasdist < bestbiasd) {
+ bestbiasd = biasdist;
+ bestbiaspos = i;
+ }
+ betafreq = (freq[i] >> betashift);
+ freq[i] -= betafreq;
+ bias[i] += (betafreq << gammashift);
+ }
+ freq[bestpos] += beta;
+ bias[bestpos] -= betagamma;
+ return (bestbiaspos);
+ }
+}
diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/exporter/ImageFilterExporter.java b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/exporter/ImageFilterExporter.java
index 7db532b82724..f5d2d8494779 100644
--- a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/exporter/ImageFilterExporter.java
+++ b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/exporter/ImageFilterExporter.java
@@ -1,107 +1,165 @@
package com.dotmarketing.portlets.contentlet.business.exporter;
+import com.google.common.collect.ImmutableSet;
import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.util.Collection;
import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.Semaphore;
-import com.dotmarketing.business.APILocator;
-import com.dotmarketing.business.UserAPI;
+import com.dotmarketing.exception.DotRuntimeException;
+import com.dotmarketing.image.ImageEngine;
import com.dotmarketing.image.filter.ImageFilter;
+import com.dotmarketing.image.filter.ImageFilterAPI;
import com.dotmarketing.image.filter.PDFImageFilter;
import com.dotmarketing.portlets.contentlet.business.BinaryContentExporter;
import com.dotmarketing.portlets.contentlet.business.BinaryContentExporterException;
-import com.dotmarketing.portlets.contentlet.business.ContentletAPI;
+import com.dotmarketing.util.Config;
import com.dotmarketing.util.Logger;
+import com.dotmarketing.util.UtilMethods;
+import io.vavr.control.Try;
/**
- *
- * A exporter that can take 1 or more filters in a chain
- *
- * the chain is provided by the "filter=" parameter
- * You can chain filters so that you resize then crop to
- * produce the resulting image
- *
- *
+ *
+ * An exporter that can take 1 or more filters in a chain
+ *
+ * the chain is provided by the "filter=" parameter You can chain filters so that you resize then
+ * crop to produce the resulting image
+ *
+ *
*/
public class ImageFilterExporter implements BinaryContentExporter {
-
- /* (non-Javadoc)
- * @see com.dotmarketing.portlets.contentlet.business.BinaryContentExporter#exportContent(java.io.File, java.util.Map)
- */
- public BinaryContentExporterData exportContent(File file, Map parameters) throws BinaryContentExporterException {
-
-
- BinaryContentExporterData data;
-
- try {
-
- List filters=new ArrayList<>();
-
- if(parameters.get("filter") != null){
- filters.addAll( Arrays.asList(parameters.get("filter")[0].split(",")));
- }
- else if(parameters.get("filters") != null){
- filters.addAll( Arrays.asList(parameters.get("filters")[0].split(",")));
- }
-
-
- if(file.getAbsolutePath().toLowerCase().endsWith(".pdf")){
- filters.remove("PDF");
- filters.add(0, "PDF");
- }
-
- else if(filters.size()== 0 ){
- filters.remove("Png");
- filters.add(0, "Png");
- }
-
-
- parameters.put("filter", filters.toArray(new String[filters.size()]));
- parameters.put("filters", filters.toArray(new String[filters.size()]));
- for(String s : filters){
- String clazz =null;
- try {
- clazz ="com.dotmarketing.image.filter." + s + "ImageFilter";
- Class iFilter = (Class) Class.forName( clazz );
- ImageFilter i= iFilter.newInstance();
- file = i.runFilter(file, parameters);
- } catch (ClassNotFoundException e) {
- Logger.error(ImageFilterExporter.class, "Unable to instanciate : " + clazz );
- } catch (InstantiationException e) {
- Logger.error(ImageFilterExporter.class, "InstantiationException : " + clazz );
- } catch (IllegalAccessException e) {
- Logger.error(ImageFilterExporter.class, "IllegalAccessException : " + clazz );
- }
- catch (Exception e) {
- Logger.error(ImageFilterExporter.class, "Exception in " + clazz + " :" + e.getMessage() + e.getStackTrace()[0] );
- }
- }
-
-
- data = new BinaryContentExporterData(file);
-
- } catch (Exception e) {
- Logger.error(ImageFilterExporter.class, e.getMessage(), e);
- throw new BinaryContentExporterException(e.getMessage(), e);
- }
-
- return data;
- }
-
- public String getName() {
- return "Image Filter Exporter";
- }
-
- public String getPathMapping() {
- return "image";
- }
-
- public String getDescription() {
- return "Specify filters to run a source image through";
- }
+ private final int allowedRequests = Config.getIntProperty("IMAGE_GENERATION_SIMULTANEOUS_REQUESTS", 10);
+
+ private final Semaphore semaphore = new Semaphore(allowedRequests);
+
+ private static final Set VECTOR_EXTENSIONS = ImmutableSet.of("svg", "eps", "ai", "dxf");
+
+
+ /**
+ * Selects the image engine per the {@code IMAGE_API_USE_LIBVIPS} feature flag. The choice only
+ * affects which {@link ImageFilter} subclasses {@code resolveFilters} returns — the URL parameter
+ * contract is identical for both engines.
+ */
+ // package-visible for tests that pin the feature-flag selection behaviour
+ ImageFilterAPI imageFilterAPI() {
+ return ImageEngine.resolve();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.dotmarketing.portlets.contentlet.business.BinaryContentExporter#exportContent(java.io.File,
+ * java.util.Map)
+ */
+ public BinaryContentExporterData exportContent(File file, final Map parameters)
+ throws BinaryContentExporterException {
+
+ final String fileExtension = UtilMethods.getFileExtension(file.getName());
+ if (VECTOR_EXTENSIONS.contains(fileExtension)) {
+ Logger.info(this.getClass(), "Skipping vector image transformation for " + fileExtension);
+ return new BinaryContentExporterData(file);
+ }
+
+ Class extends ImageFilter> errorClass = ImageFilter.class;
+ try {
+
+ final Map> filters = imageFilterAPI().resolveFilters(parameters);
+ parameters.put("filter", filters.keySet().toArray(new String[0]));
+ parameters.put("filters", filters.keySet().toArray(new String[0]));
+
+ // run pdf filter first (if a pdf)
+ if(!filters.isEmpty() && "pdf".equals(fileExtension) && !filters.containsKey("pdf")) {
+ file = runFilter(new PDFImageFilter(), file, parameters);
+ }
+
+ Optional tempFile = alreadyGenerated(filters.values(), file, parameters);
+
+ //short circuit if we already have it generated
+ if (tempFile.isPresent()) {
+ return new BinaryContentExporterData(tempFile.get());
+ }
+
+
+ for (final Class extends ImageFilter> filter : filters.values()) {
+ errorClass=filter;
+ final ImageFilter imageFilter = filter.getDeclaredConstructor().newInstance();
+
+ file = runFilter(imageFilter, file, parameters);
+ }
+
+ return new BinaryContentExporterData(file);
+ } catch (Exception e) {
+
+ Logger.warnAndDebug(errorClass, e);
+ throw new BinaryContentExporterException(e.getMessage(), e);
+ }
+
+ }
+
+ public class ImageNotReadyException extends Exception {
+ ImageNotReadyException(String message) {
+ super(message);
+ }
+ }
+
+ private File runFilter(ImageFilter imageFilter, final File fileIn,final Map parameters)
+ throws ImageNotReadyException {
+
+ boolean canRun=false;
+ try {
+
+ canRun = semaphore.tryAcquire();
+ Logger.debug(getClass(), "Image permits/requests : " + allowedRequests + "/" + (allowedRequests-semaphore.availablePermits()));
+
+ if(!canRun) {
+ Logger.warn(getClass(), "Image permits exhausted : " + allowedRequests + "/" + (allowedRequests-semaphore.availablePermits()));
+
+ throw new ImageNotReadyException("Image permits exhausted");
+
+ }
+
+ return imageFilter.runFilter(fileIn, parameters);
+ }
+ finally {
+ if(canRun) {
+ semaphore.release();
+ }
+ }
+ }
+
+ private Optional alreadyGenerated(final Collection> clazzes, final File fileIn,
+ final Map parameters) {
+
+ File fileToReturn = fileIn;
+
+ for (final Class extends ImageFilter> filter : clazzes) {
+ final ImageFilter imageFilter = Try.of(()-> filter.getDeclaredConstructor().newInstance()).getOrElseThrow(DotRuntimeException::new);
+ fileToReturn = imageFilter.getResultsFile(fileToReturn, parameters);
+
+
+ }
+
+ if (fileToReturn == null || ! fileToReturn.exists() || fileToReturn.length() < 50) {
+ return Optional.empty();
+ }
+ return Optional.of(fileToReturn);
+ }
+
+ public String getName() {
+ return "Image Filter Exporter";
+ }
+
+ public String getPathMapping() {
+ return "image";
+ }
+
+ public String getDescription() {
+ return "Specify filters to run a source image through";
+ }
}
diff --git a/dotCMS/src/main/webapp/html/portlet/ext/common/edit_permissions_tab_js_inc.jsp b/dotCMS/src/main/webapp/html/portlet/ext/common/edit_permissions_tab_js_inc.jsp
index 7e92424bc953..33bf1af1426e 100644
--- a/dotCMS/src/main/webapp/html/portlet/ext/common/edit_permissions_tab_js_inc.jsp
+++ b/dotCMS/src/main/webapp/html/portlet/ext/common/edit_permissions_tab_js_inc.jsp
@@ -143,7 +143,10 @@
return;
}
- dojo.style('loadingPermissionsAccordion', { display: '' });
+ if(dojo.byId("loadingPermissionsAccordion")){
+ dojo.style('loadingPermissionsAccordion', { display: '' });
+ }
+
dojo.style('assetPermissionsWrapper', { display: 'none' });
if(dijit.byId('permissionsAccordionContainer')) {