You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by ga...@apache.org on 2010/09/17 19:29:24 UTC

svn commit: r998214 - in /shindig/trunk/java/gadgets/src: main/java/org/apache/shindig/gadgets/rewrite/image/BasicImageRewriter.java test/java/org/apache/shindig/gadgets/rewrite/image/ImageRewriterTest.java

Author: gagan
Date: Fri Sep 17 17:29:23 2010
New Revision: 998214

URL: http://svn.apache.org/viewvc?rev=998214&view=rev
Log:
Patch by vikas.arora | http://codereview.appspot.com/1876047/ | Refactor Shindig BasicImageRewriter

Modified:
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BasicImageRewriter.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/ImageRewriterTest.java

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BasicImageRewriter.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BasicImageRewriter.java?rev=998214&r1=998213&r2=998214&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BasicImageRewriter.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BasicImageRewriter.java Fri Sep 17 17:29:23 2010
@@ -67,7 +67,7 @@ public class BasicImageRewriter implemen
   static final String
       CONTENT_TYPE_AND_MIME_MISMATCH =
           "Content is not an image but mime type asserts it is";
-    
+
   private static final String CONTENT_TYPE_IMAGE_PNG = "image/png";
   /** Returned as the output message if a huge image is submitted to be scaled */
   private static final String RESIZE_IMAGE_TOO_LARGE = "The image is too large to resize";
@@ -99,32 +99,190 @@ public class BasicImageRewriter implemen
 
   private final OptimizerConfig config;
 
+  private class ImageResizeData {
+    private Integer requestedWidth;
+    private Integer requestedHeight;
+    private Integer widthDelta;
+    private Integer heightDelta;
+
+    protected ImageResizeData(Integer requestedWidth, Integer requestedHeight, Integer widthDelta,
+        Integer heightDelta) {
+      this.requestedWidth = requestedWidth;
+      this.requestedHeight = requestedHeight;
+      this.widthDelta = widthDelta;
+      this.heightDelta = heightDelta;
+    }
+
+    public Integer getWidth() {
+      return requestedWidth;
+    }
+
+    public Integer getHeight() {
+      return requestedHeight;
+    }
+
+    public Integer getWidthDelta() {
+      return widthDelta;
+    }
+
+    public Integer getHeightDelta() {
+      return heightDelta;
+    }
+  }
+
   @Inject
   public BasicImageRewriter(OptimizerConfig config) {
     this.config = config;
   }
 
