| 1 | |
package org.webslinger.ext.image; |
| 2 | |
|
| 3 | |
import java.awt.Dimension; |
| 4 | |
import java.awt.Point; |
| 5 | |
import java.awt.Rectangle; |
| 6 | |
import java.io.File; |
| 7 | |
import java.io.InputStream; |
| 8 | |
import java.io.IOException; |
| 9 | |
import java.util.ArrayList; |
| 10 | |
import java.util.Arrays; |
| 11 | |
import java.util.List; |
| 12 | |
import java.util.ListIterator; |
| 13 | |
import java.util.Map; |
| 14 | |
import java.util.concurrent.CopyOnWriteArrayList; |
| 15 | |
import static java.lang.Math.max; |
| 16 | |
import static java.lang.Math.min; |
| 17 | |
import javax.servlet.ServletException; |
| 18 | |
|
| 19 | |
import org.apache.commons.collections.Transformer; |
| 20 | |
import org.apache.commons.lang.StringUtils; |
| 21 | |
import org.apache.commons.vfs.FileContent; |
| 22 | |
import org.apache.commons.vfs.FileObject; |
| 23 | |
|
| 24 | |
import org.webslinger.PathContext; |
| 25 | |
import org.webslinger.TypeHandler; |
| 26 | |
import org.webslinger.Webslinger; |
| 27 | |
import org.webslinger.WebslingerServletContext; |
| 28 | |
import org.webslinger.collections.CollectionUtil; |
| 29 | |
import org.webslinger.commons.vfs.VFSUtil; |
| 30 | |
import org.webslinger.io.IOUtil; |
| 31 | |
import org.webslinger.lang.ConcurrentCache; |
| 32 | |
import static org.webslinger.lang.NumberUtil.parseInt; |
| 33 | |
import org.webslinger.servlet.Binary; |
| 34 | |
import org.webslinger.util.GeneratedResult; |
| 35 | |
import org.webslinger.util.TTLObject; |
| 36 | |
|
| 37 | 0 | public class ImageUtils { |
| 38 | 0 | private static final ImageSizeCache imageBoxes = new ImageSizeCache(ImageUtils.class, "imageBoxes", null); |
| 39 | |
|
| 40 | |
protected FileObject root; |
| 41 | |
|
| 42 | |
public static String runImageMagick(String[] commandAndArgs) throws InterruptedException, IOException { |
| 43 | 0 | ProcessBuilder builder = new ProcessBuilder(commandAndArgs); |
| 44 | 0 | Map<String, String> env = builder.environment(); |
| 45 | 0 | env.remove("DISPLAY"); |
| 46 | 0 | Process process = builder |
| 47 | |
.directory(new File("/")) |
| 48 | |
.redirectErrorStream(true) |
| 49 | |
.start(); |
| 50 | 0 | String output = IOUtil.readString(process.getInputStream()); |
| 51 | 0 | process.waitFor(); |
| 52 | 0 | return process.exitValue() == 0 ? output : null; |
| 53 | |
} |
| 54 | |
|
| 55 | |
private static Rectangle getImageBox(String prefix, InputStream in) throws InterruptedException, IOException { |
| 56 | 0 | File tmpFile = File.createTempFile(prefix, ".tmp"); |
| 57 | 0 | tmpFile.deleteOnExit(); |
| 58 | |
try { |
| 59 | 0 | IOUtil.copy(in, true, tmpFile); |
| 60 | 0 | return getImageBox(tmpFile); |
| 61 | |
} finally { |
| 62 | 0 | tmpFile.delete(); |
| 63 | |
} |
| 64 | |
} |
| 65 | |
|
| 66 | |
public static Rectangle getImageBoxCached(File file) throws InterruptedException, IOException { |
| 67 | 0 | return imageBoxes.get(file).getObject(); |
| 68 | |
} |
| 69 | |
|
| 70 | |
public static Rectangle getImageBox(File file) throws InterruptedException, IOException { |
| 71 | 0 | String output = runImageMagick(new String[] {"identify", file.getPath()}); |
| 72 | 0 | if (output == null) return null; |
| 73 | |
|
| 74 | |
String[] dimensions; |
| 75 | |
try { |
| 76 | 0 | dimensions = (output.substring(file.getPath().length() + 1).split(" "))[1].split("x"); |
| 77 | 0 | } catch (RuntimeException e) { |
| 78 | 0 | System.err.println("error parsing(" + file + "->" + output + ")"); |
| 79 | 0 | throw e; |
| 80 | 0 | } |
| 81 | 0 | return new Rectangle(0, 0, Integer.parseInt(dimensions[0]), Integer.parseInt(dimensions[1])); |
| 82 | |
} |
| 83 | |
|
| 84 | |
public static void convertType(File file, String type) throws InterruptedException, IOException { |
| 85 | 0 | if (type != null) runImageMagick(new String[] {"mogrify", "-format", type, file.getPath()}); |
| 86 | 0 | } |
| 87 | |
|
| 88 | |
public static Rectangle getImageBox(FileObject file) throws InterruptedException, IOException { |
| 89 | |
File localFile; |
| 90 | |
try { |
| 91 | 0 | localFile = VFSUtil.findLocalFile(file); |
| 92 | 0 | } catch (IOException e) { |
| 93 | 0 | localFile = null; |
| 94 | 0 | } |
| 95 | 0 | if (localFile != null) return getImageBoxCached(localFile); |
| 96 | 0 | return getImageBox("image-box-commons-vfs-", file.getContent().getInputStream()); |
| 97 | |
} |
| 98 | |
|
| 99 | |
public static Rectangle getImageBox(Binary.FileContent content) throws InterruptedException, IOException { |
| 100 | 0 | return getImageBox(content.getFile()); |
| 101 | |
} |
| 102 | |
|
| 103 | |
public static Rectangle getImageBox(Binary.Content content) throws InterruptedException, IOException { |
| 104 | 0 | if (content instanceof Binary.FileContent) return getImageBox((Binary.FileContent) content); |
| 105 | 0 | return getImageBox("image-box-binary-content-", content.getInputStream()); |
| 106 | |
} |
| 107 | |
|
| 108 | |
public static Rectangle cropImage(File file, Rectangle cropArea) throws InterruptedException, IOException { |
| 109 | 0 | return cropImage(file, cropArea, false); |
| 110 | |
} |
| 111 | |
|
| 112 | |
public static Rectangle cropImage(File file, Rectangle cropArea, boolean preview) throws InterruptedException, IOException { |
| 113 | 0 | Rectangle imageBox = getImageBox(file); |
| 114 | 0 | if (imageBox == null) return null; |
| 115 | 0 | cropArea = imageBox.intersection(cropArea); |
| 116 | 0 | if (!preview) { |
| 117 | 0 | runImageMagick(new String[] {"mogrify", "-crop", ((int) cropArea.getWidth()) + "x" + ((int) cropArea.getHeight()) + "+" + ((int) cropArea.getX()) + "+" + ((int) cropArea.getY()), file.getPath()}); |
| 118 | 0 | return cropArea; |
| 119 | |
} |
| 120 | 0 | int x0 = (int) cropArea.getX(); |
| 121 | 0 | int y0 = (int) cropArea.getY(); |
| 122 | 0 | int x1 = (int) (cropArea.getX() + cropArea.getWidth()); |
| 123 | 0 | int y1 = (int) (cropArea.getY() + cropArea.getHeight()); |
| 124 | 0 | int width = (int) imageBox.getWidth(); |
| 125 | 0 | int height = (int) imageBox.getHeight(); |
| 126 | |
|
| 127 | |
|
| 128 | |
|
| 129 | |
|
| 130 | |
|
| 131 | |
|
| 132 | |
|
| 133 | |
|
| 134 | |
|
| 135 | |
|
| 136 | |
|
| 137 | |
|
| 138 | |
|
| 139 | |
|
| 140 | |
|
| 141 | |
|
| 142 | |
|
| 143 | |
|
| 144 | |
|
| 145 | |
|
| 146 | |
|
| 147 | |
|
| 148 | |
|
| 149 | |
|
| 150 | 0 | int[][] rectangles = new int[][] { |
| 151 | |
{0, 0, x0, height}, |
| 152 | |
{x1, 0, width, height}, |
| 153 | |
{x0, 0, x1, y0}, |
| 154 | |
{x0, y1, x1, height}, |
| 155 | |
}; |
| 156 | 0 | List<String> args = new ArrayList<String>(); |
| 157 | 0 | args.addAll(Arrays.asList("mogrify", "-fill", "#0000003f")); |
| 158 | 0 | for (int[] r: rectangles) { |
| 159 | 0 | args.add("-draw"); |
| 160 | 0 | args.add("rectangle " + r[0] + "," + r[1] + " " + r[2] + "," + r[3]); |
| 161 | |
} |
| 162 | 0 | args.add(file.getPath()); |
| 163 | 0 | runImageMagick(args.toArray(new String[args.size()])); |
| 164 | 0 | return imageBox; |
| 165 | |
} |
| 166 | |
|
| 167 | |
public static Rectangle resizeImage(File file, int width, int height) throws InterruptedException, IOException { |
| 168 | 0 | StringBuilder sizeArg = new StringBuilder(); |
| 169 | 0 | if (width > 0) sizeArg.append(width); |
| 170 | 0 | sizeArg.append('x'); |
| 171 | 0 | if (height > 0) sizeArg.append(height); |
| 172 | 0 | sizeArg.append('!'); |
| 173 | 0 | if (runImageMagick(new String[] {"mogrify", "-resize", sizeArg.toString(), file.getPath()}) == null) return null; |
| 174 | 0 | return getImageBox(file); |
| 175 | |
} |
| 176 | |
|
| 177 | |
public static Rectangle constrainImage(File file, int width, int height) throws InterruptedException, IOException { |
| 178 | 0 | StringBuilder sizeArg = new StringBuilder(); |
| 179 | 0 | if (width > 0) sizeArg.append(width); |
| 180 | 0 | sizeArg.append('x'); |
| 181 | 0 | if (height > 0) sizeArg.append(height); |
| 182 | 0 | sizeArg.append('>'); |
| 183 | 0 | runImageMagick(new String[] {"mogrify", "-resize", sizeArg.toString(), file.getPath()}); |
| 184 | 0 | Rectangle box = getImageBox(file); |
| 185 | 0 | if (box == null) return null; |
| 186 | 0 | if (box.getWidth() <= width && box.getHeight() <= height) return box; |
| 187 | 0 | if (box.getWidth() < width) { |
| 188 | 0 | return resizeImage(file, (int) box.getWidth(), height); |
| 189 | |
} else { |
| 190 | 0 | return resizeImage(file, width, (int) box.getHeight()); |
| 191 | |
} |
| 192 | |
} |
| 193 | |
|
| 194 | |
public static Rectangle constrainImage(File file, String fillColor, float wantedRatio) throws InterruptedException, IOException { |
| 195 | 0 | Rectangle imageBox = getImageBox(file); |
| 196 | 0 | int width = (int) imageBox.getWidth(), height = (int) imageBox.getHeight(); |
| 197 | 0 | float gotRatio = (float) height / width; |
| 198 | 0 | int pageWidth = width, pageHeight = height; |
| 199 | 0 | if (width > height) { |
| 200 | 0 | if (wantedRatio > gotRatio) { |
| 201 | 0 | pageHeight = (int) (width * wantedRatio); |
| 202 | |
} else { |
| 203 | 0 | pageWidth = (int) (height / wantedRatio); |
| 204 | |
} |
| 205 | |
} else { |
| 206 | 0 | if (wantedRatio > gotRatio) { |
| 207 | 0 | pageWidth = (int) (height / wantedRatio); |
| 208 | |
} else { |
| 209 | 0 | pageHeight = (int) (width * wantedRatio); |
| 210 | |
} |
| 211 | |
} |
| 212 | 0 | if (pageWidth == width && pageHeight == height) return imageBox; |
| 213 | 0 | int x = (pageWidth - width) / 2; |
| 214 | 0 | int y = (pageHeight - height) / 2; |
| 215 | 0 | List<String> args = new ArrayList<String>(); |
| 216 | 0 | args.add("mogrify"); |
| 217 | 0 | args.add("-fill"); args.add(fillColor); |
| 218 | 0 | args.add("-page"); args.add(pageWidth + "x" + pageHeight); |
| 219 | 0 | args.add("-extent"); args.add(pageWidth + "x" + pageHeight); |
| 220 | 0 | args.add("-draw"); args.add("image Over " + x + "," + y + " " + width + "x" + height + " '" + file.getPath() + "'"); |
| 221 | 0 | args.add("-fill"); args.add(fillColor); |
| 222 | 0 | args.add("-draw"); args.add("rectangle 0,0 " + pageWidth + "," + y); |
| 223 | 0 | args.add("-fill"); args.add(fillColor); |
| 224 | 0 | args.add("-draw"); args.add("rectangle 0,0 " + x + "," + pageHeight); |
| 225 | 0 | args.add("-fill"); args.add(fillColor); |
| 226 | 0 | args.add("-draw"); args.add("rectangle 0," + (pageHeight - y - 1) + " " + pageWidth + "," + pageHeight); |
| 227 | 0 | args.add("-fill"); args.add(fillColor); |
| 228 | 0 | args.add("-draw"); args.add("rectangle " + (pageWidth - x - 1) + ",0 " + pageWidth + "," + pageHeight); |
| 229 | 0 | args.add(file.getPath()); |
| 230 | 0 | runImageMagick(args.toArray(new String[args.size()])); |
| 231 | 0 | imageBox = getImageBox(file); |
| 232 | 0 | return imageBox; |
| 233 | |
} |
| 234 | |
|
| 235 | |
public static Rectangle constrainBox(int width, int height, int containerWidth, int containerHeight) { |
| 236 | |
int boxHeight, boxWidth; |
| 237 | 0 | if (width > containerWidth) { |
| 238 | 0 | if (height > containerHeight) { |
| 239 | 0 | double widthRatio = (float) containerWidth / width; |
| 240 | 0 | double heightRatio = (float) containerHeight / height; |
| 241 | 0 | if (widthRatio > heightRatio) { |
| 242 | 0 | boxHeight = containerHeight; |
| 243 | 0 | boxWidth = (int) (width * heightRatio); |
| 244 | |
} else { |
| 245 | 0 | boxHeight = (int) (height * widthRatio); |
| 246 | 0 | boxWidth = containerWidth; |
| 247 | |
} |
| 248 | 0 | } else { |
| 249 | 0 | double widthRatio = (float) containerWidth / width; |
| 250 | 0 | boxHeight = (int) (height * widthRatio); |
| 251 | 0 | boxWidth = containerWidth; |
| 252 | 0 | } |
| 253 | |
} else { |
| 254 | 0 | double widthRatio = (float) containerWidth / width; |
| 255 | 0 | double heightRatio = (float) containerHeight / height; |
| 256 | 0 | if (widthRatio > heightRatio) { |
| 257 | 0 | boxHeight = containerHeight; |
| 258 | 0 | boxWidth = (int) (width * heightRatio); |
| 259 | |
} else { |
| 260 | 0 | boxHeight = (int) (height * widthRatio); |
| 261 | 0 | boxWidth = containerWidth; |
| 262 | |
} |
| 263 | |
} |
| 264 | 0 | return new Rectangle(0, 0, boxWidth, boxHeight); |
| 265 | |
} |
| 266 | |
} |