From eac924564b58c564b8c9718d02c3a0aa1c1f0166 Mon Sep 17 00:00:00 2001 From: Diego Galeota Date: Fri, 23 Nov 2018 13:33:18 +0000 Subject: [PATCH] Implement separation rendering --- pdfbox/pom.xml | 2 +- .../org/apache/pdfbox/pdmodel/PDDocument.java | 2 +- .../graphics/color/PDCIEBasedColorSpace.java | 2 +- .../pdmodel/graphics/color/PDColorSpace.java | 4 +- .../pdmodel/graphics/color/PDDeviceCMYK.java | 50 +++-- .../pdmodel/graphics/color/PDDeviceGray.java | 9 +- .../pdmodel/graphics/color/PDDeviceN.java | 31 ++- .../pdmodel/graphics/color/PDDeviceRGB.java | 8 +- .../pdmodel/graphics/color/PDICCBased.java | 6 +- .../pdmodel/graphics/color/PDIndexed.java | 35 +++- .../graphics/color/PDJPXColorSpace.java | 4 +- .../pdfbox/pdmodel/graphics/color/PDLab.java | 2 +- .../pdmodel/graphics/color/PDPattern.java | 2 +- .../pdmodel/graphics/color/PDSeparation.java | 4 +- .../pdmodel/graphics/form/PDFormXObject.java | 6 + .../pdmodel/graphics/image/PDImage.java | 2 +- .../graphics/image/PDImageXObject.java | 8 +- .../pdmodel/graphics/image/PDInlineImage.java | 4 +- .../graphics/image/SampledImageReader.java | 24 +-- .../apache/pdfbox/rendering/PDFRenderer.java | 102 ++++++++-- .../apache/pdfbox/rendering/PageDrawer.java | 176 +++++++++++++++--- .../rendering/PageDrawerParameters.java | 21 ++- .../pdfbox/rendering/TestSeparations.java | 143 ++++++++++++++ .../resources/output/rendering/.gitignore | 2 + 24 files changed, 538 insertions(+), 111 deletions(-) create mode 100644 pdfbox/src/test/java/org/apache/pdfbox/rendering/TestSeparations.java create mode 100644 pdfbox/src/test/resources/output/rendering/.gitignore diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index e5c04cd1ead..8899e5195fb 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -112,7 +112,7 @@ maven-surefire-plugin - -Xmx768m -Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider + -Xmx4G -Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider org/apache/pdfbox/rendering/TestPDFToImage.java diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java index fcbbf9d6762..8efbbea3df8 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java @@ -102,7 +102,7 @@ public class PDDocument implements Closeable try { WritableRaster raster = Raster.createBandedRaster(DataBuffer.TYPE_BYTE, 1, 1, 3, new Point(0, 0)); - PDDeviceRGB.INSTANCE.toRGBImage(raster); + PDDeviceRGB.INSTANCE.toRGBImage(raster, null, -1); } catch (IOException ex) { diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDCIEBasedColorSpace.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDCIEBasedColorSpace.java index 5d0a603c90e..013c848d783 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDCIEBasedColorSpace.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDCIEBasedColorSpace.java @@ -33,7 +33,7 @@ public abstract class PDCIEBasedColorSpace extends PDColorSpace // WARNING: this method is performance sensitive, modify with care! // @Override - public BufferedImage toRGBImage(WritableRaster raster) throws IOException + public BufferedImage toRGBImage(WritableRaster raster, PDColorSpace colorSpace, int component) throws IOException { // This method calls toRGB to convert images one pixel at a time. For matrix-based // CIE color spaces this is fast enough. However, it should not be used with any diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDColorSpace.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDColorSpace.java index bd7db466e4e..7218f865e17 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDColorSpace.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDColorSpace.java @@ -288,7 +288,7 @@ private static PDColorSpace createFromCOSObject(COSObject colorSpace, PDResource * @return an (A)RGB buffered image * @throws IOException if the color conversion fails */ - public abstract BufferedImage toRGBImage(WritableRaster raster) throws IOException; + public abstract BufferedImage toRGBImage(WritableRaster raster, PDColorSpace targetColorSpace, int component) throws IOException; /** * Returns the (A)RGB equivalent of the given raster, using the given AWT color space @@ -297,7 +297,7 @@ private static PDColorSpace createFromCOSObject(COSObject colorSpace, PDResource * @param colorSpace the AWT * @return an (A)RGB buffered image */ - protected BufferedImage toRGBImageAWT(WritableRaster raster, ColorSpace colorSpace) + protected BufferedImage toRGBImageAWT(WritableRaster raster, ColorSpace colorSpace, PDColorSpace targetColorSpace, int component) { // // WARNING: this method is performance sensitive, modify with care! diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceCMYK.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceCMYK.java index c3abc5b95f4..dbcbeefab78 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceCMYK.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceCMYK.java @@ -141,24 +141,21 @@ public float[] toRGB(float[] value) throws IOException } @Override - public BufferedImage toRGBImage(WritableRaster raster) throws IOException + public BufferedImage toRGBImage(WritableRaster raster, PDColorSpace targetColorSpace, int component) throws IOException { init(); - return toRGBImageAWT(raster, awtColorSpace); + return toRGBImageAWT(raster, awtColorSpace, targetColorSpace, component); } @Override - protected BufferedImage toRGBImageAWT(WritableRaster raster, ColorSpace colorSpace) + protected BufferedImage toRGBImageAWT(WritableRaster raster, ColorSpace colorSpace, PDColorSpace targetColorSpace, int component) { - if (usePureJavaCMYKConversion) + boolean skipColorSpace = targetColorSpace != null && targetColorSpace != this; + + if (usePureJavaCMYKConversion || skipColorSpace || component >= 0) { - BufferedImage dest = new BufferedImage(raster.getWidth(), raster.getHeight(), - BufferedImage.TYPE_INT_RGB); - ColorSpace destCS = dest.getColorModel().getColorSpace(); - WritableRaster destRaster = dest.getRaster(); + WritableRaster destRaster = raster.createCompatibleWritableRaster(); // dest.getRaster(); float[] srcValues = new float[4]; - float[] lastValues = new float[] { -1.0f, -1.0f, -1.0f, -1.0f }; - float[] destValues = new float[3]; int width = raster.getWidth(); int startX = raster.getMinX(); int height = raster.getHeight(); @@ -167,30 +164,31 @@ protected BufferedImage toRGBImageAWT(WritableRaster raster, ColorSpace colorSpa { for (int y = startY; y < height + startY; y++) { + if (skipColorSpace) { + destRaster.setPixel(x, y, new int[] {0, 0, 0, 0}); + + continue; + } + raster.getPixel(x, y, srcValues); - // check if the last value can be reused - if (!Arrays.equals(lastValues, srcValues)) - { - for (int k = 0; k < 4; k++) - { - lastValues[k] = srcValues[k]; - srcValues[k] = srcValues[k] / 255f; - } - // use CIEXYZ as intermediate format to optimize the color conversion - destValues = destCS.fromCIEXYZ(colorSpace.toCIEXYZ(srcValues)); - for (int k = 0; k < destValues.length; k++) - { - destValues[k] = destValues[k] * 255f; + + if (component >= 0) { + for(int i = 0; i < srcValues.length; i++) { + if (i != component) { + srcValues[i] = 0; + } } } - destRaster.setPixel(x, y, destValues); + + destRaster.setPixel(x, y, srcValues); } } - return dest; + + return super.toRGBImageAWT(destRaster, colorSpace, targetColorSpace, component); } else { - return super.toRGBImageAWT(raster, colorSpace); + return super.toRGBImageAWT(raster, colorSpace, targetColorSpace, component); } } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceGray.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceGray.java index 5c55cf0c088..8e95d7864b5 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceGray.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceGray.java @@ -70,7 +70,7 @@ public float[] toRGB(float[] value) } @Override - public BufferedImage toRGBImage(WritableRaster raster) throws IOException + public BufferedImage toRGBImage(WritableRaster raster, PDColorSpace targetColorSpace, int component) throws IOException { int width = raster.getWidth(); int height = raster.getHeight(); @@ -82,8 +82,13 @@ public BufferedImage toRGBImage(WritableRaster raster) throws IOException for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) - { + { raster.getPixel(x, y, gray); + + if (targetColorSpace != null && targetColorSpace != this) { + gray[0] = 255; + } + rgb[0] = gray[0]; rgb[1] = gray[0]; rgb[2] = gray[0]; diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceN.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceN.java index 02ba23a299b..e55edee15d9 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceN.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceN.java @@ -170,23 +170,24 @@ private void initColorConversionCache() throws IOException } } + // TODO: Implement separation rendering for DeviceN raster images @Override - public BufferedImage toRGBImage(WritableRaster raster) throws IOException + public BufferedImage toRGBImage(WritableRaster raster, PDColorSpace targetColorSpace, int component) throws IOException { if (attributes != null) { - return toRGBWithAttributes(raster); - } + return toRGBWithAttributes(raster, targetColorSpace, component); + } else { - return toRGBWithTintTransform(raster); + return toRGBWithTintTransform(raster, targetColorSpace, component); } } // // WARNING: this method is performance sensitive, modify with care! // - private BufferedImage toRGBWithAttributes(WritableRaster raster) throws IOException + private BufferedImage toRGBWithAttributes(WritableRaster raster, PDColorSpace targetColorSpace, int component) throws IOException { int width = raster.getWidth(); int height = raster.getHeight(); @@ -213,7 +214,7 @@ else if (spotColorSpaces[c] == null) { // TODO this happens in the Altona Visual test, is there a better workaround? // missing spot color, fallback to using tintTransform - return toRGBWithTintTransform(raster); + return toRGBWithTintTransform(raster, targetColorSpace, component); } else { @@ -249,7 +250,7 @@ else if (spotColorSpaces[c] == null) } // convert single-component raster to RGB - BufferedImage rgbComponentImage = componentColorSpace.toRGBImage(componentRaster); + BufferedImage rgbComponentImage = componentColorSpace.toRGBImage(componentRaster, null, -1); WritableRaster rgbComponentRaster = rgbComponentImage.getRaster(); // combine the RGB component with the RGB composite raster @@ -259,6 +260,12 @@ else if (spotColorSpaces[c] == null) { for (int x = 0; x < width; x++) { + if (targetColorSpace != null && targetColorSpace != alternateColorSpace) { + rgbRaster.setPixel(x, y, new int[] {255, 255, 255}); + + continue; + } + rgbComponentRaster.getPixel(x, y, rgbChannel); rgbRaster.getPixel(x, y, rgbComposite); @@ -278,7 +285,7 @@ else if (spotColorSpaces[c] == null) // // WARNING: this method is performance sensitive, modify with care! // - private BufferedImage toRGBWithTintTransform(WritableRaster raster) throws IOException + private BufferedImage toRGBWithTintTransform(WritableRaster raster, PDColorSpace targetColorSpace, int component) throws IOException { // cache color mappings Map map1 = new HashMap<>(); @@ -298,6 +305,12 @@ private BufferedImage toRGBWithTintTransform(WritableRaster raster) throws IOExc { for (int x = 0; x < width; x++) { + if (targetColorSpace != null && targetColorSpace != alternateColorSpace) { + rgbRaster.setPixel(x, y, new int[] {255, 255, 255}); + + continue; + } + raster.getPixel(x, y, src); // use a string representation as key key = Float.toString(src[0]); @@ -329,7 +342,7 @@ private BufferedImage toRGBWithTintTransform(WritableRaster raster) throws IOExc rgb[s] = (int) (rgbFloat[s] * 255f); } // must clone because rgb is reused - map1.put(key, rgb.clone()); + map1.put(key, rgb.clone()); rgbRaster.setPixel(x, y, rgb); } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceRGB.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceRGB.java index 719bc9aff7d..fa8439d069d 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceRGB.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceRGB.java @@ -36,6 +36,7 @@ public final class PDDeviceRGB extends PDDeviceColorSpace public static final PDDeviceRGB INSTANCE = new PDDeviceRGB(); private final PDColor initialColor = new PDColor(new float[] { 0, 0, 0 }, this); + private final PDColor white = new PDColor(new float[] { 1, 1, 1 }, this); private volatile ColorSpace awtColorSpace; private PDDeviceRGB() @@ -96,6 +97,11 @@ public PDColor getInitialColor() return initialColor; } + public PDColor getWhite() + { + return white; + } + @Override public float[] toRGB(float[] value) { @@ -103,7 +109,7 @@ public float[] toRGB(float[] value) } @Override - public BufferedImage toRGBImage(WritableRaster raster) throws IOException + public BufferedImage toRGBImage(WritableRaster raster, PDColorSpace colorSpace, int component) throws IOException { init(); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDICCBased.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDICCBased.java index 0b752217362..9e664afb640 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDICCBased.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDICCBased.java @@ -267,15 +267,15 @@ private float[] clampColors(ICC_ColorSpace cs, float[] value) } @Override - public BufferedImage toRGBImage(WritableRaster raster) throws IOException + public BufferedImage toRGBImage(WritableRaster raster, PDColorSpace targetColorSpace, int component) throws IOException { if (awtColorSpace != null) { - return toRGBImageAWT(raster, awtColorSpace); + return toRGBImageAWT(raster, awtColorSpace, targetColorSpace, component); } else { - return alternateColorSpace.toRGBImage(raster); + return alternateColorSpace.toRGBImage(raster, targetColorSpace, component); } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java index f349da9619e..b150a7c8e3c 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java @@ -138,7 +138,7 @@ private void initRgbColorTable() throws IOException } // convert the base image to RGB - BufferedImage rgbImage = baseColorSpace.toRGBImage(baseRaster); + BufferedImage rgbImage = baseColorSpace.toRGBImage(baseRaster, null, -1); WritableRaster rgbRaster = rgbImage.getRaster(); // build an RGB lookup table from the raster @@ -176,7 +176,7 @@ public float[] toRGB(float[] value) // WARNING: this method is performance sensitive, modify with care! // @Override - public BufferedImage toRGBImage(WritableRaster raster) throws IOException + public BufferedImage toRGBImage(WritableRaster raster, PDColorSpace targetColorSpace, int component) throws IOException { // use lookup table int width = raster.getWidth(); @@ -184,6 +184,7 @@ public BufferedImage toRGBImage(WritableRaster raster) throws IOException BufferedImage rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); WritableRaster rgbRaster = rgbImage.getRaster(); + float[] baseColor = new float[baseColorSpace.getNumberOfComponents()]; int[] src = new int[1]; for (int y = 0; y < height; y++) @@ -194,7 +195,35 @@ public BufferedImage toRGBImage(WritableRaster raster) throws IOException // lookup int index = Math.min(src[0], actualMaxIndex); - rgbRaster.setPixel(x, y, rgbColorTable[index]); + + if (targetColorSpace == null || this == targetColorSpace || baseColorSpace == targetColorSpace) { + if (component < 0) { + rgbRaster.setPixel(x, y, rgbColorTable[index]); + } + else { + float[] tableColor = colorTable[index]; + + for (int i = 0; i < baseColor.length; i++) { + if (i != component) { + baseColor[i] = 0; + } + else { + baseColor[i] = tableColor[i]; + } + } + + float[] rgbColor = baseColorSpace.toRGB(baseColor); + + for(int i = 0; i < rgbColor.length; i++) { + rgbColor[i] = rgbColor[i] * 255f; + } + + rgbRaster.setPixel(x, y, rgbColor); + } + } + else { + rgbRaster.setPixel(x, y, new int[] {255, 255, 255}); + } } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDJPXColorSpace.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDJPXColorSpace.java index f1760c637b4..5ff180c0549 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDJPXColorSpace.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDJPXColorSpace.java @@ -80,9 +80,9 @@ public float[] toRGB(float[] value) } @Override - public BufferedImage toRGBImage(WritableRaster raster) throws IOException + public BufferedImage toRGBImage(WritableRaster raster, PDColorSpace targetColorSpace, int component) throws IOException { - return toRGBImageAWT(raster, awtColorSpace); + return toRGBImageAWT(raster, awtColorSpace, targetColorSpace, component); } @Override diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDLab.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDLab.java index 38ca50ba416..46bc53ff14b 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDLab.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDLab.java @@ -61,7 +61,7 @@ public String getName() // WARNING: this method is performance sensitive, modify with care! // @Override - public BufferedImage toRGBImage(WritableRaster raster) throws IOException + public BufferedImage toRGBImage(WritableRaster raster, PDColorSpace targetColorSpace, int component) throws IOException { int width = raster.getWidth(); int height = raster.getHeight(); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDPattern.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDPattern.java index dd2a7f22de1..afb6307fea8 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDPattern.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDPattern.java @@ -96,7 +96,7 @@ public float[] toRGB(float[] value) } @Override - public BufferedImage toRGBImage(WritableRaster raster) throws IOException + public BufferedImage toRGBImage(WritableRaster raster, PDColorSpace targetColorSpace, int component) throws IOException { throw new UnsupportedOperationException(); } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDSeparation.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDSeparation.java index caf72f11348..ea3ea19a954 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDSeparation.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDSeparation.java @@ -133,7 +133,7 @@ public float[] toRGB(float[] value) throws IOException // WARNING: this method is performance sensitive, modify with care! // @Override - public BufferedImage toRGBImage(WritableRaster raster) throws IOException + public BufferedImage toRGBImage(WritableRaster raster, PDColorSpace targeColorSpace, int component) throws IOException { if (alternateColorSpace instanceof PDLab) { @@ -172,7 +172,7 @@ public BufferedImage toRGBImage(WritableRaster raster) throws IOException } // convert the alternate color space to RGB - return alternateColorSpace.toRGBImage(altRaster); + return alternateColorSpace.toRGBImage(altRaster, targeColorSpace, component); } // converter that works without using super implementation of toRGBImage() diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/form/PDFormXObject.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/form/PDFormXObject.java index 13c5aa4339b..bfeb97435f5 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/form/PDFormXObject.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/form/PDFormXObject.java @@ -192,6 +192,12 @@ public PDRectangle getBBox() if (array != null) { retval = new PDRectangle(array); + + // Make the Bounding Box larger to prevent clipping of the contents + retval.setLowerLeftX((float)Math.floor(retval.getLowerLeftX()) - 20); + retval.setLowerLeftY((float)Math.floor(retval.getLowerLeftY()) - 20); + retval.setUpperRightX((float)Math.ceil(retval.getUpperRightX()) + 20); + retval.setUpperRightY((float)Math.ceil(retval.getUpperRightY()) + 20); } return retval; } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImage.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImage.java index e76852c14bd..47ae1b5458e 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImage.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImage.java @@ -56,7 +56,7 @@ public interface PDImage extends COSObjectable * @return subsampled content of the requested subregion as a buffered image. * @throws IOException */ - BufferedImage getImage(Rectangle region, int subsampling) throws IOException; + BufferedImage getImage(Rectangle region, int subsampling, PDColorSpace targetColorSpace, int component) throws IOException; /** * Returns an ARGB image filled with the given paint and using this image as a mask. diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java index 03e0dc00376..bb065672d5a 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDImageXObject.java @@ -397,16 +397,16 @@ public void setStructParent(int key) @Override public BufferedImage getImage() throws IOException { - return getImage(null, 1); + return getImage(null, 1, null, -1); } /** * {@inheritDoc} */ @Override - public BufferedImage getImage(Rectangle region, int subsampling) throws IOException + public BufferedImage getImage(Rectangle region, int subsampling, PDColorSpace targetColorSpace, int component) throws IOException { - if (region == null && subsampling == cachedImageSubsampling && cachedImage != null) + if (region == null && subsampling == cachedImageSubsampling && cachedImage != null && targetColorSpace == null) { BufferedImage cached = cachedImage.get(); if (cached != null) @@ -415,7 +415,7 @@ public BufferedImage getImage(Rectangle region, int subsampling) throws IOExcept } } // get image as RGB - BufferedImage image = SampledImageReader.getRGBImage(this, region, subsampling, getColorKeyMask()); + BufferedImage image = SampledImageReader.getRGBImage(this, region, subsampling, getColorKeyMask(), targetColorSpace, component); // soft mask (overrides explicit mask) PDImageXObject softMask = getSoftMask(); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDInlineImage.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDInlineImage.java index 37f467e6a89..811b8aa8255 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDInlineImage.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/PDInlineImage.java @@ -350,9 +350,9 @@ public BufferedImage getImage() throws IOException } @Override - public BufferedImage getImage(Rectangle region, int subsampling) throws IOException + public BufferedImage getImage(Rectangle region, int subsampling, PDColorSpace targetColorSpace, int component) throws IOException { - return SampledImageReader.getRGBImage(this, region, subsampling, null); + return SampledImageReader.getRGBImage(this, region, subsampling, null, targetColorSpace, component); } @Override diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/SampledImageReader.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/SampledImageReader.java index eb8afc660fa..2b3ebb2b35a 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/SampledImageReader.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/SampledImageReader.java @@ -141,7 +141,7 @@ public static BufferedImage getStencilImage(PDImage pdImage, Paint paint) throws */ public static BufferedImage getRGBImage(PDImage pdImage, COSArray colorKey) throws IOException { - return getRGBImage(pdImage, null, 1, colorKey); + return getRGBImage(pdImage, null, 1, colorKey, null, -1); } private static Rectangle clipRegion(PDImage pdImage, Rectangle region) @@ -174,7 +174,7 @@ private static Rectangle clipRegion(PDImage pdImage, Rectangle region) * @throws IOException if the image cannot be read */ public static BufferedImage getRGBImage(PDImage pdImage, Rectangle region, int subsampling, - COSArray colorKey) throws IOException + COSArray colorKey, PDColorSpace targetColorSpace, int component) throws IOException { if (pdImage.isEmpty()) { @@ -197,7 +197,7 @@ public static BufferedImage getRGBImage(PDImage pdImage, Rectangle region, int s if (bitsPerComponent == 1 && colorKey == null && numComponents == 1) { - return from1Bit(pdImage, clipped, subsampling, width, height); + return from1Bit(pdImage, clipped, subsampling, width, height, targetColorSpace, component); } // @@ -212,13 +212,13 @@ public static BufferedImage getRGBImage(PDImage pdImage, Rectangle region, int s if (bitsPerComponent == 8 && Arrays.equals(decode, defaultDecode) && colorKey == null) { // convert image, faster path for non-decoded, non-colormasked 8-bit images - return from8bit(pdImage, raster, clipped, subsampling, width, height); + return from8bit(pdImage, raster, clipped, subsampling, width, height, targetColorSpace, component); } - return fromAny(pdImage, raster, colorKey, clipped, subsampling, width, height); + return fromAny(pdImage, raster, colorKey, clipped, subsampling, width, height, targetColorSpace, component); } private static BufferedImage from1Bit(PDImage pdImage, Rectangle clipped, final int subsampling, - final int width, final int height) throws IOException + final int width, final int height, PDColorSpace targetColorSpace, int component) throws IOException { int currentSubsampling = subsampling; final PDColorSpace colorSpace = pdImage.getColorSpace(); @@ -332,13 +332,13 @@ private static BufferedImage from1Bit(PDImage pdImage, Rectangle clipped, final } // use the color space to convert the image to RGB - return colorSpace.toRGBImage(raster); + return colorSpace.toRGBImage(raster, targetColorSpace, component); } } // faster, 8-bit non-decoded, non-colormasked image conversion private static BufferedImage from8bit(PDImage pdImage, WritableRaster raster, Rectangle clipped, final int subsampling, - final int width, final int height) throws IOException + final int width, final int height, PDColorSpace targetColorSpace, int component) throws IOException { int currentSubsampling = subsampling; DecodeOptions options = new DecodeOptions(currentSubsampling); @@ -380,7 +380,7 @@ private static BufferedImage from8bit(PDImage pdImage, WritableRaster raster, Re { LOG.debug("Tried reading " + width * height * (long) numComponents + " bytes but only " + inputResult + " bytes read"); } - return pdImage.getColorSpace().toRGBImage(raster); + return pdImage.getColorSpace().toRGBImage(raster, targetColorSpace, component); } // either subsampling is required, or reading only part of the image, so its @@ -423,13 +423,13 @@ private static BufferedImage from8bit(PDImage pdImage, WritableRaster raster, Re } } // use the color space to convert the image to RGB - return pdImage.getColorSpace().toRGBImage(raster); + return pdImage.getColorSpace().toRGBImage(raster, targetColorSpace, component); } } // slower, general-purpose image conversion from any image format private static BufferedImage fromAny(PDImage pdImage, WritableRaster raster, COSArray colorKey, Rectangle clipped, - final int subsampling, final int width, final int height) + final int subsampling, final int width, final int height, PDColorSpace targetColorSpace, int component) throws IOException { int currentSubsampling = subsampling; @@ -547,7 +547,7 @@ private static BufferedImage fromAny(PDImage pdImage, WritableRaster raster, COS } // use the color space to convert the image to RGB - BufferedImage rgbImage = colorSpace.toRGBImage(raster); + BufferedImage rgbImage = colorSpace.toRGBImage(raster, targetColorSpace, component); // apply color mask, if any if (colorKeyMask != null) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/rendering/PDFRenderer.java b/pdfbox/src/main/java/org/apache/pdfbox/rendering/PDFRenderer.java index 8ca2056f715..dd01c471a85 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/rendering/PDFRenderer.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/rendering/PDFRenderer.java @@ -20,6 +20,7 @@ import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.IOException; +import java.util.HashSet; import java.util.StringTokenizer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -29,6 +30,8 @@ import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.blend.BlendMode; +import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace; +import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation; import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; import org.apache.pdfbox.pdmodel.interactive.annotation.AnnotationFilter; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; @@ -42,6 +45,7 @@ public class PDFRenderer { private static final Log LOG = LogFactory.getLog(PDFRenderer.class); + private final HashSet separations = new HashSet(); protected final PDDocument document; // TODO keep rendering state such as caches here @@ -129,6 +133,13 @@ public void setSubsamplingAllowed(boolean subsamplingAllowed) this.subsamplingAllowed = subsamplingAllowed; } + /** + * Returns the set of color separations found in a page after rendering it + */ + public HashSet getSeparations() { + return separations; + } + /** * Returns the given page as an RGB image at 72 DPI * @param pageIndex the zero-based index of the page to be converted. @@ -140,6 +151,11 @@ public BufferedImage renderImage(int pageIndex) throws IOException return renderImage(pageIndex, 1); } + public BufferedImage renderImage(PDPage page) throws IOException + { + return renderImage(page, 1); + } + /** * Returns the given page as an RGB image at the given scale. * A scale of 1 will render at 72 DPI. @@ -150,8 +166,58 @@ public BufferedImage renderImage(int pageIndex) throws IOException */ public BufferedImage renderImage(int pageIndex, float scale) throws IOException { - return renderImage(pageIndex, scale, ImageType.RGB); + return renderImage(pageIndex, scale, ImageType.RGB, null, -1); + } + + public BufferedImage renderImage(PDPage page, float scale) throws IOException + { + return renderImage(page, scale, ImageType.RGB, null, -1); + } + + /** + * Returns the given page as an RGB image at 72 DPI + * @param pageIndex the zero-based index of the page to be converted. + * @param colorSpace the color space to render + * @return the rendered page image + * @throws IOException if the PDF cannot be read + */ + public BufferedImage renderImage(int pageIndex, PDColorSpace colorSpace) throws IOException + { + return renderImage(pageIndex, 1, colorSpace); + } + + public BufferedImage renderImage(PDPage page, float scale, PDColorSpace colorSpace) throws IOException + { + return renderImage(page, scale, ImageType.RGB, colorSpace, -1); + } + + public BufferedImage renderImage(int pageIndex, float scale, PDColorSpace colorSpace) throws IOException + { + return renderImage(pageIndex, scale, ImageType.RGB, colorSpace, -1); + } + + /** + * Returns the given page as an RGB image at 72 DPI + * @param pageIndex the zero-based index of the page to be converted. + * @param colorSpace the color space to render + * @param component the component of the color space to render + * @return the rendered page image + * @throws IOException if the PDF cannot be read + */ + public BufferedImage renderImage(int pageIndex, PDColorSpace colorSpace, int component) throws IOException + { + return renderImage(pageIndex, 1, colorSpace, component); } + + public BufferedImage renderImage(int pageIndex, float scale, PDColorSpace colorSpace, int component) throws IOException + { + return renderImage(pageIndex, scale, ImageType.RGB, colorSpace, component); + } + + public BufferedImage renderImage(PDPage page, float scale, PDColorSpace colorSpace, int component) throws IOException + { + return renderImage(page, scale, ImageType.RGB, colorSpace, component); + } /** * Returns the given page as an RGB image at the given DPI. @@ -162,7 +228,7 @@ public BufferedImage renderImage(int pageIndex, float scale) throws IOException */ public BufferedImage renderImageWithDPI(int pageIndex, float dpi) throws IOException { - return renderImage(pageIndex, dpi / 72f, ImageType.RGB); + return renderImage(pageIndex, dpi / 72f, ImageType.RGB, null, -1); } /** @@ -176,22 +242,20 @@ public BufferedImage renderImageWithDPI(int pageIndex, float dpi) throws IOExcep public BufferedImage renderImageWithDPI(int pageIndex, float dpi, ImageType imageType) throws IOException { - return renderImage(pageIndex, dpi / 72f, imageType); + return renderImage(pageIndex, dpi / 72f, imageType, null, -1); } /** * Returns the given page as an RGB or ARGB image at the given scale. - * @param pageIndex the zero-based index of the page to be converted + * @param page the page to be converted * @param scale the scaling factor, where 1 = 72 DPI * @param imageType the type of image to return * @return the rendered page image * @throws IOException if the PDF cannot be read */ - public BufferedImage renderImage(int pageIndex, float scale, ImageType imageType) + public BufferedImage renderImage(PDPage page, float scale, ImageType imageType, PDColorSpace colorSpace, int component) throws IOException { - PDPage page = document.getPage(pageIndex); - PDRectangle cropbBox = page.getCropBox(); float widthPt = cropbBox.getWidth(); float heightPt = cropbBox.getHeight(); @@ -240,7 +304,7 @@ public BufferedImage renderImage(int pageIndex, float scale, ImageType imageType transform(g, page, scale, scale); // the end-user may provide a custom PageDrawer - PageDrawerParameters parameters = new PageDrawerParameters(this, page, subsamplingAllowed); + PageDrawerParameters parameters = new PageDrawerParameters(this, page, subsamplingAllowed, colorSpace, component); PageDrawer drawer = createPageDrawer(parameters); drawer.drawPage(g, page.getCropBox()); @@ -262,6 +326,22 @@ public BufferedImage renderImage(int pageIndex, float scale, ImageType imageType return image; } + /** + * Returns the given page as an RGB or ARGB image at the given scale. + * @param pageIndex the zero-based index of the page to be converted + * @param scale the scaling factor, where 1 = 72 DPI + * @param imageType the type of image to return + * @return the rendered page image + * @throws IOException if the PDF cannot be read + */ + public BufferedImage renderImage(int pageIndex, float scale, ImageType imageType, PDColorSpace colorSpace, int component) + throws IOException + { + PDPage page = document.getPage(pageIndex); + + return renderImage(page, scale, imageType, colorSpace, component); + } + /** * Renders a given page to an AWT Graphics2D instance. * @param pageIndex the zero-based index of the page to be converted @@ -283,7 +363,7 @@ public void renderPageToGraphics(int pageIndex, Graphics2D graphics) throws IOEx public void renderPageToGraphics(int pageIndex, Graphics2D graphics, float scale) throws IOException { - renderPageToGraphics(pageIndex, graphics, scale, scale); + renderPageToGraphics(pageIndex, graphics, scale, scale, null, -1); } /** @@ -295,7 +375,7 @@ public void renderPageToGraphics(int pageIndex, Graphics2D graphics, float scale * @param scaleY the scale to draw the page at for the y-axis * @throws IOException if the PDF cannot be read */ - public void renderPageToGraphics(int pageIndex, Graphics2D graphics, float scaleX, float scaleY) + public void renderPageToGraphics(int pageIndex, Graphics2D graphics, float scaleX, float scaleY, PDColorSpace colorSpace, int component) throws IOException { PDPage page = document.getPage(pageIndex); @@ -307,7 +387,7 @@ public void renderPageToGraphics(int pageIndex, Graphics2D graphics, float scale graphics.clearRect(0, 0, (int) cropBox.getWidth(), (int) cropBox.getHeight()); // the end-user may provide a custom PageDrawer - PageDrawerParameters parameters = new PageDrawerParameters(this, page, subsamplingAllowed); + PageDrawerParameters parameters = new PageDrawerParameters(this, page, subsamplingAllowed, colorSpace, component); PageDrawer drawer = createPageDrawer(parameters); drawer.drawPage(graphics, cropBox); } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java b/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java index 08035bcddf5..ea1b5d507fa 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java @@ -69,8 +69,10 @@ import org.apache.pdfbox.pdmodel.graphics.color.PDColor; import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace; import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray; +import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB; import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased; import org.apache.pdfbox.pdmodel.graphics.color.PDPattern; +import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation; import org.apache.pdfbox.pdmodel.graphics.form.PDTransparencyGroup; import org.apache.pdfbox.pdmodel.graphics.image.PDImage; import org.apache.pdfbox.pdmodel.graphics.pattern.PDAbstractPattern; @@ -136,6 +138,12 @@ public class PageDrawer extends PDFGraphicsStreamEngine private final Stack transparencyGroupStack = new Stack<>(); + // separation color space + private PDColorSpace colorSpace; + + // separation color space component + private final int component; + /** * Default annotations filter, returns all annotations */ @@ -159,6 +167,8 @@ public PageDrawer(PageDrawerParameters parameters) throws IOException super(parameters.getPage()); this.renderer = parameters.getRenderer(); this.subsamplingAllowed = parameters.isSubsamplingAllowed(); + this.colorSpace = parameters.getColorSpace(); + this.component = parameters.getComponent(); } /** @@ -460,19 +470,26 @@ private void drawGlyph(GeneralPath path, PDFont font, int code, Vector displacem if (renderingMode.isFill()) { - graphics.setComposite(state.getNonStrokingJavaComposite()); - graphics.setPaint(getNonStrokingPaint()); - setClip(); - graphics.fill(glyph); + if (shouldDraw(state.getNonStrokingColor())) { + checkColors(); + graphics.setComposite(state.getNonStrokingJavaComposite()); + graphics.setPaint(getNonStrokingPaint()); + setClip(); + graphics.fill(glyph); + } } if (renderingMode.isStroke()) { - graphics.setComposite(state.getStrokingJavaComposite()); - graphics.setPaint(getStrokingPaint()); - graphics.setStroke(getStroke()); - setClip(); - graphics.draw(glyph); + if (shouldDraw(state.getStrokingColor())) { + checkColors(); + + graphics.setComposite(state.getStrokingJavaComposite()); + graphics.setPaint(getStrokingPaint()); + graphics.setStroke(getStroke()); + setClip(); + graphics.draw(glyph); + } } if (renderingMode.isClip()) @@ -497,8 +514,12 @@ public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) linePath.closePath(); } + private Paint applySoftMaskToPaint(Paint parentPaint, PDSoftMask softMask) throws IOException { + return applySoftMaskToPaint(parentPaint, softMask, 0, 0); + } + //TODO: move soft mask apply to getPaint()? - private Paint applySoftMaskToPaint(Paint parentPaint, PDSoftMask softMask) throws IOException + private Paint applySoftMaskToPaint(Paint parentPaint, PDSoftMask softMask, float x, float y) throws IOException { if (softMask == null || softMask.getGroup() == null) { @@ -541,7 +562,7 @@ else if (COSName.LUMINOSITY.equals(softMask.getSubType())) gray = adjustImage(gray); Rectangle2D tpgBounds = transparencyGroup.getBounds(); - adjustRectangle(tpgBounds); + adjustRectangle(tpgBounds, x, y); return new SoftMask(parentPaint, gray, tpgBounds, backdropColor, softMask.getTransferFunction()); } @@ -551,14 +572,15 @@ else if (COSName.LUMINOSITY.equals(softMask.getSubType())) // 2. change transparencyGroup.getBounds() to getOrigin(), because size isn't used in SoftMask // 3. Is it possible to create the softmask and transparency group in the correct rotation? // (needs rendering identity testing before committing!) - private void adjustRectangle(Rectangle2D r) + private void adjustRectangle(Rectangle2D r, float x, float y) { Matrix m = new Matrix(xform); double scaleX = m.getScalingFactorX(); double scaleY = m.getScalingFactorY(); - + AffineTransform adjustedTransform = new AffineTransform(xform); adjustedTransform.scale(1.0 / scaleX, 1.0 / scaleY); + adjustedTransform.translate(Math.round(-x), Math.round(-y)); r.setRect(adjustedTransform.createTransformedShape(r).getBounds2D()); } @@ -652,18 +674,26 @@ private BasicStroke getStroke() @Override public void strokePath() throws IOException { + checkColors(); + graphics.setComposite(getGraphicsState().getStrokingJavaComposite()); graphics.setPaint(getStrokingPaint()); graphics.setStroke(getStroke()); setClip(); - //TODO bbox of shading pattern should be used here? (see fillPath) - graphics.draw(linePath); + + if (shouldDraw(getGraphicsState().getStrokingColor())) { + //TODO bbox of shading pattern should be used here? (see fillPath) + graphics.draw(linePath); + } + linePath.reset(); } @Override public void fillPath(int windingRule) throws IOException { + checkColors(); + graphics.setComposite(getGraphicsState().getNonStrokingJavaComposite()); graphics.setPaint(getNonStrokingPaint()); setClip(); @@ -688,9 +718,12 @@ public void fillPath(int windingRule) throws IOException Area area = new Area(linePath); area.intersect(new Area(graphics.getClip())); intersectShadingBBox(getGraphicsState().getNonStrokingColor(), area); - graphics.fill(area); + + if (shouldDraw(getGraphicsState().getNonStrokingColor())) { + graphics.fill(area); + } } - else + else if (shouldDraw(getGraphicsState().getNonStrokingColor())) { graphics.fill(linePath); } @@ -958,12 +991,12 @@ public void drawImage(PDImage pdImage) throws IOException { int subsampling = getSubsampling(pdImage, at); // draw the subsampled image - drawBufferedImage(pdImage.getImage(null, subsampling), at); + drawBufferedImage(pdImage.getImage(null, subsampling, colorSpace, component), at); } else { // subsampling not allowed, draw the image - drawBufferedImage(pdImage.getImage(), at); + drawBufferedImage(pdImage.getImage(null, 1, colorSpace, component), at); } } @@ -1234,12 +1267,21 @@ public void showTransparencyGroup(PDTransparencyGroup form) throws IOException PDSoftMask softMask = getGraphicsState().getSoftMask(); if (softMask != null) { - Paint awtPaint = new TexturePaint(image, - new Rectangle2D.Float(0, 0, image.getWidth(), image.getHeight())); - awtPaint = applySoftMaskToPaint(awtPaint, softMask); - graphics.setPaint(awtPaint); - graphics.fill( - new Rectangle2D.Float(0, 0, bbox.getWidth() * xScale, bbox.getHeight() * yScale)); + BufferedImage img = new BufferedImage((int)(bbox.getWidth() * xScale), (int)(bbox.getHeight() * yScale), BufferedImage.TYPE_INT_ARGB); + Paint awtPaint = new TexturePaint(image, new Rectangle2D.Float(0, 0, image.getWidth(), image.getHeight())); + Graphics2D g = img.createGraphics(); + awtPaint = applySoftMaskToPaint(awtPaint, softMask, x * xScale, y * yScale); + try { + g.setPaint(awtPaint); + g.fill( + new Rectangle2D.Float(0, 0, img.getWidth(), img.getHeight()) + ); + } + finally { + g.dispose(); + } + + graphics.drawImage(img, null, null); } else { @@ -1557,4 +1599,88 @@ private boolean hasBlendMode(PDTransparencyGroup group, Set groupsDone) return false; } + + private void checkColors() { + PDColor white = PDDeviceRGB.INSTANCE.getWhite(); + PDGraphicsState graphicsState = getGraphicsState(); + PDColor sColor = graphicsState.getStrokingColor(); + PDColor nsColor = graphicsState.getNonStrokingColor(); + PDColorSpace sColorSpace = sColor.getColorSpace(); + PDColorSpace nsColorSpace = nsColor.getColorSpace(); + + if (colorSpace == null) { + if (sColorSpace instanceof PDSeparation) { + getRenderer().getSeparations().add((PDSeparation)sColorSpace); + } + + if (nsColorSpace instanceof PDSeparation) { + getRenderer().getSeparations().add((PDSeparation)nsColorSpace); + } + + return; + } + + if (!isRegistrationColor(sColor)) { + if (sColorSpace != colorSpace) { + graphicsState.setStrokingColor(white); + } + else if (component >= 0) { + float[] components = sColor.getComponents(); + + for (int i = 0; i < components.length; i++) { + if (i != component) { + components[i] = 0; + } + } + + graphicsState.setStrokingColor(new PDColor(components, sColorSpace)); + } + } + + if (!isRegistrationColor(nsColor)) { + if (nsColorSpace != colorSpace) { + graphicsState.setNonStrokingColor(white); + } + else if (component >= 0) { + float[] components = nsColor.getComponents(); + + for (int i = 0; i < components.length; i++) { + if (i != component) { + components[i] = 0; + } + } + + graphicsState.setNonStrokingColor(new PDColor(components, nsColorSpace)); + } + } + } + + private boolean isRegistrationColor(PDColor color) { + PDColorSpace colorSpace = color.getColorSpace(); + + if (colorSpace instanceof PDSeparation) { + PDSeparation separation = (PDSeparation)colorSpace; + + if (separation.getColorantName().equals("All")) { + return true; + } + } + + return false; + } + + private boolean shouldDraw(PDColor color) { + if (colorSpace == null) { + return true; + } + + PDColorSpace cs = color.getColorSpace(); + boolean isOverprint = getGraphicsState().isOverprint() || getGraphicsState().isNonStrokingOverprint(); + + if (isOverprint && colorSpace != cs && !isRegistrationColor(color)) { + return false; + } + + return true; + } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawerParameters.java b/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawerParameters.java index 48d4428b9f6..c686b058c59 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawerParameters.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawerParameters.java @@ -18,6 +18,7 @@ package org.apache.pdfbox.rendering; import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace; /** * Parameters for a PageDrawer. This class ensures allows PDFRenderer and PageDrawer to share @@ -31,15 +32,19 @@ public final class PageDrawerParameters private final PDFRenderer renderer; private final PDPage page; private final boolean subsamplingAllowed; + private final PDColorSpace colorSpace; + private final int component; /** * Package-private constructor. */ - PageDrawerParameters(PDFRenderer renderer, PDPage page, boolean subsamplingAllowed) + PageDrawerParameters(PDFRenderer renderer, PDPage page, boolean subsamplingAllowed, PDColorSpace colorSpace, int component) { this.renderer = renderer; this.page = page; this.subsamplingAllowed = subsamplingAllowed; + this.colorSpace = colorSpace; + this.component = component; } /** @@ -65,4 +70,18 @@ public boolean isSubsamplingAllowed() { return subsamplingAllowed; } + + /** + * Returns the separation color space or null when rendering composite + */ + public PDColorSpace getColorSpace() { + return colorSpace; + } + + /** + * Returns the component of the separation color space or -1 when rendering all components + */ + public int getComponent() { + return component; + } } diff --git a/pdfbox/src/test/java/org/apache/pdfbox/rendering/TestSeparations.java b/pdfbox/src/test/java/org/apache/pdfbox/rendering/TestSeparations.java new file mode 100644 index 00000000000..cc56ca8ba1a --- /dev/null +++ b/pdfbox/src/test/java/org/apache/pdfbox/rendering/TestSeparations.java @@ -0,0 +1,143 @@ +/* + * 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. + */ + +package org.apache.pdfbox.rendering; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import javax.imageio.ImageIO; + +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDResources; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace; +import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK; +import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation; +import org.junit.Test; + +public class TestSeparations { + private static final String INPUT_DIR = "/input/rendering"; + private static String OUTPUT_DIR = "src/test/resources/output/rendering"; + + @Test + public void test() throws IOException, URISyntaxException { + float maxSize = 2048; + float maxScale = 300 / 72f; + String filename = "FANTASTICCMYK.ai"; + Path filePath = Paths.get(getClass().getResource(INPUT_DIR + "/" + filename).toURI()); + File file = filePath.toFile(); + PDDocument document = PDDocument.load(file); + PDRectangle cropBox = document.getPage(0).getCropBox(); + float wScale = maxSize / cropBox.getWidth(); + float hScale = maxSize / cropBox.getHeight(); + float scale = wScale < hScale ? wScale : hScale; + + if (scale > maxScale) { + scale = maxScale; + } + + PDPage page = document.getPage(0); + PDFRenderer renderer = new PDFRenderer(document); + + //renderSeparation(scale, filename, document, renderer, "PANTONE Orange 021 C"); + + renderComposite(page, filename, renderer, scale); + + renderCMYKSeparations(page, filename, renderer, scale); + + renderSpotColorSeparations(page, filename, renderer, scale); + + document.close(); + } + + private void renderSeparation(float scale, String filename, PDDocument document, PDFRenderer renderer, String colorant) throws IOException { + PDResources pageResources = document.getPage(0).getResources(); + + for (COSName csName : pageResources.getColorSpaceNames()) { + PDColorSpace colorSpace = pageResources.getColorSpace(csName); + + if (colorSpace instanceof PDSeparation) { + PDSeparation separation = (PDSeparation)colorSpace; + + String colorantName = separation.getColorantName(); + + if (colorantName.equals(colorant)) { + BufferedImage image = renderer.renderImage(0, scale, separation, -1); + + writeImage(image, OUTPUT_DIR + "/" + filename + "." + colorantName); + } + } + } + } + + private void renderSpotColorSeparations(PDPage page, String filename, PDFRenderer renderer, float scale) throws IOException { + PDSeparation[] separations = new PDSeparation[renderer.getSeparations().size()]; + + separations = renderer.getSeparations().toArray(separations); + + for (PDSeparation separation : separations) { + String colorant = separation.getColorantName(); + + if (colorant.equals("All")) { + continue; + } + + BufferedImage image = renderer.renderImage(page, scale, separation); + + writeImage(image, OUTPUT_DIR + "/" + filename + "." + colorant, "png"); + } + } + + private void renderComposite(PDPage page, String filename, PDFRenderer renderer, float scale) throws IOException { + BufferedImage image = renderer.renderImage(page, scale); + + writeImage(image, OUTPUT_DIR + "/" + filename, "png"); + } + + private void renderCMYKSeparations(PDPage page, String filename, PDFRenderer renderer, float scale) throws IOException { + String[] processColors = new String[] { "Cyan", "Magenta", "Yellow", "Black" }; + + for (int i = 0; i < processColors.length; i++) { + BufferedImage image = renderer.renderImage(page, scale, PDDeviceCMYK.INSTANCE, i); + + writeImage(image, OUTPUT_DIR + "/" + filename + "." + processColors[i], "png"); + } + } + + private void writeImage(BufferedImage image, String filename, String extension) throws IOException { + FileOutputStream outputFile = new FileOutputStream(filename + "." + extension); + + try { + ImageIO.write(image, extension, outputFile); + } + finally { + outputFile.close(); + } + } + + private void writeImage(BufferedImage image, String filename) throws IOException { + writeImage(image, filename, "jpg"); + } +} \ No newline at end of file diff --git a/pdfbox/src/test/resources/output/rendering/.gitignore b/pdfbox/src/test/resources/output/rendering/.gitignore new file mode 100644 index 00000000000..c96a04f008e --- /dev/null +++ b/pdfbox/src/test/resources/output/rendering/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file