001package com.hfg.image; 002 003import java.awt.*; 004import java.io.File; 005import java.io.IOException; 006import java.io.InputStream; 007 008import com.hfg.util.Executor; 009import com.hfg.util.io.StreamUtil; 010 011//------------------------------------------------------------------------------ 012/** 013 * Wrapper for ImageMagick tools. 014 * 015 * @author J. Alex Taylor, hairyfatguy.com 016 */ 017//------------------------------------------------------------------------------ 018// com.hfg XML/HTML Coding Library 019// 020// This library is free software; you can redistribute it and/or 021// modify it under the terms of the GNU Lesser General Public 022// License as published by the Free Software Foundation; either 023// version 2.1 of the License, or (at your option) any later version. 024// 025// This library is distributed in the hope that it will be useful, 026// but WITHOUT ANY WARRANTY; without even the implied warranty of 027// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 028// Lesser General Public License for more details. 029// 030// You should have received a copy of the GNU Lesser General Public 031// License along with this library; if not, write to the Free Software 032// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 033// 034// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 035// jataylor@hairyfatguy.com 036//------------------------------------------------------------------------------ 037 038public class ImageMagick 039{ 040 private int mJpgQuality = 100; 041 042 private static File sBinDir = new File("/opt/local/bin"); 043 private static File sConvertExe; 044 private static File sIdentifyExe; 045 046 047 private static String CORNER_ADD = "corner_add.png"; 048 private static String CORNER_SUB = "corner_sub.png"; 049 050 //########################################################################### 051 // PUBLIC FUNCTIONS 052 //########################################################################### 053 054 //-------------------------------------------------------------------------- 055 /** 056 Sets the location of the ImageMagick bin dir. Defaults to '/opt/local/bin'. 057 * @param inValue 058 */ 059 public static void setBinDir(File inValue) 060 { 061 sBinDir = inValue; 062 } 063 064 //-------------------------------------------------------------------------- 065 public static File getBinDir() 066 { 067 return sBinDir; 068 } 069 070 //-------------------------------------------------------------------------- 071 public ImageMagick setJpgQuality(int inValue) 072 { 073 mJpgQuality = inValue; 074 return this; 075 } 076 077 //-------------------------------------------------------------------------- 078 public int getJpgQuality() 079 { 080 return mJpgQuality; 081 } 082 083 //-------------------------------------------------------------------------- 084 public Dimension getImageDimensions(File inImageFile) 085 { 086 StringBuilder cmd = new StringBuilder(); 087 cmd.append(getIdentifyExe()); 088 cmd.append(" -format '%wx%h'"); 089 cmd.append(" '" + inImageFile + "'"); 090 091 Executor executor = new Executor(cmd.toString()); 092 int exitStatus = executor.exec(); 093 if (exitStatus != 0) 094 { 095 throw new RuntimeException("Problem getting dimensions of '" + inImageFile + "':\n" 096 + "COMMAND: '" + cmd.toString() + "'\n" 097 + executor.getSTDERR()); 098 } 099 100 String[] pieces = executor.getSTDOUT().trim().split("x", 2); 101 102 return new Dimension(Integer.parseInt(pieces[0]), Integer.parseInt(pieces[1])); 103 } 104 105 //-------------------------------------------------------------------------- 106 public void scaleImage(File inOrigImg, int inMaxDimension, File inScaledImg) 107 { 108 StringBuilder cmd = new StringBuilder(); 109 cmd.append(getConvertExe()); 110 if (inMaxDimension > 0) 111 { 112 cmd.append(" -size '" + (inMaxDimension * 2) + "x" + (inMaxDimension * 2) + "'"); 113 } 114 115 cmd.append(" '" + inOrigImg + "'"); 116 117 if (ImageFormat.isJPEG(inScaledImg)) cmd.append(" -quality " + mJpgQuality); 118 119 if (inMaxDimension > 0) 120 { 121 cmd.append(" -thumbnail '" + inMaxDimension + "x" + inMaxDimension + ">'"); 122 } 123 cmd.append(" '" + inScaledImg + "'"); 124 125 Executor executor = new Executor(cmd.toString()); 126 int exitStatus = executor.exec(); 127 if (exitStatus != 0) 128 { 129 throw new RuntimeException("Problem creating scaled image of '" + inOrigImg + "':\n" 130 + "COMMAND: '" + cmd.toString() + "'\n" 131 + executor.getSTDERR()); 132 } 133 } 134 135 //-------------------------------------------------------------------------- 136 public void convertImage(File inOrigImg, File inDestImg) 137 { 138 StringBuilder cmd = new StringBuilder(); 139 cmd.append(getConvertExe()); 140 141 cmd.append(" '" + inOrigImg + "'"); 142 143 if (ImageFormat.isJPEG(inDestImg)) cmd.append(" -quality " + mJpgQuality); 144 145 cmd.append(" '" + inDestImg + "'"); 146 147 Executor executor = new Executor(cmd.toString()); 148 int exitStatus = executor.exec(); 149 if (exitStatus != 0) 150 { 151 throw new RuntimeException("ImageMagick problem w/ command: '" + cmd.toString() + "'\n" 152 + executor.getSTDERR()); 153 } 154 } 155 156 //-------------------------------------------------------------------------- 157 /** 158 Addes a white matte and a 1-pixel border. 159 */ 160 public void addMatte(File inImageFile, int inMatteWidth) 161 { 162 StringBuilder cmd = new StringBuilder(); 163 cmd.append(getConvertExe()); 164 cmd.append(" '" + inImageFile + "'"); 165 cmd.append(" -bordercolor white -border " + (inMatteWidth - 1)); 166 cmd.append(" -bordercolor grey -border 1"); 167 cmd.append(" '" + inImageFile + "'"); 168 169 Executor executor = new Executor(cmd.toString()); 170 int exitStatus = executor.exec(); 171 if (exitStatus != 0) 172 { 173 throw new RuntimeException("Problem adding matte to image '" + inImageFile + ":\n" 174 + executor.getSTDERR()); 175 } 176 } 177 178 //-------------------------------------------------------------------------- 179 public void addImageShadow(File inImageFile) 180 { 181 addImageShadow(inImageFile, inImageFile); 182 } 183 184 //-------------------------------------------------------------------------- 185 public void addImageShadow(File inImageFile, File inOutputImageFile) 186 { 187 StringBuilder cmd = new StringBuilder(); 188 cmd.append(getConvertExe()); 189 cmd.append(" -page +2+2"); 190 cmd.append(" '" + inImageFile + "'"); 191 cmd.append(" \\( +clone -background black -shadow 60x4+4+4 \\)"); 192// cmd.append(" +swap -background none -mosaic -depth 8 -colors 256 -quality 95"); 193 cmd.append(" +swap -background none -mosaic -quality 95"); 194 cmd.append(" '" + inOutputImageFile + "'"); 195 196 Executor executor = new Executor(cmd.toString()); 197 int exitStatus = executor.exec(); 198 if (exitStatus != 0) 199 { 200 throw new RuntimeException("Problem adding shadow to image '" + inImageFile + ":\n" 201 + executor.getSTDERR()); 202 } 203 } 204 205 //-------------------------------------------------------------------------- 206 public void roundImageCorners(File inImageFile) 207 { 208 roundImageCorners(inImageFile, inImageFile); 209 } 210 211 //-------------------------------------------------------------------------- 212 public void roundImageCorners(File inImageFile, File inOutputImageFile) 213 { 214 File cornerAddFile = stageCornerResourceImage(CORNER_ADD, inOutputImageFile.getParentFile()); 215 File cornerSubFile = stageCornerResourceImage(CORNER_SUB, inOutputImageFile.getParentFile()); 216 217 StringBuilder cmd = new StringBuilder(); 218 cmd.append(getConvertExe()); 219 cmd.append(" '" + inImageFile + "'"); 220 221 cmd.append(" -bordercolor black -border 1"); 222 cmd.append(" -matte \\( '" + cornerAddFile.getPath() + "' -flip -flop \\)"); 223 cmd.append(" -gravity NorthWest"); 224 cmd.append(" -composite \\( '" + cornerAddFile.getPath() + "' -flop \\)"); 225 cmd.append(" -gravity SouthWest"); 226 cmd.append(" -composite \\( '" + cornerAddFile.getPath() + "' -flip \\)"); 227 cmd.append(" -gravity NorthEast"); 228 cmd.append(" -composite \\( '" + cornerAddFile.getPath() + "' \\)"); 229 cmd.append(" -gravity SouthEast"); 230 cmd.append(" -composite"); 231 cmd.append(" -compose DstOut \\( '" + cornerSubFile.getPath() + "' -flip -flop \\)"); 232 cmd.append(" -gravity NorthWest"); 233 cmd.append(" -composite \\( '" + cornerSubFile.getPath() + "' -flop \\)"); 234 cmd.append(" -gravity SouthWest"); 235 cmd.append(" -composite \\( '" + cornerSubFile.getPath() + "' -flip \\)"); 236 cmd.append(" -gravity NorthEast"); 237 cmd.append(" -composite \\( '" + cornerSubFile.getPath() + "' \\)"); 238 cmd.append(" -gravity SouthEast"); 239 cmd.append(" -composite"); 240// cmd.append(" -depth 8 -colors 256 -quality 95"); 241 cmd.append(" '" + inOutputImageFile + "'"); 242 243 Executor executor = new Executor(cmd.toString()); 244 245 int exitStatus = executor.exec(); 246 if (exitStatus != 0) 247 { 248 throw new RuntimeException("Problem rounding corners of image '" + inImageFile + "':\n" 249 + executor.getSTDERR()); 250 } 251 } 252 253 254 //########################################################################### 255 // PRIVATE FUNCTIONS 256 //########################################################################### 257 258 //-------------------------------------------------------------------------- 259 private static File getIdentifyExe() 260 { 261 if (null == sIdentifyExe) 262 { 263 sIdentifyExe = new File(sBinDir, "identify"); 264 } 265 266 return sIdentifyExe; 267 } 268 269 //-------------------------------------------------------------------------- 270 private static File getConvertExe() 271 { 272 if (null == sConvertExe) 273 { 274 sConvertExe = new File(sBinDir, "convert"); 275 } 276 277 return sConvertExe; 278 } 279 280 //-------------------------------------------------------------------------- 281 private static File stageCornerResourceImage(String inResource, File inTempDir) 282 { 283 File file; 284 try 285 { 286 InputStream stream = ImageMagick.class.getResourceAsStream("rsrc/" + inResource); 287 if (null == stream) 288 { 289 throw new RuntimeException("Resource '" + inResource + "' couldn't be found!"); 290 } 291 292 file = new File(inTempDir, inResource); 293 file.deleteOnExit(); 294 StreamUtil.writeToFile(stream, file); 295 } 296 catch (IOException e) 297 { 298 throw new RuntimeException("Problem staging corner image resource!", e); 299 } 300 301 return file; 302 } 303 304}