+  /**
+   * Predicate check for validating the Image Rewrite step. Images that are either too huge or
+   * invalid resize URL parameters are specified are not fit for rewrite.
+   *
+   * @param request the HTTP request.
+   * @param response the HTTP response for the original image fetched.
+   * @param imageInfo the image information extracted via Apache's Sanselan APIs.
+   * @param isResizeRequested boolean flag to indicate whether Image resize is requested or not.
+   * Huge images for which resize is requested are not fit for rewrite.
+   * @return true if the specified image can be rewriten; else it's set to false.
+   */
+  private Boolean canRewrite(HttpRequest request, HttpResponseBuilder response,
+      ImageInfo imageInfo, Boolean isResizeRequested) {
+    Uri uri = request.getUri();
+    if (null == uri) return false;
+
+    // Don't handle very small images, but check after parsing format to
+    // detect attacks.
+    if (response.getContentLength() < config.getMinThresholdBytes()) {
+      return false;
+    }
+
+    // TODO(anyone): The following check has been retained to maintain the functional equivalency
+    // with the old code (and testcases). In case resize parameters are not specified, still one
+    // can apply optimization to the response (call to 'applyOptimizer').
+    Integer resizeQuality = request.getParamAsInteger(PARAM_RESIZE_QUALITY);
+    Integer requestedWidth = request.getParamAsInteger(PARAM_RESIZE_WIDTH);
+    Integer requestedHeight = request.getParamAsInteger(PARAM_RESIZE_HEIGHT);
+    if (!isUsableParameter(requestedWidth) || !isUsableParameter(requestedHeight) ||
+        !isUsableParameter(resizeQuality)) {
+      return false;
+    }
+
+    if (isResizeRequested && isImageTooLarge(imageInfo)) {
+      errorResponse(response, HttpResponse.SC_FORBIDDEN, RESIZE_IMAGE_TOO_LARGE);
+      return false;
+    }
+
+    // Don't handle animations.
+    // TODO: This doesn't work as current Sanselan doesn't return accurate image counts.
+    // See animated GIF detection below.
+    if (imageInfo.getNumberOfImages() > 1 || isImageTooLarge(imageInfo)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Predicate check for validating the Image Resizing step. Images with improper resize parameters
+   * specified are not fit for resize.
+   *
+   * @param request the HTTP request.
+   * @param response the HTTP response for the original image fetched.
+   * @param imageInfo the image information extracted via Apache's Sanselan APIs.
+   * @return true if the specified image can be rewriten; else it's set to false.
+   */
+  private Boolean isResizeRequested(HttpRequest request, HttpResponseBuilder response,
+      ImageInfo imageInfo) {
+    Integer requestedWidth = request.getParamAsInteger(PARAM_RESIZE_WIDTH);
+    Integer requestedHeight = request.getParamAsInteger(PARAM_RESIZE_HEIGHT);
+
+    boolean resizeRequested = ((requestedWidth != null) && isUsableParameter(requestedWidth) ||
+                               (requestedHeight != null) && isUsableParameter(requestedHeight));
+    boolean noExpand = "1".equals(request.getParam(PARAM_NO_EXPAND));
+    if (noExpand &&
+        (requestedHeight == null || imageInfo.getHeight() <= requestedHeight) &&
+        (requestedWidth == null || imageInfo.getWidth() <= requestedWidth)) {
+      // Don't do anything, since the current image fits within the bounding area.
+      resizeRequested = false;
+    }
+
+    return resizeRequested;
+  }
+
+  /**
+   * Get Image Resize data by honoring resizing parameters specified in the request.
+   *
+   * @param request the HTTP request.
+   * @param response the HTTP response for the original image fetched.
+   * @param image the image to resize and format conversion.
+   * @param imageInfo the image information extracted via Apache's Sanselan APIs.
+   * @return image resize data corresponding to the transformed width and height. The return value
+   * is null for cases where image can't be resized.
+   */
+   private ImageResizeData getResizeData(HttpRequest request, HttpResponseBuilder response,
+       BufferedImage image, ImageInfo imageInfo) throws IOException {
+    int origWidth = imageInfo.getWidth();
+    int origHeight = imageInfo.getHeight();
+    int widthDelta = 0;
+    int heightDelta = 0;
+    Integer requestedWidth = request.getParamAsInteger(PARAM_RESIZE_WIDTH);
+    Integer requestedHeight = request.getParamAsInteger(PARAM_RESIZE_HEIGHT);
+
+    if (requestedWidth == null || requestedHeight == null) {
+      // It is enough to cast only one int to double, Java will coerce all others to double
+      // (JAVA spec, section 5.1.2).  In addition, interleave divisions and multiplications
+      // to keep the end result at bay, and clip the requested dimensions from below to
+      // compensate for small image dimensions.
+      if (requestedWidth == null) {
+        requestedWidth = max(1, (int) (origWidth / (double) origHeight * requestedHeight));
+      }
+      if (requestedHeight == null) {
+        requestedHeight = max(1, (int) (origHeight / (double) origWidth * requestedWidth));
+      }
+    } else {
+      // If both image dimensions are fixed, the two-step resizing process will need to know
+      // how much it has to fix up the image.
+      double ratio = getResizeRatio(requestedWidth, requestedHeight, origWidth, origHeight);
+      int widthAfterStep1 = max(1, (int) Math.round(ratio * origWidth));
+      widthDelta = requestedWidth - widthAfterStep1;
+
+      int heightAfterStep1 = max(1, (int) Math.round(ratio * origHeight));
+      heightDelta = requestedHeight - heightAfterStep1;
+
+      boolean noExpand = "1".equals(request.getParam(PARAM_NO_EXPAND));
+      if (noExpand) {
+        // No expansion requested: make sure not to expand the resulting image on either axis,
+        // even if both resize_[w,h] params are specified.
+        if (widthDelta == 0) {
+          requestedHeight = heightAfterStep1;
+          heightDelta = 0;
+        } else if (heightDelta == 0) {
+          requestedWidth = widthAfterStep1;
+          widthDelta = 0;
+        }
+      }
+    }
+
+    if (isResizeRequired(requestedWidth, requestedHeight, imageInfo)
+        && !isTargetImageTooLarge(requestedWidth, requestedHeight, imageInfo)) {
+      return new ImageResizeData(requestedWidth, requestedHeight, widthDelta, heightDelta);
+    } else {
+      return null;
+    }
+  }
+
   public void rewrite(HttpRequest request, HttpResponseBuilder response) {
     if (request == null || response == null) return;
-    
+
     Uri uri = request.getUri();
     if (null == uri) return;
-    
-    try {
-      // Check resizing
-      Integer resizeQuality = request.getParamAsInteger(PARAM_RESIZE_QUALITY);
-      Integer requestedWidth = request.getParamAsInteger(PARAM_RESIZE_WIDTH);
-      Integer requestedHeight = request.getParamAsInteger(PARAM_RESIZE_HEIGHT);
-      boolean isResizeRequested = (requestedWidth != null || requestedHeight != null);
 
-      // If the path or MIME type don't match, continue
-      if (!isSupportedContent(response) && !isImage(uri)) {
-        return;
-      }
-      if (!isUsableParameter(requestedWidth) || !isUsableParameter(requestedHeight) ||
-          !isUsableParameter(resizeQuality)) {
-        return;
-      }
+    try {
+       // If the path or MIME type don't match, continue
+       if (!isSupportedContent(response) && !isImage(uri)) {
+         return;
+       }
 
       // Content header checking is fast so this is fine to do for every response.
       ImageFormat imageFormat = Sanselan
@@ -135,87 +293,35 @@ public class BasicImageRewriter implemen
         return;
       }
 
-      // Don't handle very small images, but check after parsing format to
-      // detect attacks.
-      if (response.getContentLength() < config.getMinThresholdBytes()) {
-        return;
-      }
-
       ImageInfo imageInfo = Sanselan.getImageInfo(response.getContentBytes(), uri.getPath());
-      
-      boolean noExpand = "1".equals(request.getParam(PARAM_NO_EXPAND));
-      if (noExpand &&
-          (requestedHeight == null || imageInfo.getHeight() <= requestedHeight) &&
-          (requestedWidth == null || imageInfo.getWidth() <= requestedWidth)) {
-        // Don't do anything, since the current image fits within the bounding area.
-        isResizeRequested = false;
-      }
 
-      boolean isOversizedImage = isImageTooLarge(imageInfo);
-      if (isResizeRequested && isOversizedImage) {
-        errorResponse(response, HttpResponse.SC_FORBIDDEN, RESIZE_IMAGE_TOO_LARGE);
-        return;
-      }
+      Boolean resizeRequested = isResizeRequested(request, response, imageInfo);
 
-      // Don't handle animations.
-      // TODO: This doesn't work as current Sanselan doesn't return accurate image counts.
-      // See animated GIF detection below.
-      if (imageInfo.getNumberOfImages() > 1 || isOversizedImage) {
+      // Return in case image can't be rewriten.
+      if (!canRewrite(request, response, imageInfo, resizeRequested)) {
         return;
       }
-      
-      BufferedImage image = readImage(imageFormat, response);
 
-      if (isResizeRequested) {
-        int origWidth = imageInfo.getWidth();
-        int origHeight = imageInfo.getHeight();
-        int widthDelta = 0;
-        int heightDelta = 0;
-
-        if (requestedWidth == null || requestedHeight == null) {
-          // It is enough to cast only one int to double, Java will coerce all others to double
-          // (JAVA spec, section 5.1.2).  In addition, interleave divisions and multiplications
-          // to keep the end result at bay, and clip the requested dimensions from below to
-          // compensate for small image dimensions.
-          if (requestedWidth == null) {
-            requestedWidth = max(1, (int) (origWidth / (double) origHeight * requestedHeight));
-          }
-          if (requestedHeight == null) {
-            requestedHeight = max(1, (int) (origHeight / (double) origWidth * requestedWidth));
-          }
-        } else {
-          // If both image dimensions are fixed, the two-step resizing process will need to know
-          // how much it has to fix up the image.
-          double ratio = getResizeRatio(requestedWidth, requestedHeight, origWidth, origHeight);
-          int widthAfterStep1 = max(1, (int) Math.round(ratio * origWidth));
-          widthDelta = requestedWidth - widthAfterStep1;
-
-          int heightAfterStep1 = max(1, (int) Math.round(ratio * origHeight));
-          heightDelta = requestedHeight - heightAfterStep1;
-          
-          if (noExpand) {
-            // No expansion requested: make sure not to expand the resulting image on either axis,
-            // even if both resize_[w,h] params are specified.
-            if (widthDelta == 0) {
-              requestedHeight = heightAfterStep1;
-              heightDelta = 0;
-            } else if (heightDelta == 0) {
-              requestedWidth = widthAfterStep1;
-              widthDelta = 0;
-            }
-          }
-        }
+      // Step#1: Read the image using appropriate readers for the corresponding image format.
+      BufferedImage image = readImage(imageFormat, response);
 
-        if (resizeQuality == null) {
-          resizeQuality = DEFAULT_QUALITY;
-        }
+      // Proceed to Resize in case image can be resized.
+      if (resizeRequested) {
+        // Step#2: Get the Resize Data
+        ImageResizeData resizeData = getResizeData(request, response, image, imageInfo);
+
+        if (resizeData != null) {
+          // Step#3: Resize (Scale+Stretch) Image using Java AWT Graphics2D package.
+          image = resizeImage(image, resizeData.getWidth(), resizeData.getHeight(),
+              resizeData.getWidthDelta(), resizeData.getHeightDelta());
 
-        if (isResizeRequired(requestedWidth, requestedHeight, imageInfo)
-            && !isTargetImageTooLarge(requestedWidth, requestedHeight, imageInfo)) {
-          image = resizeImage(image, requestedWidth, requestedHeight, widthDelta, heightDelta);
+          // Step#4: Convert the image format (MIME_TYPE) using javax.imageio package.
           updateResponse(response, image);
         }
       }
+
+      // Step#5: Optimize the supported image formats viz PNG, GIF, JPG & BMP using 'BaseOptimizer'
+      // and it's subclass implementations for the above four formats.
       applyOptimizer(response, imageFormat, image);
     } catch (IOException ioe) {
       LOG.log(Level.WARNING, "IO Error rewriting image " + request.toString() + " - " + ioe.getMessage());
@@ -229,7 +335,7 @@ public class BasicImageRewriter implemen
   }
 
   /**
-   * As the image is resized, the request needs to change so that the optimizer can
+   * If the image is resized, the request needs to change so that the optimizer can
    * make sensible image size-related decisions down the pipeline.  GIF images are rewritten
    * as PNGs though, so as not to include the dependency on the GIF decoder.
    *
@@ -237,8 +343,7 @@ public class BasicImageRewriter implemen
    * @param image the resized image that needs to be substituted for the original image from
    *        the response
    */
-  private void updateResponse(HttpResponseBuilder response, BufferedImage image)
-      throws IOException {
+  public void updateResponse(HttpResponseBuilder response, BufferedImage image) throws IOException {
     ImageWriter imageWriter = ImageIO.getImageWritersByFormatName(RESIZE_OUTPUT_FORMAT).next();
     ImageOutputter outputter = new ImageIOOutputter(imageWriter, null);
     byte[] imageBytes = outputter.toBytes(image);
@@ -281,7 +386,7 @@ public class BasicImageRewriter implemen
    * @return the image obtained by stretching the original image so that its new dimensions
    *        are {@code requestedWidth} and {@code requestedHeight}
    */
-  private BufferedImage resizeImage(BufferedImage image, Integer requestedWidth,
+  public BufferedImage resizeImage(BufferedImage image, Integer requestedWidth,
       Integer requestedHeight, int extraWidth, int extraHeight) {
     int widthStretch = requestedWidth - extraWidth;
     int heightStretch = requestedHeight - extraHeight;
@@ -409,7 +514,7 @@ public class BasicImageRewriter implemen
       }
     }
   }
-  
+
   private void errorResponse(HttpResponseBuilder response, int status, String msg) {
     response.clearAllHeaders().setHttpStatusCode(status).setResponseString(msg);
   }

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/ImageRewriterTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/ImageRewriterTest.java?rev=998214&r1=998213&r2=998214&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/ImageRewriterTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/image/ImageRewriterTest.java Fri Sep 17 17:29:23 2010
@@ -120,10 +120,10 @@ public class ImageRewriterTest extends A
 
   private HttpRequest getMockRequest(Integer width, Integer height, Integer quality, boolean noExpand) {
     HttpRequest request = mockControl.createMock(HttpRequest.class);
-    expect(request.getUri()).andReturn(IMAGE_URL);
-    expect(request.getParamAsInteger(Param.RESIZE_QUALITY.getKey())).andReturn(quality);
-    expect(request.getParamAsInteger(Param.RESIZE_WIDTH.getKey())).andReturn(width);
-    expect(request.getParamAsInteger(Param.RESIZE_HEIGHT.getKey())).andReturn(height);
+    expect(request.getUri()).andReturn(IMAGE_URL).anyTimes();
+    expect(request.getParamAsInteger(Param.RESIZE_QUALITY.getKey())).andReturn(quality).anyTimes();
+    expect(request.getParamAsInteger(Param.RESIZE_WIDTH.getKey())).andReturn(width).anyTimes();
+    expect(request.getParamAsInteger(Param.RESIZE_HEIGHT.getKey())).andReturn(height).anyTimes();
     expect(request.getParam(Param.NO_EXPAND.getKey())).andReturn(noExpand ? "1" : null).anyTimes();
     return request;
   }