]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Extracted some bitmap handling functions from the PCL implementation into new BitmapI...
authorJeremias Maerki <jeremias@apache.org>
Thu, 15 Jan 2009 15:51:36 +0000 (15:51 +0000)
committerJeremias Maerki <jeremias@apache.org>
Thu, 15 Jan 2009 15:51:36 +0000 (15:51 +0000)
Added limited support for more efficient image encoding in AFP. Where possible the raw image data from the RenderedImage is used. Limitations include:
- missing support for color lookup tables (IndexColorModel)
- monochrome images: additive/subtractive flag is not supported by the AFP library, yet.
Images with fewer bits per pixel than specified in the configuration are no longer blown up to 4, 8 or even 24 bits.

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@734737 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java
src/java/org/apache/fop/render/pcl/PCLGenerator.java
src/java/org/apache/fop/util/BitmapImageUtil.java [new file with mode: 0644]

index 28c942a0843b3d222ff721722158e6120ba39970..b956d826428bfd4313f44bc8c97e0b65c18c2bd8 100644 (file)
 
 package org.apache.fop.render.afp;
 
+import java.awt.image.ColorModel;
 import java.awt.image.RenderedImage;
 import java.io.IOException;
 
 import org.apache.commons.io.output.ByteArrayOutputStream;
-import org.apache.fop.afp.AFPDataObjectInfo;
-import org.apache.fop.afp.AFPImageObjectInfo;
-import org.apache.fop.afp.AFPObjectAreaInfo;
-import org.apache.fop.afp.AFPPaintingState;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
 import org.apache.xmlgraphics.image.loader.ImageFlavor;
 import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
 import org.apache.xmlgraphics.ps.ImageEncodingHelper;
 import org.apache.xmlgraphics.util.MimeConstants;
 
