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}