Version 5.1 by smmccraw on 2007/07/08 09:46

Hide last authors
Quinton Dolan 2.1 1 == Overview ==
2
3 Many people have asked how to create thumbnails of images using WebObjects. There is nothing specific to WebObjects about this problem, and there are quite a few options for solving it:
4
5 * On Mac OS X, you can use Runtime.exec(..) and call the commandline program "sips," which uses the ImageIO and CoreImage frameworks
6 * On Mac OS X, you can use Brendan Duddridge's [[ImageIO JNI Wrapper>>http://wocode.com/cgi-bin/WebObjects/WOCode.woa/wa/ShareCodeItem?itemId=430]]
7 * An all platforms, you can use Java2D with Java's ImageIO and BufferedImage
8 * On many platforms, you can run [[ImageMagick>>http://imagemagick.org/script/index.php]] and use Runtime.exec to call the commandline "convert"
9 * On many platforms, you can build and run [[JMagick>>http://www.yeo.id.au/jmagick/]], a Java JNI wrapper around ImageMagick
10
11 == ImageMagick ==
12
13 A utility class to call ImageMagick binaries as an external process with Runtime.exec, to either discover height/width or resize an image. It has a few default filepaths that correspond to what I need on my systems, you probably want to change them for your system(s).
14
15 //Anjo Krank: I may be wrong, but I'm pretty sure that this code won't work, at least not reliably. There is no guarantee that the the contents of the supplied arrays are filled before the process exits. I had a lot of null-results when I tried this in Wonder. The only safe way I've seen so far is to actually wait until the stream is truly finished reading before accessing the result. And this can only be done by waiting on a sema. Take a look at ERXRuntimeUtilities for a version that does work.//
16
smmccraw 4.1 17 {{panel}}
Quinton Dolan 2.1 18
smmccraw 4.1 19 /* ImageMagickUtil.java created by jrochkind on Thu 24-Apr-2003 */
Quinton Dolan 2.1 20
smmccraw 4.1 21 import com.webobjects.foundation.*;
22 import com.webobjects.eocontrol.*;
23 import com.webobjects.eoaccess.*;
24 import com.webobjects.appserver.*;
Quinton Dolan 2.1 25
smmccraw 4.1 26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.io.BufferedReader;
Quinton Dolan 2.1 30
smmccraw 4.1 31 /* Utility methods that deal with images by calling the ImageMagick software
32 as an external process */
Quinton Dolan 2.1 33
smmccraw 4.1 34 /* Dealing with Runtime.exec in a thread-safe way is tricky!_Sorry_for_that.
35 __I_used_the_article_at
36 __http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html
37 __as_a_guide_for_understanding_how_to_do_it_right._*/
38 __
39 __public_class_ImageMagickUtil_{
40 ____//protected_static_final_String_imUtilLocationPath_=_"C:\Program_Files\ImageMagick-5.5.6-Q8\";
41 ____protected_static_final_String_imUtilLocationPath;//_=_"/export/home/jar247/mtBin/";
42 ____//The_image_util_location_path_can_be_supplied_as_a_Java_property,
43 ____//or_we_can_try_to_guess_it_from_the_OS.
44 ____static_{
45 ________String_locProp_=_System.getProperty("imUtilPath");
46 ________String_osName_=_System.getProperty("os.name");
47 ________if_(_locProp_!= null ) {
48 imUtilLocationPath = locProp;
49 }
50 else if ( osName.indexOf("Windows") !=_-1_)_{
51 ____________imUtilLocationPath_=_"C:\Program_Files\ImageMagick-5.5.6-Q8\";
52 ________}
53 ________else_{
54 ____________//Assume_our_deployment_machine,_which_is_currently_set_up
55 ____________//to_make_this_the_location...
56 ____________imUtilLocationPath_=_"/export/home/webob/ImageMagick/bin/";
57 ________}
58 ____}
59 ____
60 ____protected_static_final_String_imIdentifyCommand_=_"identify";
61 ____protected_static_final_String_imConvertCommand_=_"convert";
62 __
63 ____public_static_ImageProperties_getImageSize(String_filePath)_throws_IMException_{
64 ________return_getImageSize(_new_java.io.File(filePath)_);
65 ____}
66 __
67 ____public_static_ImageProperties_getImageSize(java.io.File_imageFile)_throws_IMException_{
68 ________String_filePathToImage_=_imageFile.getPath();
69 __
70 ________String[)_cmdArray_=_new_String(]_{
71 ____________imUtilLocationPath_+_imIdentifyCommand,
72 ____________"-format",_"%wn%h",_//_[width][newline][height]
73 ____________filePathToImage
74 ________};
75 ________
76 ________NSMutableArray_stdOutContents_=_new_NSMutableArray();
77 ________NSMutableArray_stdErrContents_=_new_NSMutableArray();
78 ________int_resultCode_=_-1;
79 ________try_{
80 ____________resultCode_=_exec(cmdArray,_stdOutContents,_stdErrContents);____________
81 ________}
82 ________catch_(IOException_ioE_)_{
83 ____________//For_some_reason_we_couldn't_exec_the_process! Convert it to an IMException.
84 //One reason this exception is thrown is if the path specified to the im
85 //executable isn't correct.
86 throw new IMException("Could not exec imagemagick process: " + ioE.getMessage(), cmdArray, null);
87 }
88 catch ( InterruptedException intE ) {
89 //re-throw it as an IMException.
90 //This exception should really never be thrown, as far as I know.
91 throw new IMException("imagemagick process interrupted!_"_+_intE.getMessage(),_cmdArray,_null);
92 ________}
93 __
94 ________if_(_resultCode_!= 0 ) {
95 //The external process reports failure!
96 ____________IMException_e_=_new_IMException("Identify_failed! ", cmdArray, stdErrContents);
97 e.exitValue = resultCode;
98 throw e;
99 }
100
101 //Now we need to parse the result line for the height
102 //and width information.
103 if ( stdOutContents.count() >= 2 ) {
104 //First line is width, second is height, because
105 //we asked the imagemagick 'identify' utility to
106 //output like that.
107 String widthStr = (String) stdOutContents.objectAtIndex(0);
108 String heightStr = (String) stdOutContents.objectAtIndex(1);
109 Integer width = new Integer( widthStr );
110 Integer height = new Integer( heightStr );
111
112 ImageProperties p = new ImageProperties();
113 p.width = width;
114 p.height = height;
115 return p;
116 }
117 else {
118 //Umm? Error condition.
119 throw new IMException("Unexpected output of imagemagick process", cmdArray, stdErrContents);
120 }
121 }
122
123 // An external image magick process will be run to resize the image. It's reccomended you
124 // check to make sure it's neccesary to resize the image first!
125 ____//_Beware,_if_the_sizes_you_pass_in_are_LARGER_than_the_existing_size,_the_output_image
126 ____//_WILL_be_BIGGER_than_the_input---this_isn't_just_for_resizing_downward.
127 ____//_Null_outFilePath_means_to_overwrite_the_source_file_path_with_the_resized_image._
128 ____//_You_can_pass_null_for_either_maxWidth_or_maxHeight,_but_not_both,_that_would_be_silly!
129 // [not implemented yet.] Returned is an object telling you the new resized size of the output image.
130 public static void resizeImage(String sourceFilePath, String outFilePath, int maxWidth, int maxHeight)
131 throws IMException {
132 if ( outFilePath == null ) {
133 //overwrite original file if neccesary
134 outFilePath = sourceFilePath;
135 }
136 /*else if ( NSPathUtilities.pathExtension( outFilePath ) == null ) {
137 //give the output file path the same extension as the in file path.
138 outFilePath = NSPathUtilities.stringByAppendingExtension( outFilePath,
139 NSPathUtilities.pathExtension(sourceFilePath));
140 }*/
141
142 StringBuffer dimensionBuffer = new StringBuffer();
143 if ( maxWidth !=_-1)_{
144 ____________dimensionBuffer.append(maxWidth);
145 ________}
146 ________dimensionBuffer.append(_"x"_);
147 ________if_(_maxHeight_!= -1) {
148 dimensionBuffer.append( maxHeight );
149 }
150 String dimensionDirective = dimensionBuffer.toString();
151
152 //We include the ' +profile "*" ' argument to remove
153 //all profiles from the output. Not sure exactly what this means...
154 //but before we were doing this, we wound up with JPGs that
155 //caused problems for IE, for reasons I do not understand.
156 String[) cmdArray = new String(] {
157 imUtilLocationPath + imConvertCommand,
158 "-size", dimensionDirective,
159 sourceFilePath,
160 "-resize", dimensionDirective,
161 "+profile", "*",
162 outFilePath
163 };
164
165 NSMutableArray stdErrContents = new NSMutableArray();
166 NSMutableArray stdOutContents = new NSMutableArray();
167 int resultCode;
168 try {
169 resultCode = exec( cmdArray, stdOutContents, stdErrContents );
170 }
171 catch (IOException ioE ) {
172 //For some reason we couldn't exec the process!_Convert_it_to_an_IMException.
173 ____________//One_reason_this_exception_is_thrown_is_if_the_path_specified_to_the_im
174 ____________//executable_isn't_correct.
175 ____________throw_new_IMException("Could_not_exec_imagemagick_process:_"_+_ioE.getMessage(),_cmdArray,_null);
176 ________}
177 ________catch_(_InterruptedException_intE_)_{
178 ____________//re-throw_it_as_an_IMException.
179 ____________//This_exception_should_really_never_be_thrown,_as_far_as_I_know.
180 ____________throw_new_IMException("imagemagick_process_interrupted! " + intE.getMessage(), cmdArray, null);
181 }
182 if ( resultCode !=_0_)_{
183 ____________//The_external_process_reports_failure!
184 IMException e = new IMException("Conversion failed!_",_cmdArray,_stdErrContents);
185 ____________e.exitValue_=_resultCode;
186 ____________throw_e;
187 ________}________
188 ____}
189 __
190 ____//Invokes_the_external_process._Puts_standard_out_and_standard_error_into_the
191 ____//arrays_given_in_arguments_(they_can_be_null,_in_which_case_the_err/out_stream
192 ____//is_just_thrown_out.
193 ____//Throws_IOException_if_the_exec_of_the_external_process_doesn't_work,_for_instance
194 ____//because_the_path_to_the_command_is_no_good.
195 ____//Throws_the_InterruptedException..._not_sure_when,_if_ever._But_it_means_that_the_exec
196 ____//didn't_work_completely._
197 ____public_static_int_exec(String[]_cmdArray,_NSMutableArray_stdOut,_NSMutableArray_stdErr)_throws_IOException,_InterruptedException_{
198 ________Process_process_=_Runtime.getRuntime().exec(_cmdArray_);________
199 __
200 ________//We_are_interested_in_what_that_process_writes_to_standard
201 ________//output_and_standard_error.
202 ________//Grab_the_contents_of_those_in_their_own_seperate_threads!
203 //To avoid deadlock on the external process thread!
204 __
205 ________StreamGrabber_errGrabber_=_new_StreamGrabber(_process.getErrorStream(),_stdErr_);
206 ________StreamGrabber_outGrabber_=_new_StreamGrabber(_process.getInputStream(),_stdOut_);
207 __
208 ________errGrabber.start();
209 ________outGrabber.start();
210 __
211 ________return_process.waitFor();
212 ____}
213 ____
214 ____
215 ____//Will_launch_a_NEW_THREAD_and_put_the_contents_of_the_given_stream_into
216 ____//the_NSMutableArray._Don't_be_accessing_the_array_in_another_thread_before
217 ____//we're_done!
218 //If array is null, StreamGrabber reads the input stream to completion,
219 //but doesn't store results anywhere.
220 static class StreamGrabber extends Thread
221 {
222 InputStream inputStream;
223 NSMutableArray array;
224 boolean done = false;
225 Exception exceptionEncountered;
226
227
228 StreamGrabber(InputStream is, NSMutableArray a)
229 {
230 super("StreamGrabber");
231 this.inputStream = is;
232 this.array = a;
233 }
234
235 public void run()
236 {
237 try {
238 InputStreamReader isr = new InputStreamReader(inputStream);
239 BufferedReader br = new BufferedReader(isr);
240 String line=null;
241 while ( (line = br.readLine()) !=_null)
242 ____________{
243 ________________if_(_array_!= null ) {
244 array.addObject(line);
245 }
246 }
247 }
248 catch ( java.io.IOException e) {
249 //hmm, what should we do?!?
250 ________________setExceptionEncountered(_e_);
251 ____________}
252 ____________setDone(_true_);
253 ________}
254 ____
255 ________public_synchronized_void_setDone(boolean_v)_{
256 ____________done_=_v;
257 ________}
258 ________//Can_be_used_by_parent_thread_to_see_if_we're_done_yet._
259 ________public_synchronized_boolean_done()_{
260 ____________return_done;
261 ________}
262 ________public_synchronized_Exception_exceptionEncountered()_{
263 ____________return_exceptionEncountered;
264 ________}
265 ________public_synchronized_void_setExceptionEncountered(Exception_e)_{
266 ____________exceptionEncountered_=_e;
267 ________}
268 ________public_boolean_didEncounterException()_{
269 ____________return_exceptionEncountered()_!= null;
270 }
271 }
272
273 //Exception thrown by utilty methods when the call to an external image magick
274 //process failed.
275 public static class IMException extends Exception {
276 protected int exitValue;
277 protected String processErrorMessage;
278 protected String invocationLine;
279 protected String message;
280
281 public IMException() {
282 super();
283 }
284 public IMException(String s) {
285 super();
286 message = s;
287 }
288 //Constructs a long message from all these parts
289 public IMException(String messagePrefix, String[] cmdArray, NSMutableArray stdErr) {
290 super();
291 if ( cmdArray !=_null_)_{
292 ________________invocationLine_=_new_NSArray(_cmdArray_).componentsJoinedByString("_");
293 ____________}
294 ____________if_(_stdErr_!= null ) {
295 processErrorMessage = stdErr.componentsJoinedByString("; ");
296 }
297
298 StringBuffer b = new StringBuffer();
299 b.append( messagePrefix );
300 b.append(". invocation line: ");
301 b.append( invocationLine );
302 b.append(". error output: " );
303 b.append( processErrorMessage );
304 message = b.toString();
305 }
306
307 //the return code from the image magick external invocation.
308 //I think it's probably always 1 in an error condition, so not so useful.
309 public int exitValue() {
310 return exitValue;
311 }
312 //The error message reported by image magick.
313 public String processErrorMessage() {
314 return processErrorMessage;
315 }
316 //The command line used to invoke the external im process that
317 //resulted in an error.
318 public String invocationLine() {
319 return invocationLine;
320 }
321 public void setInvocationLine( String[] cmdArray ) {
322 invocationLine = new NSArray(cmdArray).componentsJoinedByString(" ");
323 }
324 //over-riding
325 public String getMessage() {
326 return message;
327 }
328
329 }
330
331 //Object that encapsulates data returned by an image operation
332 public static class ImageProperties extends Object {
333 protected Integer height;
334 protected Integer width;
335
336 public ImageProperties() {
337 super();
338 }
339 public Integer height() {
340 return height;
341 }
342 public Integer width() {
343 return width;
344 }
345 }
Quinton Dolan 2.1 346 }
347
smmccraw 4.1 348 {{/panel}}
Quinton Dolan 2.1 349
350 == JAI example ==
351
352 An example how to resize an image with Java Advanced Imaging (http:~/~/java.sun.com/products/java-media/jai/). The jar files jai-codec.jar and jac-core.jar are in the NEXT//ROOT/Library/Java/Extensions folder and referenced in the classpath. This example uses logging capability from project wonder.//
353
smmccraw 4.1 354 {{panel}}
Quinton Dolan 2.1 355
smmccraw 4.1 356 /* ImageResizer.java */
Quinton Dolan 2.1 357
smmccraw 4.1 358 import java.awt.image.renderable.ParameterBlock;
359 import java.io.ByteArrayOutputStream;
360 import java.io.IOException;
361 import javax.media.jai.InterpolationNearest;
362 import javax.media.jai.JAI;
363 import javax.media.jai.OpImage;
364 import javax.media.jai.RenderedOp;
365 import com.sun.media.jai.codec.ByteArraySeekableStream;
366 import com.webobjects.foundation.NSData;
367 import er.extensions.ERXLogger;
368
369 public class ImageResizer {
370
371 private static final ERXLogger log = ERXLogger.getERXLogger(ImageResizer.class);
372
373 /**
374 * utility function to resize an image to either maxWidth or maxHeight with jai
375 * example: logo = new NSData(aFileContents1);
376 * logo = ImageResizer.resizeImage(logo, 128, 42);
377 * @param data image content in NSData array
378 * @param maxWidth maxWidth in pixels picture
379 * @param maxHeight maxHeight in pixels of picutre
380 * @return resized array in JPG format if succesfull, null otherwise
381 */
382 static public NSData resizeImage(NSData data, int maxWidth, int maxHeight) {
383 try {
384 ByteArraySeekableStream s = new ByteArraySeekableStream(data
385 .bytes());
386
387 RenderedOp objImage = JAI.create("stream", s);
388 ((OpImage) objImage.getRendering()).setTileCache(null);
389
390 if (objImage.getWidth() h1. 0 || objImage.getHeight() 0) {
391 log.error("graphic size is zero");
392 return null;
393 }
394
395 float xScale = (float) (maxWidth * 1.0) / objImage.getWidth();
396 float yScale = (float) (maxHeight * 1.0) / objImage.getHeight();
397 float scale = xScale;
398 if (xScale > yScale) {
399 scale = yScale;
400 }
401
402 ParameterBlock pb = new ParameterBlock();
403 pb.addSource(objImage); // The source image
404 pb.add(scale); // The xScale
405 pb.add(scale); // The yScale
406 pb.add(0.0F); // The x translation
407 pb.add(0.0F); // The y translation
408 pb.add(new InterpolationNearest()); // The interpolation
409
410 objImage = JAI.create("scale", pb, null);
411
412 ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
413 JAI.create("encode", objImage, out, "JPEG");
414 return new NSData(out.toByteArray());
415 } catch (IOException e) {
416 log.error("io exception " + e);
417 } catch (RuntimeException e) {
418 log.error("runtime exception "+e);
419 }
420
421 return null;
422 }
Quinton Dolan 2.1 423 }
424
smmccraw 4.1 425 {{/panel}}
Quinton Dolan 2.1 426
427 Category:WebObjects