跳转到内容

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 包装器。

ImageMagick

[编辑 | 编辑源代码]

一个实用程序类,使用 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;
       }
   }
 }

JAI 示例

[编辑 | 编辑源代码]

一个使用 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;
   }
 }
华夏公益教科书