2 * Copyright (C) 2001, 2002 The Mir-coders group
4 * This file is part of Mir.
6 * Mir is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * Mir is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Mir; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * In addition, as a special exception, The Mir-coders gives permission to link
21 * the code of this program with any library licensed under the Apache Software License,
22 * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library
23 * (or with modified versions of the above that use the same license as the above),
24 * and distribute linked combinations including the two. You must obey the
25 * GNU General Public License in all respects for all of the code used other than
26 * the above mentioned libraries. If you modify this file, you may extend this
27 * exception to your version of the file, but you are not obligated to do so.
28 * If you do not wish to do so, delete this exception statement from your version.
31 package mir.media.image;
33 import mir.log.LoggerWrapper;
34 import mir.media.MediaExc;
35 import mir.media.MediaFailure;
36 import mir.util.StreamCopier;
37 import mir.util.ExecFunctions;
38 import mir.config.MirPropertiesConfiguration;
41 import java.util.StringTokenizer;
46 * Image processing by calling the ImageMagick command line progrmas
47 * "convert" and "identify". The main task of this class is to scale
48 * images. The path to ImageMagick commandline programs can be
49 * specified in the coonfiguration file.
50 * @author <grok@no-log.org>, the Mir-coders group
52 public class ImageMagickImageProcessor implements ImageProcessor
54 protected static MirPropertiesConfiguration configuration =
55 MirPropertiesConfiguration.instance();
56 static final LoggerWrapper logger =
57 new LoggerWrapper("media.image.imagemagick");
59 private ImageFile sourceImage;
60 private ImageFile scaledImage;
63 * ImageFile is a thin wrapper around a file that contains an
64 * image. It uses ImageMagick to retreive information about the
65 * image. It can also scale images using ImageMagick. Intended for
66 * use in the ImageMagickImageProcessor class.
68 static class ImageFile
70 /** path to the file represented by this class */
72 /** whether the file must be deleted on cleanup */
73 boolean fileIsTemp=false;
74 /** image information is stored here to avoid multiple costly calls to
78 /** Image type as returned by identify %m : "PNG", "GIF", ... */
80 /** number of scenes in image >1 (typically animated gif) */
83 /** Empty constructor automatically creates a temporary file
84 * that will later hold an image */
85 ImageFile() throws IOException
87 file=File.createTempFile("mirimage","");
90 /** if the file doesn't already have an image in it
91 * we don't want to read its information */
92 ImageFile(File file,boolean doReadInfo) throws IOException
95 if(doReadInfo){readInfo();}
97 ImageFile(File file) throws IOException
101 /** delete temporary files */
102 public void cleanup()
104 logger.debug("ImageFile.cleanup()");
107 logger.debug("deleting:"+file);
115 logger.debug(" filename:"+file+
120 " isAnimation:"+isAnimation);
122 private void checkFile() throws IOException
124 if(file==null || !file.exists())
126 String message="ImageFile.checkFile file \""+file+
128 logger.error(message);
129 throw new IOException(message);
133 /** Uses the imagemagick "identify" command to retreive image information */
134 public void readInfo() throws IOException
137 String infoString=ExecFunctions.execIntoString
138 (getImageMagickPath()+
140 file.getAbsolutePath()+" "+
141 "-format \"%w %h %m %n \"");// extra space, for multiframe (animations)
142 StringTokenizer st = new StringTokenizer(infoString);
143 width =Integer.parseInt(st.nextToken());
144 height =Integer.parseInt(st.nextToken());
145 type = st.nextToken() ;
146 isAnimation=Integer.parseInt(st.nextToken())>1;
149 public ImageFile scale(float aScalingFactor) throws IOException
151 logger.debug("ImageFile.scale");
153 ImageFile result=new ImageFile();
154 String command=getImageMagickPath()+"convert "+
155 file.getAbsolutePath()+" "+
157 new Float(aScalingFactor*100).toString()+"% "+
158 result.file.getAbsolutePath();
159 logger.debug("ImageFile.scale:command:"+command);
160 ExecFunctions.simpleExec(command);
166 public ImageMagickImageProcessor(InputStream inputImageStream)
169 logger.debug("ImageMagickImageProcessor(stream)");
170 sourceImage=new ImageFile();
171 // copy stream into temporary file
172 StreamCopier.copy(inputImageStream,new FileOutputStream(sourceImage.file));
173 sourceImage.readInfo();
174 sourceImage.debugOutput();
178 public ImageMagickImageProcessor(File aFile) throws IOException
180 logger.debug("ImageMagickImageProcessor(file)");
181 sourceImage=new ImageFile(aFile);
182 sourceImage.debugOutput();
186 * Deletes temporary files
188 public void cleanup()
190 logger.debug("ImageMagickImageProcessor.cleanup()");
191 sourceImage.cleanup();
192 scaledImage.cleanup();
196 * Path to ImageMagick commandline programs
198 private static String getImageMagickPath()
200 String result=configuration.getString("Producer.Image.ImageMagickPath");
201 // we want the path to finish by "/", so add it if it's missing
202 if(result.length()!=0 && !result.endsWith("/"))
204 result=result.concat("/");
206 logger.debug("getImageMagickPath:"+result);
210 public void descaleImage(int aMaxSize) throws MediaExc {
211 descaleImage(aMaxSize, 0);
214 public void descaleImage(int aMaxSize, float aMinDescale) throws MediaExc {
215 descaleImage(aMaxSize, aMaxSize, aMinDescale, 0);
218 public void descaleImage(int aMaxSize, int aMinResize) throws MediaExc
220 descaleImage(aMaxSize, aMaxSize, 0, aMinResize);
223 public void descaleImage(int aMaxSize, float aMinDescale, int aMinResize)
226 descaleImage(aMaxSize, aMaxSize, aMinDescale, aMinResize);
230 public void descaleImage(int aMaxWidth, int aMaxHeight,
231 float aMinDescale, int aMinResize) throws MediaExc
234 logger.debug("descaleImage:"+
235 " aMaxWidth:"+aMaxWidth+
236 ", aMaxHeight:"+aMaxHeight+
237 ", aMinDescale:"+aMinDescale+
238 ", aMinResize:"+aMinResize);
239 if ((aMaxWidth >0 && getWidth ()>aMaxWidth +aMinResize-1) ||
240 (aMaxHeight>0 && getHeight()>aMaxHeight+aMinResize-1))
242 logger.debug("descaleImage: image needs scaling");
246 if (aMaxWidth>0 && getWidth()>aMaxWidth)
248 scale = Math.min(scale, (float) aMaxWidth / (float) getWidth());
250 if (aMaxHeight>0 && getHeight()>aMaxHeight)
252 scale = Math.min(scale, (float) aMaxHeight / (float) getHeight());
255 if (1-scale>aMinDescale)
262 logger.debug("descaleImage: image size is ok, not scaling image");
263 try{scaledImage=new ImageFile(sourceImage.file);}
264 catch(IOException e){throw new MediaExc(e.toString());}
270 * Scales image by a factor using the convert ImageMagick command
272 public void scaleImage(float aScalingFactor)
275 logger.debug("scaleImage:"+aScalingFactor);
278 // first cleanup previous temp scaledimage file if necesary
279 if(scaledImage!=null){scaledImage.cleanup();}
280 // now create temp file and execute convert
281 scaledImage=sourceImage.scale(aScalingFactor);
283 catch(Exception e){throw new MediaExc(e.toString());}
284 logger.debug(" scaledImage:");
285 scaledImage.debugOutput();
288 public int getWidth() {
289 return sourceImage.width;
292 public int getHeight() {
293 return sourceImage.height;
296 public int getScaledWidth() {
297 return scaledImage.width;
300 public int getScaledHeight() {
301 return scaledImage.height;
304 public void writeScaledData(OutputStream aStream, String anImageType)
307 // we can't asume that requested "anImageType" is the same as the
308 // scaled image type, so we have to convert
311 // if image is an animation and target type doesn't support
312 // animations, then just use first frame
314 scaledImage.debugOutput();
315 if(scaledImage.isAnimation && !anImageType.equals("GIF"))
319 // ImageMagick "convert" into temp file
320 File temp=File.createTempFile("mirimage","");
321 String command=getImageMagickPath()+"convert "+
322 scaledImage.file.getAbsolutePath()+frame+" "+
323 anImageType+":"+temp.getAbsolutePath();
324 logger.debug("writeScaledData command:"+command);
325 ExecFunctions.simpleExec(command);
326 // copy temp file into stream
327 StreamCopier.copy(new FileInputStream(temp),aStream);
330 catch(Exception e){throw new MediaExc(e.toString());}
333 public byte[] getScaledData(String anImageType) throws MediaExc
335 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
336 writeScaledData(outputStream, anImageType);
337 return outputStream.toByteArray();
340 public void writeScaledData(File aFile, String anImageType) throws MediaExc {
342 writeScaledData(new BufferedOutputStream
343 (new FileOutputStream(aFile),8192), anImageType);
345 catch (FileNotFoundException f) {
346 throw new MediaFailure(f);