WebObjects/Web 应用程序/开发/缩略图
外观
很多人问过如何使用 WebObjects 创建图像的缩略图。这个问题与 WebObjects 本身无关,而且有很多解决方法。
- 在 Mac OS X 上,您可以使用 Runtime.exec(..) 并调用命令行程序 "sips",它使用 ImageIO 和 CoreImage 框架。
- 在 Mac OS X 上,您可以使用 Brendan Duddridge 的 ImageIO JNI Wrapper。
- 跨所有平台,您可以使用 Java2D 以及 Java 的 ImageIO 和 BufferedImage。
- 在许多平台上,您可以运行 ImageMagick 并使用 Runtime.exec 调用命令行 "convert"。
- 在许多平台上,您可以构建和运行 JMagick,一个围绕 ImageMagick 的 Java JNI 包装器。
一个实用程序类,使用 Runtime.exec 将 ImageMagick 二进制文件作为外部进程调用,以发现高度/宽度或调整图像大小。它有一些与我系统上的需求相对应的默认文件路径,您可能需要根据自己的系统更改它们。
Anjo Krank:我可能错了,但我相当确定这段代码无法正常工作,至少不稳定。无法保证在进程退出之前,提供的数组的内容被填充。我在 Wonder 中尝试时,出现了很多空结果。到目前为止,我见过的唯一安全的方法是实际等到流完全完成读取后再访问结果。这只能通过等待信号量来实现。看看 ERXRuntimeUtilities,它有一个可用的版本。
/* ImageMagickUtil.java created by jrochkind on Thu 24-Apr-2003 */ import com.webobjects.foundation.*; import com.webobjects.eocontrol.*; import com.webobjects.eoaccess.*; import com.webobjects.appserver.*; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.BufferedReader; /* Utility methods that deal with images by calling the ImageMagick software as an external process */ /* Dealing with Runtime.exec in a thread-safe way is tricky! Sorry for that. I used the article at http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html as a guide for understanding how to do it right. */ public class ImageMagickUtil { //protected static final String imUtilLocationPath = "C:\\Program Files\\ImageMagick-5.5.6-Q8\\"; protected static final String imUtilLocationPath;// = "/export/home/jar247/mtBin/"; //The image util location path can be supplied as a Java property, //or we can try to guess it from the OS. static { String locProp = System.getProperty("imUtilPath"); String osName = System.getProperty("os.name"); if ( locProp != null ) { imUtilLocationPath = locProp; } else if ( osName.indexOf("Windows") != -1 ) { imUtilLocationPath = "C:\\Program Files\\ImageMagick-5.5.6-Q8\\"; } else { //Assume our deployment machine, which is currently set up //to make this the location... imUtilLocationPath = "/export/home/webob/ImageMagick/bin/"; } } protected static final String imIdentifyCommand = "identify"; protected static final String imConvertCommand = "convert"; public static ImageProperties getImageSize(String filePath) throws IMException { return getImageSize( new java.io.File(filePath) ); } public static ImageProperties getImageSize(java.io.File imageFile) throws IMException { String filePathToImage = imageFile.getPath(); String[] cmdArray = new String[] { imUtilLocationPath + imIdentifyCommand, "-format", "%w\n%h", // [width][newline][height] filePathToImage }; NSMutableArray stdOutContents = new NSMutableArray(); NSMutableArray stdErrContents = new NSMutableArray(); int resultCode = -1; try { resultCode = exec(cmdArray, stdOutContents, stdErrContents); } catch (IOException ioE ) { //For some reason we couldn't exec the process! Convert it to an IMException. //One reason this exception is thrown is if the path specified to the im //executable isn't correct. throw new IMException("Could not exec imagemagick process: " + ioE.getMessage(), cmdArray, null); } catch ( InterruptedException intE ) { //re-throw it as an IMException. //This exception should really never be thrown, as far as I know. throw new IMException("imagemagick process interrupted! " + intE.getMessage(), cmdArray, null); } if ( resultCode != 0 ) { //The external process reports failure! IMException e = new IMException("Identify failed! ", cmdArray, stdErrContents); e.exitValue = resultCode; throw e; } //Now we need to parse the result line for the height //and width information. if ( stdOutContents.count() >= 2 ) { //First line is width, second is height, because //we asked the imagemagick 'identify' utility to //output like that. String widthStr = (String) stdOutContents.objectAtIndex(0); String heightStr = (String) stdOutContents.objectAtIndex(1); Integer width = new Integer( widthStr ); Integer height = new Integer( heightStr ); ImageProperties p = new ImageProperties(); p.width = width; p.height = height; return p; } else { //Umm? Error condition. throw new IMException("Unexpected output of imagemagick process", cmdArray, stdErrContents); } } // An external image magick process will be run to resize the image. It's recommended you // check to make sure it's necessary to resize the image first! // Beware, if the sizes you pass in are LARGER than the existing size, the output image // WILL be BIGGER than the input---this isn't just for resizing downward. // Null outFilePath means to overwrite the source file path with the resized image. // You can pass null for either maxWidth or maxHeight, but not both, that would be silly! // [not implemented yet:] Returned is an object telling you the new resized size of the output image. public static void resizeImage(String sourceFilePath, String outFilePath, int maxWidth, int maxHeight) throws IMException { if ( outFilePath == null ) { //overwrite original file if necessary outFilePath = sourceFilePath; } /*else if ( NSPathUtilities.pathExtension( outFilePath ) == null ) { //give the output file path the same extension as the in file path. outFilePath = NSPathUtilities.stringByAppendingExtension( outFilePath, NSPathUtilities.pathExtension(sourceFilePath)); }*/ StringBuffer dimensionBuffer = new StringBuffer(); if ( maxWidth != -1) { dimensionBuffer.append(maxWidth); } dimensionBuffer.append( "x" ); if ( maxHeight != -1) { dimensionBuffer.append( maxHeight ); } String dimensionDirective = dimensionBuffer.toString(); //We include the ' +profile "*" ' argument to remove //all profiles from the output. Not sure exactly what this means... //but before we were doing this, we wound up with JPGs that //caused problems for IE, for reasons I do not understand. String[] cmdArray = new String[] { imUtilLocationPath + imConvertCommand, "-size", dimensionDirective, sourceFilePath, "-resize", dimensionDirective, "+profile", "*", outFilePath }; NSMutableArray stdErrContents = new NSMutableArray(); NSMutableArray stdOutContents = new NSMutableArray(); int resultCode; try { resultCode = exec( cmdArray, stdOutContents, stdErrContents ); } catch (IOException ioE ) { //For some reason we couldn't exec the process! Convert it to an IMException. //One reason this exception is thrown is if the path specified to the im //executable isn't correct. throw new IMException("Could not exec imagemagick process: " + ioE.getMessage(), cmdArray, null); } catch ( InterruptedException intE ) { //re-throw it as an IMException. //This exception should really never be thrown, as far as I know. throw new IMException("imagemagick process interrupted! " + intE.getMessage(), cmdArray, null); } if ( resultCode != 0 ) { //The external process reports failure! IMException e = new IMException("Conversion failed! ", cmdArray, stdErrContents); e.exitValue = resultCode; throw e; } } //Invokes the external process. Puts standard out and standard error into the //arrays given in arguments (they can be null, in which case the err/out stream //is just thrown out. //Throws IOException if the exec of the external process doesn't work, for instance //because the path to the command is no good. //Throws the InterruptedException... not sure when, if ever. But it means that the exec //didn't work completely. public static int exec(String[] cmdArray, NSMutableArray stdOut, NSMutableArray stdErr) throws IOException, InterruptedException { Process process = Runtime.getRuntime().exec( cmdArray ); //We are interested in what that process writes to standard //output and standard error. //Grab the contents of those in their own separate threads! //To avoid deadlock on the external process thread! StreamGrabber errGrabber = new StreamGrabber( process.getErrorStream(), stdErr ); StreamGrabber outGrabber = new StreamGrabber( process.getInputStream(), stdOut ); errGrabber.start(); outGrabber.start(); return process.waitFor(); } //Will launch a NEW THREAD and put the contents of the given stream into //the NSMutableArray. Don't be accessing the array in another thread before //we're done! //If array is null, StreamGrabber reads the input stream to completion, //but doesn't store results anywhere. static class StreamGrabber extends Thread { InputStream inputStream; NSMutableArray array; boolean done = false; Exception exceptionEncountered; StreamGrabber(InputStream is, NSMutableArray a) { super("StreamGrabber"); this.inputStream = is; this.array = a; } public void run() { try { InputStreamReader isr = new InputStreamReader(inputStream); BufferedReader br = new BufferedReader(isr); String line=null; while ( (line = br.readLine()) != null) { if ( array != null ) { array.addObject(line); } } } catch ( java.io.IOException e) { //hmm, what should we do?!? setExceptionEncountered( e ); } setDone( true ); } public synchronized void setDone(boolean v) { done = v; } //Can be used by parent thread to see if we're done yet. public synchronized boolean done() { return done; } public synchronized Exception exceptionEncountered() { return exceptionEncountered; } public synchronized void setExceptionEncountered(Exception e) { exceptionEncountered = e; } public boolean didEncounterException() { return exceptionEncountered() != null; } } //Exception thrown by utility methods when the call to an external image magick //process failed. public static class IMException extends Exception { protected int exitValue; protected String processErrorMessage; protected String invocationLine; protected String message; public IMException() { super(); } public IMException(String s) { super(); message = s; } //Constructs a long message from all these parts public IMException(String messagePrefix, String[] cmdArray, NSMutableArray stdErr) { super(); if ( cmdArray != null ) { invocationLine = new NSArray( cmdArray ).componentsJoinedByString(" "); } if ( stdErr != null ) { processErrorMessage = stdErr.componentsJoinedByString("; "); } StringBuffer b = new StringBuffer(); b.append( messagePrefix ); b.append(". invocation line: "); b.append( invocationLine ); b.append(". error output: " ); b.append( processErrorMessage ); message = b.toString(); } //the return code from the image magick external invocation. //I think it's probably always 1 in an error condition, so not so useful. public int exitValue() { return exitValue; } //The error message reported by image magick. public String processErrorMessage() { return processErrorMessage; } //The command line used to invoke the external im process that //resulted in an error. public String invocationLine() { return invocationLine; } public void setInvocationLine( String[] cmdArray ) { invocationLine = new NSArray(cmdArray).componentsJoinedByString(" "); } //over-riding public String getMessage() { return message; } } //Object that encapsulates data returned by an image operation public static class ImageProperties extends Object { protected Integer height; protected Integer width; public ImageProperties() { super(); } public Integer height() { return height; } public Integer width() { return width; } } }
一个使用 Java Advanced Imaging (http://java.sun.com/products/java-media/jai/) 调整图像大小的示例。jar 文件 jai-codec.jar 和 jac-core.jar 位于 NEXT_ROOT/Library/Java/Extensions 文件夹中,并在类路径中引用。此示例使用来自 Wonder 项目的日志记录功能。
/* ImageResizer.java */ import java.awt.image.renderable.ParameterBlock; import java.io.ByteArrayOutputStream; import java.io.IOException; import javax.media.jai.InterpolationNearest; import javax.media.jai.JAI; import javax.media.jai.OpImage; import javax.media.jai.RenderedOp; import com.sun.media.jai.codec.ByteArraySeekableStream; import com.webobjects.foundation.NSData; import er.extensions.ERXLogger; public class ImageResizer { private static final ERXLogger log = ERXLogger.getERXLogger(ImageResizer.class); /** * utility function to resize an image to either maxWidth or maxHeight with jai * example: logo = new NSData(aFileContents1); * logo = ImageResizer.resizeImage(logo, 128, 42); * @param data image content in NSData array * @param maxWidth maxWidth in pixels picture * @param maxHeight maxHeight in pixels of picture * @return resized array in JPG format if succesfull, null otherwise */ static public NSData resizeImage(NSData data, int maxWidth, int maxHeight) { try { ByteArraySeekableStream s = new ByteArraySeekableStream(data .bytes()); RenderedOp objImage = JAI.create("stream", s); ((OpImage) objImage.getRendering()).setTileCache(null); if (objImage.getWidth() == 0 || objImage.getHeight() == 0) { log.error("graphic size is zero"); return null; } float xScale = (float) (maxWidth * 1.0) / objImage.getWidth(); float yScale = (float) (maxHeight * 1.0) / objImage.getHeight(); float scale = xScale; if (xScale > yScale) { scale = yScale; } ParameterBlock pb = new ParameterBlock(); pb.addSource(objImage); // The source image pb.add(scale); // The xScale pb.add(scale); // The yScale pb.add(0.0F); // The x translation pb.add(0.0F); // The y translation pb.add(new InterpolationNearest()); // The interpolation objImage = JAI.create("scale", pb, null); ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); JAI.create("encode", objImage, out, "JPEG"); return new NSData(out.toByteArray()); } catch (IOException e) { log.error("io exception " + e); } catch (RuntimeException e) { log.error("runtime exception "+e); } return null; } }