2 * Copyright (C) 2005 The Mir-coders group
\r
4 * This file is part of Mir.
\r
6 * Mir is free software; you can redistribute it and/or modify
\r
7 * it under the terms of the GNU General Public License as published by
\r
8 * the Free Software Foundation; either version 2 of the License, or
\r
9 * (at your option) any later version.
\r
11 * Mir is distributed in the hope that it will be useful,
\r
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
14 * GNU General Public License for more details.
\r
16 * You should have received a copy of the GNU General Public License
\r
17 * along with Mir; if not, write to the Free Software
\r
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
\r
20 * In addition, as a special exception, The Mir-coders gives permission to link
\r
21 * the code of this program with any library licensed under the Apache Software License.
\r
22 * You must obey the GNU General Public License in all respects for all of the code used
\r
23 * other than the above mentioned libraries. If you modify this file, you may extend this
\r
24 * exception to your version of the file, but you are not obligated to do so.
\r
25 * If you do not wish to do so, delete this exception statement from your version.
\r
27 package mir.media.image;
\r
29 import mir.config.MirPropertiesConfiguration;
\r
30 import mir.log.LoggerWrapper;
\r
31 import mir.media.MediaExc;
\r
32 import mir.util.ShellRoutines;
\r
33 import mir.util.StreamCopier;
\r
35 import java.io.BufferedOutputStream;
\r
36 import java.io.File;
\r
37 import java.io.FileInputStream;
\r
38 import java.io.FileNotFoundException;
\r
39 import java.io.FileOutputStream;
\r
40 import java.io.IOException;
\r
41 import java.io.InputStream;
\r
42 import java.io.OutputStream;
\r
43 import java.util.StringTokenizer;
\r
47 * Image processing by calling the ImageMagick command line progrmas
\r
48 * "convert" and "identify". The main task of this class is to scale
\r
49 * images. The path to ImageMagick commandline programs can be
\r
50 * specified in the configuration file.
\r
52 * @author <grok@no-log.org>, the Mir-coders group
\r
54 public class ImageMagickImageProcessor implements ImageProcessor {
\r
55 protected static MirPropertiesConfiguration configuration =
\r
56 MirPropertiesConfiguration.instance();
\r
57 static final LoggerWrapper logger =
\r
58 new LoggerWrapper("media.image.imagemagick");
\r
60 private ImageFile sourceImage;
\r
61 private ImageFile scaledImage;
\r
63 public ImageMagickImageProcessor(InputStream inputImageStream)
\r
64 throws IOException {
\r
65 logger.debug("ImageMagickImageProcessor(stream)");
\r
66 sourceImage = new ImageFile();
\r
67 // copy stream into temporary file
\r
69 FileOutputStream outputStream = new FileOutputStream(sourceImage.file);
\r
71 StreamCopier.copy(inputImageStream, outputStream);
\r
74 outputStream.close();
\r
76 sourceImage.readInfo();
\r
77 sourceImage.debugOutput();
\r
81 public ImageMagickImageProcessor(File aFile) throws IOException {
\r
82 logger.debug("ImageMagickImageProcessor(file)");
\r
83 sourceImage = new ImageFile(aFile);
\r
84 sourceImage.debugOutput();
\r
88 * Deletes temporary files
\r
90 public void cleanup() {
\r
91 logger.debug("ImageMagickImageProcessor.cleanup()");
\r
92 sourceImage.cleanup();
\r
93 scaledImage.cleanup();
\r
97 * Path to ImageMagick commandline programs
\r
99 private static String getImageMagickPath() {
\r
100 String result = configuration.getString("Producer.Image.ImageMagickPath");
\r
101 // we want the path to finish by "/", so add it if it's missing
\r
102 if (result.length() != 0 && !result.endsWith("/")) {
\r
103 result = result.concat("/");
\r
105 logger.debug("getImageMagickPath:" + result);
\r
109 public void descaleImage(int aMaxSize) throws MediaExc {
\r
110 descaleImage(aMaxSize, 0);
\r
113 public void descaleImage(int aMaxSize, float aMinDescale) throws MediaExc {
\r
114 descaleImage(aMaxSize, aMaxSize, aMinDescale, 0);
\r
117 public void descaleImage(int aMaxSize, int aMinResize) throws MediaExc {
\r
118 descaleImage(aMaxSize, aMaxSize, 0, aMinResize);
\r
121 public void descaleImage(int aMaxSize, float aMinDescale, int aMinResize)
\r
123 descaleImage(aMaxSize, aMaxSize, aMinDescale, aMinResize);
\r
129 public void descaleImage(int aMaxWidth, int aMaxHeight,
\r
130 float aMinDescale, int aMinResize) throws MediaExc {
\r
132 logger.debug("descaleImage:" +
\r
133 " aMaxWidth:" + aMaxWidth +
\r
134 ", aMaxHeight:" + aMaxHeight +
\r
135 ", aMinDescale:" + aMinDescale +
\r
136 ", aMinResize:" + aMinResize);
\r
137 if ((aMaxWidth > 0 && getWidth() > aMaxWidth + aMinResize - 1) ||
\r
138 (aMaxHeight > 0 && getHeight() > aMaxHeight + aMinResize - 1)) {
\r
139 logger.debug("descaleImage: image needs scaling");
\r
143 if (aMaxWidth > 0 && getWidth() > aMaxWidth) {
\r
144 scale = Math.min(scale, (float) aMaxWidth / (float) getWidth());
\r
146 if (aMaxHeight > 0 && getHeight() > aMaxHeight) {
\r
147 scale = Math.min(scale, (float) aMaxHeight / (float) getHeight());
\r
150 if (1 - scale > aMinDescale) {
\r
157 // the image didn't need to be scaled: scaledImage = original image
\r
159 scaledImage = new ImageFile(sourceImage.file);
\r
161 catch (IOException e) {
\r
162 throw new MediaExc(e.toString());
\r
169 * Scales image by a factor using the convert ImageMagick command
\r
171 public void scaleImage(float aScalingFactor) throws MediaExc {
\r
172 logger.debug("scaleImage:" + aScalingFactor);
\r
174 // first cleanup previous temp scaledimage file if necesary
\r
175 if (scaledImage != null) {
\r
176 scaledImage.cleanup();
\r
178 // now create temp file and execute convert
\r
179 scaledImage = sourceImage.scale(aScalingFactor);
\r
181 catch (Exception e) {
\r
182 throw new MediaExc(e.toString());
\r
184 logger.debug(" scaledImage:");
\r
185 scaledImage.debugOutput();
\r
188 public int getWidth() {
\r
189 return sourceImage.width;
\r
192 public int getHeight() {
\r
193 return sourceImage.height;
\r
196 public int getScaledWidth() {
\r
197 return scaledImage.width;
\r
200 public int getScaledHeight() {
\r
201 return scaledImage.height;
\r
204 public int getScaledFileSize() {
\r
205 return scaledImage.fileSize;
\r
208 public void writeScaledData(OutputStream aStream, String anImageType) throws MediaExc, IOException {
\r
209 // we can't asume that requested "anImageType" is the same as the
\r
210 // scaled image type, so we have to convert
\r
211 // if image is an animation and target type doesn't support
\r
212 // animations, then just use first frame
\r
214 scaledImage.debugOutput();
\r
216 if (scaledImage.isAnimation && !anImageType.equals("GIF")) {
\r
219 // ImageMagick "convert" into temp file
\r
220 File temp = File.createTempFile("mirimage", "");
\r
221 String command = getImageMagickPath() + "convert " +
\r
222 scaledImage.file.getAbsolutePath() + frame + " " +
\r
223 anImageType + ":" + temp.getAbsolutePath();
\r
224 logger.debug("writeScaledData command:" + command);
\r
225 ShellRoutines.simpleExec(command);
\r
226 // copy temp file into stream
\r
227 StreamCopier.copy(new FileInputStream(temp), aStream);
\r
231 public void writeScaledData(File aFile, String anImageType) throws MediaExc, IOException, FileNotFoundException {
\r
232 OutputStream stream = new BufferedOutputStream(new FileOutputStream(aFile), 8192);
\r
235 writeScaledData(stream, anImageType);
\r
241 catch (Throwable t) {
\r
247 * ImageFile is a thin wrapper around a file that contains an
\r
248 * image. It uses ImageMagick to retreive information about the
\r
249 * image. It can also scale images using ImageMagick. Intended for
\r
250 * use in the ImageMagickImageProcessor class.
\r
252 static class ImageFile {
\r
254 * path to the file represented by this class
\r
258 * whether the file must be deleted on cleanup
\r
260 boolean fileIsTemp = false;
\r
262 * image information is stored here to avoid multiple costly calls to
\r
270 * Image type as returned by identify %m : "PNG", "GIF", ...
\r
274 * number of scenes in image >1 (typically animated gif)
\r
276 boolean isAnimation;
\r
279 * Empty constructor automatically creates a temporary file
\r
280 * that will later hold an image
\r
282 ImageFile() throws IOException {
\r
283 file = File.createTempFile("mirimage", "");
\r
288 * if the file doesn't already have an image in it
\r
289 * we don't want to read its information
\r
291 ImageFile(File file, boolean doReadInfo) throws IOException {
\r
298 ImageFile(File file) throws IOException {
\r
303 * delete temporary files
\r
305 public void cleanup() {
\r
306 logger.debug("ImageFile.cleanup()");
\r
308 logger.debug("deleting:" + file);
\r
311 fileIsTemp = false;
\r
315 void debugOutput() {
\r
316 logger.debug(" filename:" + file +
\r
318 " width:" + width +
\r
319 " height:" + height +
\r
321 " isAnimation:" + isAnimation);
\r
324 private void checkFile() throws IOException {
\r
325 if (file == null || !file.exists()) {
\r
326 String message = "ImageFile.checkFile file \"" + file +
\r
327 "\" does not exist";
\r
328 logger.error(message);
\r
329 throw new IOException(message);
\r
334 * Uses the imagemagick "identify" command to retreive image information
\r
336 public void readInfo() throws IOException {
\r
338 String infoString = ShellRoutines.execIntoString
\r
339 (getImageMagickPath() +
\r
340 "identify " + "-format \"%w %h %m %n \" " +
\r
341 file.getAbsolutePath()); // extra space, for multiframe (animations)
\r
342 StringTokenizer st = new StringTokenizer(infoString);
\r
343 width = Integer.parseInt(st.nextToken());
\r
344 height = Integer.parseInt(st.nextToken());
\r
345 type = st.nextToken();
\r
346 isAnimation = Integer.parseInt(st.nextToken()) > 1;
\r
347 fileSize = (int)file.length();
\r
350 public ImageFile scale(float aScalingFactor) throws IOException {
\r
351 logger.debug("ImageFile.scale");
\r
354 ImageFile result = new ImageFile();
\r
355 String command = getImageMagickPath() + "convert " +
\r
356 file.getAbsolutePath() + " " +
\r
358 Float.toString(aScalingFactor * 100) + "% " +
\r
359 result.file.getAbsolutePath();
\r
360 logger.debug("ImageFile.scale:command:" + command);
\r
361 ShellRoutines.simpleExec(command);
\r