Changes for page Development-Thumbnailing

Last modified by Pascal Robert on 2010/09/19 10:27

From version 4.1
edited by smmccraw
on 2007/07/08 09:46
Change comment: There is no comment for this version
To version 6.1
edited by Pascal Robert
on 2007/09/03 15:17
Change comment: There is no comment for this version

Summary

Details

Page properties
Title
... ... @@ -1,1 +1,1 @@
1 -Programming__WebObjects-Web Applications-Development-Thumbnailing
1 +Web Applications-Development-Thumbnailing
Author
... ... @@ -1,1 +1,1 @@
1 -XWiki.smmccraw
1 +XWiki.probert
Content
... ... @@ -1,4 +1,4 @@
1 -== Overview ==
1 +== Overview ==
2 2  
3 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 4  
... ... @@ -8,420 +8,418 @@
8 8  * On many platforms, you can run [[ImageMagick>>http://imagemagick.org/script/index.php]] and use Runtime.exec to call the commandline "convert"
9 9  * On many platforms, you can build and run [[JMagick>>http://www.yeo.id.au/jmagick/]], a Java JNI wrapper around ImageMagick
10 10  
11 -== ImageMagick ==
11 +== ImageMagick ==
12 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).
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 14  
15 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 16  
17 -{{panel}}
17 +{{code}}
18 18  
19 - /* ImageMagickUtil.java created by jrochkind on Thu 24-Apr-2003 */
20 -
21 - import com.webobjects.foundation.*;
22 - import com.webobjects.eocontrol.*;
23 - import com.webobjects.eoaccess.*;
24 - import com.webobjects.appserver.*;
25 -
26 - import java.io.IOException;
27 - import java.io.InputStream;
28 - import java.io.InputStreamReader;
29 - import java.io.BufferedReader;
30 -
31 - /* Utility methods that deal with images by calling the ImageMagick software
32 - as an external process */
33 -
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 - }
19 +/* ImageMagickUtil.java created by jrochkind on Thu 24-Apr-2003 */
20 +
21 +import com.webobjects.foundation.*;
22 +import com.webobjects.eocontrol.*;
23 +import com.webobjects.eoaccess.*;
24 +import com.webobjects.appserver.*;
25 +
26 +import java.io.IOException;
27 +import java.io.InputStream;
28 +import java.io.InputStreamReader;
29 +import java.io.BufferedReader;
30 +
31 +/* Utility methods that deal with images by calling the ImageMagick software
32 +as an external process */
33 +
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 + }
346 346   }
347 347  
348 -{{/panel}}
60 + protected static final String imIdentifyCommand = "identify";
61 + protected static final String imConvertCommand = "convert";
349 349  
350 -== JAI example ==
63 + public static ImageProperties getImageSize(String filePath) throws IMException {
64 + return getImageSize( new java.io.File(filePath) );
65 + }
351 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.//
67 + public static ImageProperties getImageSize(java.io.File imageFile) throws IMException {
68 + String filePathToImage = imageFile.getPath();
353 353  
354 -{{panel}}
70 + String[] cmdArray = new String[] {
71 + imUtilLocationPath + imIdentifyCommand,
72 + "-format", "%w\n%h", // [width][newline][height]
73 + filePathToImage
74 + };
355 355  
356 - /* ImageResizer.java */
357 -
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 - }
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 + }
423 423   }
424 424  
425 -{{/panel}}
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 + }*/
426 426  
427 -Category:WebObjects
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 + }
346 +}
347 +
348 +{{/code}}
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 +
354 +{{code}}
355 +
356 +/* ImageResizer.java */
357 +
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() == 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 + }
423 +}
424 +
425 +{{/code}}