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
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
on 2007/09/03 15:17
Change comment:
There is no comment for this version
Summary
-
Page properties (3 modified, 0 added, 0 removed)
Details
- Page properties
-
- Title
-
... ... @@ -1,1 +1,1 @@ 1 - Programming__WebObjects-WebApplications-Development-Thumbnailing1 +Web Applications-Development-Thumbnailing - Author
-
... ... @@ -1,1 +1,1 @@ 1 -XWiki. smmccraw1 +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}}