+import org.apache.fop.afp.AFPDataObjectInfo;
+import org.apache.fop.afp.AFPImageObjectInfo;
+import org.apache.fop.afp.AFPObjectAreaInfo;
+import org.apache.fop.afp.AFPPaintingState;
+import org.apache.fop.util.BitmapImageUtil;
+
 /**
  * PDFImageHandler implementation which handles RenderedImage instances.
  */
 public class AFPImageHandlerRenderedImage extends AFPImageHandler {
 
+    /** logging instance */
+    private static Log log = LogFactory.getLog(AFPImageHandlerRenderedImage.class);
+
     private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {
         ImageFlavor.BUFFERED_IMAGE,
         ImageFlavor.RENDERED_IMAGE
@@ -67,22 +76,69 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler {
         int dataWidth = renderedImage.getWidth();
         imageObjectInfo.setDataWidth(dataWidth);
 
+        int maxPixelSize = paintingState.getBitsPerPixel();
+        if (paintingState.isColorImages()) {
+            maxPixelSize *= 3; //RGB only at the moment
+        }
+
+        ColorModel cm = renderedImage.getColorModel();
+        if (log.isTraceEnabled()) {
+            log.trace("ColorModel: " + cm);
+        }
+        int pixelSize = cm.getPixelSize();
+        if (cm.hasAlpha()) {
+            pixelSize -= 8;
+        }
+        //TODO Add support for CMYK images
+
+        byte[] imageData = null;
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        ImageEncodingHelper.encodeRenderedImageAsRGB(renderedImage, baos);
-        byte[] imageData = baos.toByteArray();
-
-        boolean colorImages = paintingState.isColorImages();
-        imageObjectInfo.setColor(colorImages);
-
-        // convert to grayscale
-        if (!colorImages) {
-            baos.reset();
-            int bitsPerPixel = paintingState.getBitsPerPixel();
-            imageObjectInfo.setBitsPerPixel(bitsPerPixel);
-            ImageEncodingHelper.encodeRGBAsGrayScale(
-                  imageData, dataWidth, dataHeight, bitsPerPixel, baos);
+        boolean allowDirectEncoding = true;
+        if (allowDirectEncoding && pixelSize <= maxPixelSize) {
+            //Attempt to encode without resampling the image
+            ImageEncodingHelper helper = new ImageEncodingHelper(renderedImage);
+            ColorModel encodedColorModel = helper.getEncodedColorModel();
+            boolean directEncode = true;
+            if (helper.getEncodedColorModel().getPixelSize() > maxPixelSize) {
+                directEncode = false; //pixel size needs to be reduced
+            }
+            if (BitmapImageUtil.getColorIndexSize(renderedImage) > 2) {
+                directEncode = false; //Lookup tables are not implemented, yet
+            }
+            if (BitmapImageUtil.isMonochromeImage(renderedImage)
+                    && BitmapImageUtil.isZeroBlack(renderedImage)) {
+                directEncode = false; //Passing additive/subtractive info not implemented, yet
+            }
+            if (directEncode) {
+                log.debug("Encoding image directly...");
+                imageObjectInfo.setBitsPerPixel(encodedColorModel.getPixelSize());
+                helper.encode(baos);
+                imageData = baos.toByteArray();
+            }
+        }
+        if (imageData == null) {
+            log.debug("Encoding image via RGB...");
+            //Convert image to 24bit RGB
+            ImageEncodingHelper.encodeRenderedImageAsRGB(renderedImage, baos);
             imageData = baos.toByteArray();
+
+            boolean colorImages = paintingState.isColorImages();
+            imageObjectInfo.setColor(colorImages);
+
+            // convert to grayscale
+            if (!colorImages) {
+                log.debug("Converting RGB image to grayscale...");
+                baos.reset();
+                int bitsPerPixel = paintingState.getBitsPerPixel();
+                imageObjectInfo.setBitsPerPixel(bitsPerPixel);
+                //TODO this should be done off the RenderedImage to avoid buffering the
+                //intermediate 24bit image
+                ImageEncodingHelper.encodeRGBAsGrayScale(
+                      imageData, dataWidth, dataHeight, bitsPerPixel, baos);
+                imageData = baos.toByteArray();
+            }
         }
+
         imageObjectInfo.setData(imageData);
 
         // set object area info
index fbb9c6ad0dde197ac9990a563630e4819ded55fc..50cf622c7fbd4773c1a835c1a89d8159df25f2c2 100644 (file)
@@ -47,6 +47,8 @@ import org.apache.commons.io.output.ByteArrayOutputStream;
 import org.apache.xmlgraphics.image.GraphicsUtil;
 import org.apache.xmlgraphics.util.UnitConv;
 
+import org.apache.fop.util.BitmapImageUtil;
+
 /**
  * This class provides methods for generating PCL print files.
  */
@@ -522,7 +524,7 @@ public class PCLGenerator {
      * @return the gray value
      */
     public final int convertToGray(int r, int g, int b) {
-        return (r * 30 + g * 59 + b * 11) / 100;
+        return BitmapImageUtil.convertToGray(r, g, b);
     }
 
     /**
@@ -574,13 +576,7 @@ public class PCLGenerator {
      * @return true if it's a monochrome image
      */
     public static boolean isMonochromeImage(RenderedImage img) {
-        ColorModel cm = img.getColorModel();
-        if (cm instanceof IndexColorModel) {
-            IndexColorModel icm = (IndexColorModel)cm;
-            return icm.getMapSize() == 2;
-        } else {
-            return false;
-        }
+        return BitmapImageUtil.isMonochromeImage(img);
     }
 
     /**
@@ -589,7 +585,7 @@ public class PCLGenerator {
      * @return true if it's a grayscale image
      */
     public static boolean isGrayscaleImage(RenderedImage img) {
-        return (img.getColorModel().getColorSpace().getNumComponents() == 1);
+        return BitmapImageUtil.isGrayscaleImage(img);
     }
 
     private MonochromeBitmapConverter createMonochromeBitmapConverter() {
@@ -766,22 +762,7 @@ public class PCLGenerator {
                 }
             }
             if (src == null) {
-                src = new BufferedImage(effDim.width, effDim.height,
-                        BufferedImage.TYPE_BYTE_GRAY);
-                Graphics2D g2d = src.createGraphics();
-                try {
-                    g2d.setBackground(Color.white);
-                    g2d.setColor(Color.black);
-                    g2d.clearRect(0, 0, effDim.width, effDim.height);
-
-                    AffineTransform at = new AffineTransform();
-                    double sx = effDim.getWidth() / orgDim.getWidth();
-                    double sy = effDim.getHeight() / orgDim.getHeight();
-                    at.scale(sx, sy);
-                    g2d.drawRenderedImage(img, at);
-                } finally {
-                    g2d.dispose();
-                }
+                src = BitmapImageUtil.convertToGrayscale(img, effDim);
             }
             MonochromeBitmapConverter converter = createMonochromeBitmapConverter();
             converter.setHint("quality", "false");
@@ -793,22 +774,9 @@ public class PCLGenerator {
             setTransparencyMode(sourceTransparency || mask != null, true);
             paintMonochromeBitmap(red, effResolution);
         } else {
-            //TODO untested!
             RenderedImage effImg = img;
             if (scaled) {
-                BufferedImage buf = new BufferedImage(effDim.width, effDim.height,
-                        BufferedImage.TYPE_BYTE_BINARY);
-                Graphics2D g2d = buf.createGraphics();
-                try {
-                    AffineTransform at = new AffineTransform();
-                    double sx = effDim.getWidth() / orgDim.getWidth();
-                    double sy = effDim.getHeight() / orgDim.getHeight();
-                    at.scale(sx, sy);
-                    g2d.drawRenderedImage(img, at);
-                } finally {
-                    g2d.dispose();
-                }
-                effImg = buf;
+                effImg = BitmapImageUtil.convertToMonochrome(img, effDim);
             }
             setSourceTransparencyMode(sourceTransparency);
             selectCurrentPattern(0, 0); //Solid black
diff --git a/src/java/org/apache/fop/util/BitmapImageUtil.java b/src/java/org/apache/fop/util/BitmapImageUtil.java
new file mode 100644 (file)
index 0000000..278587b
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.IndexColorModel;
+import java.awt.image.RenderedImage;
+
+/**
+ * Utility method for dealing with bitmap images.
+ */
+public class BitmapImageUtil {
+
+    /**
+     * Indicates whether an image is a monochrome (1 bit black and white) image.
+     * @param img the image
+     * @return true if it's a monochrome image
+     */
+    public static final boolean isMonochromeImage(RenderedImage img) {
+        return (getColorIndexSize(img) == 2);
+    }
+
+    /**
+     * Indicates whether a zero bit indicates a black/dark pixel for a monochrome image.
+     * @param img the image (must be 1 bit monochrome)
+     * @return true if a zero bit indicates a black/dark pixel, false for a white/bright pixel
+     */
+    public static final boolean isZeroBlack(RenderedImage img) {
+        if (!isMonochromeImage(img)) {
+            throw new IllegalArgumentException("Image is not a monochrome image!");
+        }
+        IndexColorModel icm = (IndexColorModel)img.getColorModel();
+        int gray0 = convertToGray(icm.getRGB(0));
+        int gray1 = convertToGray(icm.getRGB(1));
+        return gray0 < gray1;
+    }
+
+    /**
+     * Convert an RGB color value to a grayscale from 0 to 100.
+     * @param r the red component
+     * @param g the green component
+     * @param b the blue component
+     * @return the gray value
+     */
+    public static final int convertToGray(int r, int g, int b) {
+        return (r * 30 + g * 59 + b * 11) / 100;
+    }
+
+    /**
+     * Convert an RGB color value to a grayscale from 0 to 100.
+     * @param rgb the RGB value
+     * @return the gray value
+     */
+    public static final int convertToGray(int rgb) {
+        int r = (rgb & 0xFF0000) >> 16;
+        int g = (rgb & 0xFF00) >> 8;
+        int b = rgb & 0xFF;
+        return convertToGray(r, g, b);
+    }
+
+    /**
+     * Returns the size of the color index if the given image has one.
+     * @param img the image
+     * @return the size of the color index or 0 if there's no color index
+     */
+    public static final int getColorIndexSize(RenderedImage img) {
+        ColorModel cm = img.getColorModel();
+        if (cm instanceof IndexColorModel) {
+            IndexColorModel icm = (IndexColorModel)cm;
+            return icm.getMapSize();
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Indicates whether an image is a grayscale image.
+     * @param img the image
+     * @return true if it's a grayscale image
+     */
+    public static final boolean isGrayscaleImage(RenderedImage img) {
+        return (img.getColorModel().getColorSpace().getNumComponents() == 1);
+    }
+
+    /**
+     * Converts an image to sRGB. Optionally, the image can be scaled.
+     * @param img the image to be converted
+     * @param targetDimension the new target dimensions or null if no scaling is necessary
+     * @return the sRGB image
+     */
+    public static final BufferedImage convertTosRGB(RenderedImage img,
+            Dimension targetDimension) {
+        return convertAndScaleImage(img, targetDimension, BufferedImage.TYPE_INT_RGB);
+    }
+
+    /**
+     * Converts an image to a grayscale (8 bits) image. Optionally, the image can be scaled.
+     * @param img the image to be converted
+     * @param targetDimension the new target dimensions or null if no scaling is necessary
+     * @return the grayscale image
+     */
+    public static final BufferedImage convertToGrayscale(RenderedImage img,
+            Dimension targetDimension) {
+        return convertAndScaleImage(img, targetDimension, BufferedImage.TYPE_BYTE_GRAY);
+    }
+
+    /**
+     * Converts an image to a monochrome 1-bit image. Optionally, the image can be scaled.
+     * @param img the image to be converted
+     * @param targetDimension the new target dimensions or null if no scaling is necessary
+     * @return the monochrome image
+     */
+    public static final BufferedImage convertToMonochrome(RenderedImage img,
+            Dimension targetDimension) {
+        return convertAndScaleImage(img, targetDimension, BufferedImage.TYPE_BYTE_BINARY);
+    }
+
+    private static BufferedImage convertAndScaleImage(RenderedImage img,
+            Dimension targetDimension, int imageType) {
+        Dimension bmpDimension = targetDimension;
+        if (bmpDimension == null) {
+            bmpDimension = new Dimension(img.getWidth(), img.getHeight());
+        }
+        BufferedImage target = new BufferedImage(bmpDimension.width, bmpDimension.height,
+                imageType);
+        transferImage(img, target);
+        return target;
+    }
+
+    private static void transferImage(RenderedImage source, BufferedImage target) {
+        Graphics2D g2d = target.createGraphics();
+        try {
+            g2d.setBackground(Color.white);
+            g2d.setColor(Color.black);
+            g2d.clearRect(0, 0, target.getWidth(), target.getHeight());
+
+            AffineTransform at = new AffineTransform();
+            if (source.getWidth() != target.getWidth()
+                    || source.getHeight() != target.getHeight()) {
+                double sx = target.getWidth() / (double)source.getWidth();
+                double sy = target.getHeight() / (double)source.getHeight();
+                at.scale(sx, sy);
+            }
+            g2d.drawRenderedImage(source, at);
+        } finally {
+            g2d.dispose();
+        }
+    }
+
+
+}