You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2018/06/30 16:50:25 UTC
svn commit: r1834752 - in
/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering:
GroupGraphics.java PDFRenderer.java PageDrawer.java
Author: tilman
Date: Sat Jun 30 16:50:25 2018
New Revision: 1834752
URL: http://svn.apache.org/viewvc?rev=1834752&view=rev
Log:
PDFBOX-3000: support rendering of blend modes in non-isolated transparency groups, by Jani Pehkonen
Added:
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/GroupGraphics.java (with props)
Modified:
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PDFRenderer.java
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java
Added: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/GroupGraphics.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/GroupGraphics.java?rev=1834752&view=auto
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/GroupGraphics.java (added)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/GroupGraphics.java Sat Jun 30 16:50:25 2018
@@ -0,0 +1,728 @@
+/*
+ * 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.Color;
+import java.awt.Composite;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.Image;
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferInt;
+import java.awt.image.ImageObserver;
+import java.awt.image.RenderedImage;
+import java.awt.image.renderable.RenderableImage;
+import java.text.AttributedCharacterIterator;
+import java.util.Map;
+
+/**
+ * Graphics implementation for non-isolated transparency groups.
+ * <p>
+ * Non-isolated groups require that the group backdrop (copied from parent group or
+ * page) is used as the initial contents of the image to which the group is rendered.
+ * This allows blend modes to blend the group contents with the graphics behind
+ * the group. Finally when the group rendering is done, backdrop removal must be
+ * computed (see {@link #removeBackdrop(java.awt.image.BufferedImage, int, int) removeBackdrop}).
+ * It ensures the backdrop is not rendered twice on the parent but it leaves the
+ * effects of blend modes.
+ * <p>
+ * This class renders the group contents to two images. <code>groupImage</code> is
+ * initialized with the backdrop and group contents are drawn over it.
+ * <code>groupAlphaImage</code> is initially fully transparent and it accumulates
+ * the total alpha of the group contents excluding backdrop.
+ * <p>
+ * If a non-isolated group uses only the blend mode Normal, it can be optimized
+ * and rendered like an isolated group; backdrop usage and removal are not needed.
+ */
+
+class GroupGraphics extends Graphics2D
+{
+ private final BufferedImage groupImage;
+ private final BufferedImage groupAlphaImage;
+ private final Graphics2D groupGraphics;
+ private final Graphics2D alphaGraphics;
+
+ GroupGraphics(BufferedImage groupImage, Graphics2D groupGraphics)
+ {
+ this.groupImage = groupImage;
+ this.groupGraphics = groupGraphics;
+ this.groupAlphaImage = new BufferedImage(groupImage.getWidth(), groupImage.getHeight(),
+ BufferedImage.TYPE_INT_ARGB);
+ this.alphaGraphics = groupAlphaImage.createGraphics();
+ }
+
+ private GroupGraphics(BufferedImage groupImage, Graphics2D groupGraphics,
+ BufferedImage groupAlphaImage, Graphics2D alphaGraphics)
+ {
+ this.groupImage = groupImage;
+ this.groupGraphics = groupGraphics;
+ this.groupAlphaImage = groupAlphaImage;
+ this.alphaGraphics = alphaGraphics;
+ }
+
+ @Override
+ public void clearRect(int x, int y, int width, int height)
+ {
+ groupGraphics.clearRect(x, y, width, height);
+ alphaGraphics.clearRect(x, y, width, height);
+ }
+
+ @Override
+ public void clipRect(int x, int y, int width, int height)
+ {
+ groupGraphics.clipRect(x, y, width, height);
+ alphaGraphics.clipRect(x, y, width, height);
+ }
+
+ @Override
+ public void copyArea(int x, int y, int width, int height, int dx, int dy)
+ {
+ groupGraphics.copyArea(x, y, width, height, dx, dy);
+ alphaGraphics.copyArea(x, y, width, height, dx, dy);
+ }
+
+ @Override
+ public Graphics create()
+ {
+ Graphics g = groupGraphics.create();
+ Graphics a = alphaGraphics.create();
+ if (g instanceof Graphics2D && a instanceof Graphics2D)
+ {
+ return new GroupGraphics(groupImage, (Graphics2D)g, groupAlphaImage, (Graphics2D)a);
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void dispose()
+ {
+ groupGraphics.dispose();
+ alphaGraphics.dispose();
+ }
+
+ @Override
+ public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)
+ {
+ groupGraphics.drawArc(x, y, width, height, startAngle, arcAngle);
+ alphaGraphics.drawArc(x, y, width, height, startAngle, arcAngle);
+ }
+
+ @Override
+ public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer)
+ {
+ groupGraphics.drawImage(img, x, y, bgcolor, observer);
+ return alphaGraphics.drawImage(img, x, y, bgcolor, observer);
+ }
+
+ @Override
+ public boolean drawImage(Image img, int x, int y, ImageObserver observer)
+ {
+ groupGraphics.drawImage(img, x, y, observer);
+ return alphaGraphics.drawImage(img, x, y, observer);
+ }
+
+ @Override
+ public boolean drawImage(Image img, int x, int y, int width, int height,
+ Color bgcolor, ImageObserver observer)
+ {
+ groupGraphics.drawImage(img, x, y, width, height, bgcolor, observer);
+ return alphaGraphics.drawImage(img, x, y, width, height, bgcolor, observer);
+ }
+
+ @Override
+ public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)
+ {
+ groupGraphics.drawImage(img, x, y, width, height, observer);
+ return alphaGraphics.drawImage(img, x, y, width, height, observer);
+ }
+
+ @Override
+ public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1,
+ int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer)
+ {
+ groupGraphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer);
+ return alphaGraphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer);
+ }
+
+ @Override
+ public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1,
+ int sy1, int sx2, int sy2, ImageObserver observer)
+ {
+ groupGraphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer);
+ return alphaGraphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer);
+ }
+
+ @Override
+ public void drawLine(int x1, int y1, int x2, int y2)
+ {
+ groupGraphics.drawLine(x1, y1, x2, y2);
+ alphaGraphics.drawLine(x1, y1, x2, y2);
+ }
+
+ @Override
+ public void drawOval(int x, int y, int width, int height)
+ {
+ groupGraphics.drawOval(x, y, width, height);
+ alphaGraphics.drawOval(x, y, width, height);
+ }
+
+ @Override
+ public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
+ {
+ groupGraphics.drawPolygon(xPoints, yPoints, nPoints);
+ alphaGraphics.drawPolygon(xPoints, yPoints, nPoints);
+ }
+
+ @Override
+ public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
+ {
+ groupGraphics.drawPolyline(xPoints, yPoints, nPoints);
+ alphaGraphics.drawPolyline(xPoints, yPoints, nPoints);
+ }
+
+ @Override
+ public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
+ {
+ groupGraphics.drawRoundRect(x, y, width, height, arcWidth, arcHeight);
+ alphaGraphics.drawRoundRect(x, y, width, height, arcWidth, arcHeight);
+ }
+
+ @Override
+ public void drawString(AttributedCharacterIterator iterator, int x, int y)
+ {
+ groupGraphics.drawString(iterator, x, y);
+ alphaGraphics.drawString(iterator, x, y);
+ }
+
+ @Override
+ public void drawString(String str, int x, int y)
+ {
+ groupGraphics.drawString(str, x, y);
+ alphaGraphics.drawString(str, x, y);
+ }
+
+ @Override
+ public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle)
+ {
+ groupGraphics.fillArc(x, y, width, height, startAngle, arcAngle);
+ alphaGraphics.fillArc(x, y, width, height, startAngle, arcAngle);
+ }
+
+ @Override
+ public void fillOval(int x, int y, int width, int height)
+ {
+ groupGraphics.fillOval(x, y, width, height);
+ alphaGraphics.fillOval(x, y, width, height);
+ }
+
+ @Override
+ public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
+ {
+ groupGraphics.fillPolygon(xPoints, yPoints, nPoints);
+ alphaGraphics.fillPolygon(xPoints, yPoints, nPoints);
+ }
+
+ @Override
+ public void fillRect(int x, int y, int width, int height)
+ {
+ groupGraphics.fillRect(x, y, width, height);
+ alphaGraphics.fillRect(x, y, width, height);
+ }
+
+ @Override
+ public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
+ {
+ groupGraphics.fillRoundRect(x, y, width, height, arcWidth, arcHeight);
+ alphaGraphics.fillRoundRect(x, y, width, height, arcWidth, arcHeight);
+ }
+
+ @Override
+ public Shape getClip()
+ {
+ return groupGraphics.getClip();
+ }
+
+ @Override
+ public Rectangle getClipBounds()
+ {
+ return groupGraphics.getClipBounds();
+ }
+
+ @Override
+ public Color getColor()
+ {
+ return groupGraphics.getColor();
+ }
+
+ @Override
+ public Font getFont()
+ {
+ return groupGraphics.getFont();
+ }
+
+ @Override
+ public FontMetrics getFontMetrics(Font f)
+ {
+ return groupGraphics.getFontMetrics(f);
+ }
+
+ @Override
+ public void setClip(int x, int y, int width, int height)
+ {
+ groupGraphics.setClip(x, y, width, height);
+ alphaGraphics.setClip(x, y, width, height);
+ }
+
+ @Override
+ public void setClip(Shape clip)
+ {
+ groupGraphics.setClip(clip);
+ alphaGraphics.setClip(clip);
+ }
+
+ @Override
+ public void setColor(Color c)
+ {
+ groupGraphics.setColor(c);
+ alphaGraphics.setColor(c);
+ }
+
+ @Override
+ public void setFont(Font font)
+ {
+ groupGraphics.setFont(font);
+ alphaGraphics.setFont(font);
+ }
+
+ @Override
+ public void setPaintMode()
+ {
+ groupGraphics.setPaintMode();
+ alphaGraphics.setPaintMode();
+ }
+
+ @Override
+ public void setXORMode(Color c1)
+ {
+ groupGraphics.setXORMode(c1);
+ alphaGraphics.setXORMode(c1);
+ }
+
+ @Override
+ public void translate(int x, int y)
+ {
+ groupGraphics.translate(x, y);
+ alphaGraphics.translate(x, y);
+ }
+
+ @Override
+ public void addRenderingHints(Map<?,?> hints)
+ {
+ groupGraphics.addRenderingHints(hints);
+ alphaGraphics.addRenderingHints(hints);
+ }
+
+ @Override
+ public void clip(Shape s)
+ {
+ groupGraphics.clip(s);
+ alphaGraphics.clip(s);
+ }
+
+ @Override
+ public void draw(Shape s)
+ {
+ groupGraphics.draw(s);
+ alphaGraphics.draw(s);
+ }
+
+ @Override
+ public void drawGlyphVector(GlyphVector g, float x, float y)
+ {
+ groupGraphics.drawGlyphVector(g, x, y);
+ alphaGraphics.drawGlyphVector(g, x, y);
+ }
+
+ @Override
+ public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y)
+ {
+ groupGraphics.drawImage(img, op, x, y);
+ alphaGraphics.drawImage(img, op, x, y);
+ }
+
+ @Override
+ public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)
+ {
+ groupGraphics.drawImage(img, xform, obs);
+ return alphaGraphics.drawImage(img, xform, obs);
+ }
+
+ @Override
+ public void drawRenderableImage(RenderableImage img, AffineTransform xform)
+ {
+ groupGraphics.drawRenderableImage(img, xform);
+ alphaGraphics.drawRenderableImage(img, xform);
+ }
+
+ @Override
+ public void drawRenderedImage(RenderedImage img, AffineTransform xform)
+ {
+ groupGraphics.drawRenderedImage(img, xform);
+ alphaGraphics.drawRenderedImage(img, xform);
+ }
+
+ @Override
+ public void drawString(AttributedCharacterIterator iterator, float x, float y)
+ {
+ groupGraphics.drawString(iterator, x, y);
+ alphaGraphics.drawString(iterator, x, y);
+ }
+
+ @Override
+ public void drawString(String str, float x, float y)
+ {
+ groupGraphics.drawString(str, x, y);
+ alphaGraphics.drawString(str, x, y);
+ }
+
+ @Override
+ public void fill(Shape s)
+ {
+ groupGraphics.fill(s);
+ alphaGraphics.fill(s);
+ }
+
+ @Override
+ public Color getBackground()
+ {
+ return groupGraphics.getBackground();
+ }
+
+ @Override
+ public Composite getComposite()
+ {
+ return groupGraphics.getComposite();
+ }
+
+ @Override
+ public GraphicsConfiguration getDeviceConfiguration()
+ {
+ return groupGraphics.getDeviceConfiguration();
+ }
+
+ @Override
+ public FontRenderContext getFontRenderContext()
+ {
+ return groupGraphics.getFontRenderContext();
+ }
+
+ @Override
+ public Paint getPaint()
+ {
+ return groupGraphics.getPaint();
+ }
+
+ @Override
+ public Object getRenderingHint(RenderingHints.Key hintKey)
+ {
+ return groupGraphics.getRenderingHint(hintKey);
+ }
+
+ @Override
+ public RenderingHints getRenderingHints()
+ {
+ return groupGraphics.getRenderingHints();
+ }
+
+ @Override
+ public Stroke getStroke()
+ {
+ return groupGraphics.getStroke();
+ }
+
+ @Override
+ public AffineTransform getTransform()
+ {
+ return groupGraphics.getTransform();
+ }
+
+ @Override
+ public boolean hit(Rectangle rect, Shape s, boolean onStroke)
+ {
+ return groupGraphics.hit(rect, s, onStroke);
+ }
+
+ @Override
+ public void rotate(double theta)
+ {
+ groupGraphics.rotate(theta);
+ alphaGraphics.rotate(theta);
+ }
+
+ @Override
+ public void rotate(double theta, double x, double y)
+ {
+ groupGraphics.rotate(theta, x, y);
+ alphaGraphics.rotate(theta, x, y);
+ }
+
+ @Override
+ public void scale(double sx, double sy)
+ {
+ groupGraphics.scale(sx, sy);
+ alphaGraphics.scale(sx, sy);
+ }
+
+ @Override
+ public void setBackground(Color color)
+ {
+ groupGraphics.setBackground(color);
+ alphaGraphics.setBackground(color);
+ }
+
+ @Override
+ public void setComposite(Composite comp)
+ {
+ groupGraphics.setComposite(comp);
+ alphaGraphics.setComposite(comp);
+ }
+
+ @Override
+ public void setPaint(Paint paint)
+ {
+ groupGraphics.setPaint(paint);
+ alphaGraphics.setPaint(paint);
+ }
+
+ @Override
+ public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
+ {
+ groupGraphics.setRenderingHint(hintKey, hintValue);
+ alphaGraphics.setRenderingHint(hintKey, hintValue);
+ }
+
+ @Override
+ public void setRenderingHints(Map<?, ?> hints)
+ {
+ groupGraphics.setRenderingHints(hints);
+ alphaGraphics.setRenderingHints(hints);
+ }
+
+ @Override
+ public void setStroke(Stroke s)
+ {
+ groupGraphics.setStroke(s);
+ alphaGraphics.setStroke(s);
+ }
+
+ @Override
+ public void setTransform(AffineTransform tx)
+ {
+ groupGraphics.setTransform(tx);
+ alphaGraphics.setTransform(tx);
+ }
+
+ @Override
+ public void shear(double shx, double shy)
+ {
+ groupGraphics.shear(shx, shy);
+ alphaGraphics.shear(shx, shy);
+ }
+
+ @Override
+ public void transform(AffineTransform tx)
+ {
+ groupGraphics.transform(tx);
+ alphaGraphics.transform(tx);
+ }
+
+ @Override
+ public void translate(double tx, double ty)
+ {
+ groupGraphics.translate(tx, ty);
+ alphaGraphics.translate(tx, ty);
+ }
+
+ /**
+ * Computes backdrop removal.
+ * The backdrop removal equation is given in section 11.4.4 in the PDF 32000-1:2008
+ * standard. It returns the final color <code>C</code> for each pixel in the group:<br>
+ * <code>C = Cn + (Cn - C0) * (alpha0 / alphagn - alpha0)</code><br>
+ * where<br>
+ * <code>Cn</code> is the group color including backdrop (read from <code>groupImage</code>),<br>
+ * <code>C0</code> is the backdrop color,<br>
+ * <code>alpha0</code> is the backdrop alpha,<br>
+ * <code>alphagn</code> is the group alpha excluding backdrop (read the
+ * alpha channel from <code>groupAlphaImage</code>)<br>
+ * <p>
+ * The alpha of the result is equal to <code>alphagn</code>, i.e., the alpha
+ * channel of <code>groupAlphaImage</code>.
+ * <p>
+ * The <code>backdrop</code> image may be much larger than <code>groupImage</code> if,
+ * for example, the current page is used as the backdrop. Only a specific rectangular
+ * region of <code>backdrop</code> is used in the backdrop removal: upper-left corner
+ * is at <code>(offsetX, offsetY)</code>; width and height are equal to those of
+ * <code>groupImage</code>.
+ *
+ * @param backdrop group backdrop
+ * @param offsetX backdrop left X coordinate
+ * @param offsetY backdrop upper Y coordinate
+ */
+ void removeBackdrop(BufferedImage backdrop, int offsetX, int offsetY)
+ {
+ int groupWidth = groupImage.getWidth();
+ int groupHeight = groupImage.getHeight();
+ int backdropWidth = backdrop.getWidth();
+ int backdropHeight = backdrop.getHeight();
+ int groupType = groupImage.getType();
+ int groupAlphaType = groupAlphaImage.getType();
+ int backdropType = backdrop.getType();
+ DataBuffer groupDataBuffer = groupImage.getRaster().getDataBuffer();
+ DataBuffer groupAlphaDataBuffer = groupAlphaImage.getRaster().getDataBuffer();
+ DataBuffer backdropDataBuffer = backdrop.getRaster().getDataBuffer();
+
+ if (groupType == BufferedImage.TYPE_INT_ARGB &&
+ groupAlphaType == BufferedImage.TYPE_INT_ARGB &&
+ (backdropType == BufferedImage.TYPE_INT_ARGB || backdropType == BufferedImage.TYPE_INT_RGB) &&
+ groupDataBuffer instanceof DataBufferInt &&
+ groupAlphaDataBuffer instanceof DataBufferInt &&
+ backdropDataBuffer instanceof DataBufferInt)
+ {
+ // Optimized computation for int[] buffers.
+
+ int[] groupData = ((DataBufferInt)groupDataBuffer).getData();
+ int[] groupAlphaData = ((DataBufferInt)groupAlphaDataBuffer).getData();
+ int[] backdropData = ((DataBufferInt)backdropDataBuffer).getData();
+ boolean backdropHasAlpha = backdropType == BufferedImage.TYPE_INT_ARGB;
+
+ for (int y = 0; y < groupHeight; y++)
+ {
+ for (int x = 0; x < groupWidth; x++)
+ {
+ int index = x + y * groupWidth;
+
+ // alphagn is the total alpha of the group contents excluding backdrop.
+ int alphagn = (groupAlphaData[index] >> 24) & 0xFF;
+ if (alphagn == 0)
+ {
+ // Avoid division by 0 and set the result to fully transparent.
+ groupData[index] = 0;
+ continue;
+ }
+
+ int backdropX = x + offsetX;
+ int backdropY = y + offsetY;
+ int backdropRGB; // color of backdrop pixel
+ float alpha0; // alpha of backdrop pixel
+
+ if (backdropX >= 0 && backdropX < backdropWidth &&
+ backdropY >= 0 && backdropY < backdropHeight)
+ {
+ backdropRGB = backdropData[backdropX + backdropY * backdropWidth];
+ alpha0 = backdropHasAlpha ? ((backdropRGB >> 24) & 0xFF) : 255;
+ }
+ else
+ {
+ // Backdrop pixel is out of bounds. Use a transparent value.
+ backdropRGB = 0;
+ alpha0 = 0;
+ }
+
+ // Alpha factor alpha0 / alphagn - alpha0 is in range 0.0-1.0.
+ float alphaFactor = alpha0 / (float)alphagn - alpha0 / 255.0f;
+ int groupRGB = groupData[index]; // color of group pixel
+
+ // Compute backdrop removal for RGB components.
+ int r = backdropRemoval(groupRGB, backdropRGB, 16, alphaFactor);
+ int g = backdropRemoval(groupRGB, backdropRGB, 8, alphaFactor);
+ int b = backdropRemoval(groupRGB, backdropRGB, 0, alphaFactor);
+
+ // Copy the result back to groupImage. The alpha of the result
+ // is equal to alphagn.
+ groupData[index] = (alphagn << 24) | (r << 16) | (g << 8) | b;
+ }
+ }
+ }
+ else
+ {
+ // Non-optimized computation for other types of color spaces and pixel buffers.
+
+ for (int y = 0; y < groupHeight; y++)
+ {
+ for (int x = 0; x < groupWidth; x++)
+ {
+ int alphagn = (groupAlphaImage.getRGB(x, y) >> 24) & 0xFF;
+ if (alphagn == 0)
+ {
+ groupImage.setRGB(x, y, 0);
+ continue;
+ }
+
+ int backdropX = x + offsetX;
+ int backdropY = y + offsetY;
+ int backdropRGB;
+ float alpha0;
+ if (backdropX >= 0 && backdropX < backdropWidth &&
+ backdropY >= 0 && backdropY < backdropHeight)
+ {
+ backdropRGB = backdrop.getRGB(backdropX, backdropY);
+ alpha0 = (backdropRGB >> 24) & 0xFF;
+ }
+ else
+ {
+ backdropRGB = 0;
+ alpha0 = 0;
+ }
+
+ int groupRGB = groupImage.getRGB(x, y);
+ float alphaFactor = alpha0 / alphagn - alpha0 / 255.0f;
+
+ int r = backdropRemoval(groupRGB, backdropRGB, 16, alphaFactor);
+ int g = backdropRemoval(groupRGB, backdropRGB, 8, alphaFactor);
+ int b = backdropRemoval(groupRGB, backdropRGB, 0, alphaFactor);
+
+ groupImage.setRGB(x, y, (alphagn << 24) | (r << 16) | (g << 8) | b);
+ }
+ }
+ }
+ }
+
+ /**
+ * Computes the backdrop removal equation.
+ * <code>C = Cn + (Cn - C0) * (alpha0 / alphagn - alpha0)</code>
+ */
+ private int backdropRemoval(int groupRGB, int backdropRGB, int shift, float alphaFactor)
+ {
+ float cn = (groupRGB >> shift) & 0xFF;
+ float c0 = (backdropRGB >> shift) & 0xFF;
+ int c = Math.round(cn + (cn - c0) * alphaFactor);
+ return (c < 0) ? 0 : (c > 255 ? 255 : c);
+ }
+}
Propchange: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/GroupGraphics.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PDFRenderer.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PDFRenderer.java?rev=1834752&r1=1834751&r2=1834752&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PDFRenderer.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PDFRenderer.java Sat Jun 30 16:50:25 2018
@@ -1,363 +1,377 @@
-/*
- * 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.Color;
-import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
-import java.io.IOException;
-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.blend.BlendMode;
-import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
-import org.apache.pdfbox.pdmodel.interactive.annotation.AnnotationFilter;
-import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
-
-/**
- * Renders a PDF document to an AWT BufferedImage.
- * This class may be overridden in order to perform custom rendering.
- *
- * @author John Hewson
- */
-public class PDFRenderer
-{
- protected final PDDocument document;
- // TODO keep rendering state such as caches here
-
- /**
- * Default annotations filter, returns all annotations
- */
- private AnnotationFilter annotationFilter = new AnnotationFilter()
- {
- @Override
- public boolean accept(PDAnnotation annotation)
- {
- return true;
- }
- };
-
- private boolean subsamplingAllowed = false;
-
- /**
- * Creates a new PDFRenderer.
- * @param document the document to render
- */
- public PDFRenderer(PDDocument document)
- {
- this.document = document;
- }
-
- /**
- * Return the AnnotationFilter.
- *
- * @return the AnnotationFilter
- */
- public AnnotationFilter getAnnotationsFilter()
- {
- return annotationFilter;
- }
-
- /**
- * Set the AnnotationFilter.
- *
- * <p>Allows to only render annotation accepted by the filter.
- *
- * @param annotationsFilter the AnnotationFilter
- */
- public void setAnnotationsFilter(AnnotationFilter annotationsFilter)
- {
- this.annotationFilter = annotationsFilter;
- }
-
- /**
- * Value indicating if the renderer is allowed to subsample images before drawing, according to
- * image dimensions and requested scale.
- *
- * Subsampling may be faster and less memory-intensive in some cases, but it may also lead to
- * loss of quality, especially in images with high spatial frequency.
- *
- * @return true if subsampling of images is allowed, false otherwise.
- */
- public boolean isSubsamplingAllowed()
- {
- return subsamplingAllowed;
- }
-
- /**
- * Sets a value instructing the renderer whether it is allowed to subsample images before
- * drawing. The subsampling frequency is determined according to image size and requested scale.
- *
- * Subsampling may be faster and less memory-intensive in some cases, but it may also lead to
- * loss of quality, especially in images with high spatial frequency.
- *
- * @param subsamplingAllowed The new value indicating if subsampling is allowed.
- */
- public void setSubsamplingAllowed(boolean subsamplingAllowed)
- {
- this.subsamplingAllowed = subsamplingAllowed;
- }
-
- /**
- * Returns the given page as an RGB image at 72 DPI
- * @param pageIndex the zero-based index of the page to be converted.
- * @return the rendered page image
- * @throws IOException if the PDF cannot be read
- */
- public BufferedImage renderImage(int pageIndex) throws IOException
- {
- return renderImage(pageIndex, 1);
- }
-
- /**
- * Returns the given page as an RGB image at the given scale.
- * A scale of 1 will render at 72 DPI.
- * @param pageIndex the zero-based index of the page to be converted
- * @param scale the scaling factor, where 1 = 72 DPI
- * @return the rendered page image
- * @throws IOException if the PDF cannot be read
- */
- public BufferedImage renderImage(int pageIndex, float scale) throws IOException
- {
- return renderImage(pageIndex, scale, ImageType.RGB);
- }
-
- /**
- * Returns the given page as an RGB image at the given DPI.
- * @param pageIndex the zero-based index of the page to be converted
- * @param dpi the DPI (dots per inch) to render at
- * @return the rendered page image
- * @throws IOException if the PDF cannot be read
- */
- public BufferedImage renderImageWithDPI(int pageIndex, float dpi) throws IOException
- {
- return renderImage(pageIndex, dpi / 72f, ImageType.RGB);
- }
-
- /**
- * Returns the given page as an RGB image at the given DPI.
- * @param pageIndex the zero-based index of the page to be converted
- * @param dpi the DPI (dots per inch) to render at
- * @param imageType the type of image to return
- * @return the rendered page image
- * @throws IOException if the PDF cannot be read
- */
- public BufferedImage renderImageWithDPI(int pageIndex, float dpi, ImageType imageType)
- throws IOException
- {
- return renderImage(pageIndex, dpi / 72f, imageType);
- }
-
- /**
- * 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)
- throws IOException
- {
- PDPage page = document.getPage(pageIndex);
-
- PDRectangle cropbBox = page.getCropBox();
- float widthPt = cropbBox.getWidth();
- float heightPt = cropbBox.getHeight();
- int widthPx = Math.round(widthPt * scale);
- int heightPx = Math.round(heightPt * scale);
- int rotationAngle = page.getRotation();
-
- int bimType = imageType.toBufferedImageType();
- if (imageType != ImageType.ARGB && hasBlendMode(page))
- {
- // PDFBOX-4095: if the PDF has blending on the top level, draw on transparent background
- // Inpired from PDF.js: if a PDF page uses any blend modes other than Normal,
- // PDF.js renders everything on a fully transparent RGBA canvas.
- // Finally when the page has been rendered, PDF.js draws the RGBA canvas on a white canvas.
- bimType = BufferedImage.TYPE_INT_ARGB;
- }
-
- // swap width and height
- BufferedImage image;
- if (rotationAngle == 90 || rotationAngle == 270)
- {
- image = new BufferedImage(heightPx, widthPx, bimType);
- }
- else
- {
- image = new BufferedImage(widthPx, heightPx, bimType);
- }
-
- // use a transparent background if the image type supports alpha
- Graphics2D g = image.createGraphics();
- if (image.getType() == BufferedImage.TYPE_INT_ARGB)
- {
- g.setBackground(new Color(0, 0, 0, 0));
- }
- else
- {
- g.setBackground(Color.WHITE);
- }
- g.clearRect(0, 0, image.getWidth(), image.getHeight());
-
- transform(g, page, scale, scale);
-
- // the end-user may provide a custom PageDrawer
- PageDrawerParameters parameters = new PageDrawerParameters(this, page, subsamplingAllowed);
- PageDrawer drawer = createPageDrawer(parameters);
- drawer.drawPage(g, page.getCropBox());
-
- g.dispose();
-
- if (image.getType() != imageType.toBufferedImageType())
- {
- // PDFBOX-4095: draw temporary transparent image on white background
- BufferedImage newImage =
- new BufferedImage(image.getWidth(), image.getHeight(), imageType.toBufferedImageType());
- Graphics2D dstGraphics = newImage.createGraphics();
- dstGraphics.setBackground(Color.WHITE);
- dstGraphics.clearRect(0, 0, image.getWidth(), image.getHeight());
- dstGraphics.drawImage(image, 0, 0, null);
- dstGraphics.dispose();
- image = newImage;
- }
-
- return image;
- }
-
- /**
- * Renders a given page to an AWT Graphics2D instance.
- * @param pageIndex the zero-based index of the page to be converted
- * @param graphics the Graphics2D on which to draw the page
- * @throws IOException if the PDF cannot be read
- */
- public void renderPageToGraphics(int pageIndex, Graphics2D graphics) throws IOException
- {
- renderPageToGraphics(pageIndex, graphics, 1);
- }
-
- /**
- * Renders a given page to an AWT Graphics2D instance.
- * @param pageIndex the zero-based index of the page to be converted
- * @param graphics the Graphics2D on which to draw the page
- * @param scale the scale to draw the page at
- * @throws IOException if the PDF cannot be read
- */
- public void renderPageToGraphics(int pageIndex, Graphics2D graphics, float scale)
- throws IOException
- {
- renderPageToGraphics(pageIndex, graphics, scale, scale);
- }
-
- /**
- * Renders a given page to an AWT Graphics2D instance.
- *
- * @param pageIndex the zero-based index of the page to be converted
- * @param graphics the Graphics2D on which to draw the page
- * @param scaleX the scale to draw the page at for the x-axis
- * @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)
- throws IOException
- {
- PDPage page = document.getPage(pageIndex);
- // TODO need width/wight calculations? should these be in PageDrawer?
-
- transform(graphics, page, scaleX, scaleY);
-
- PDRectangle cropBox = page.getCropBox();
- 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);
- PageDrawer drawer = createPageDrawer(parameters);
- drawer.drawPage(graphics, cropBox);
- }
-
- // scale rotate translate
- private void transform(Graphics2D graphics, PDPage page, float scaleX, float scaleY)
- {
- graphics.scale(scaleX, scaleY);
-
- // TODO should we be passing the scale to PageDrawer rather than messing with Graphics?
- int rotationAngle = page.getRotation();
- PDRectangle cropBox = page.getCropBox();
-
- if (rotationAngle != 0)
- {
- float translateX = 0;
- float translateY = 0;
- switch (rotationAngle)
- {
- case 90:
- translateX = cropBox.getHeight();
- break;
- case 270:
- translateY = cropBox.getWidth();
- break;
- case 180:
- translateX = cropBox.getWidth();
- translateY = cropBox.getHeight();
- break;
- default:
- break;
- }
- graphics.translate(translateX, translateY);
- graphics.rotate((float) Math.toRadians(rotationAngle));
- }
- }
-
- /**
- * Returns a new PageDrawer instance, using the given parameters. May be overridden.
- */
- protected PageDrawer createPageDrawer(PageDrawerParameters parameters) throws IOException
- {
- PageDrawer pageDrawer = new PageDrawer(parameters);
- pageDrawer.setAnnotationFilter(annotationFilter);
- return pageDrawer;
- }
-
- private boolean hasBlendMode(PDPage page)
- {
- // check the current resources for blend modes
- PDResources resources = page.getResources();
- if (resources == null)
- {
- return false;
- }
- for (COSName name : resources.getExtGStateNames())
- {
- PDExtendedGraphicsState extGState = resources.getExtGState(name);
- if (extGState == null)
- {
- // can happen if key exists but no value
- // see PDFBOX-3950-23EGDHXSBBYQLKYOKGZUOVYVNE675PRD.pdf
- continue;
- }
- BlendMode blendMode = extGState.getBlendMode();
- if (blendMode != BlendMode.NORMAL)
- {
- return true;
- }
- }
- return false;
- }
-}
+/*
+ * 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.Color;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+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.blend.BlendMode;
+import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
+import org.apache.pdfbox.pdmodel.interactive.annotation.AnnotationFilter;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+
+/**
+ * Renders a PDF document to an AWT BufferedImage.
+ * This class may be overridden in order to perform custom rendering.
+ *
+ * @author John Hewson
+ */
+public class PDFRenderer
+{
+ protected final PDDocument document;
+ // TODO keep rendering state such as caches here
+
+ /**
+ * Default annotations filter, returns all annotations
+ */
+ private AnnotationFilter annotationFilter = new AnnotationFilter()
+ {
+ @Override
+ public boolean accept(PDAnnotation annotation)
+ {
+ return true;
+ }
+ };
+
+ private boolean subsamplingAllowed = false;
+
+ private BufferedImage pageImage;
+
+ /**
+ * Creates a new PDFRenderer.
+ * @param document the document to render
+ */
+ public PDFRenderer(PDDocument document)
+ {
+ this.document = document;
+ }
+
+ /**
+ * Return the AnnotationFilter.
+ *
+ * @return the AnnotationFilter
+ */
+ public AnnotationFilter getAnnotationsFilter()
+ {
+ return annotationFilter;
+ }
+
+ /**
+ * Set the AnnotationFilter.
+ *
+ * <p>Allows to only render annotation accepted by the filter.
+ *
+ * @param annotationsFilter the AnnotationFilter
+ */
+ public void setAnnotationsFilter(AnnotationFilter annotationsFilter)
+ {
+ this.annotationFilter = annotationsFilter;
+ }
+
+ /**
+ * Value indicating if the renderer is allowed to subsample images before drawing, according to
+ * image dimensions and requested scale.
+ *
+ * Subsampling may be faster and less memory-intensive in some cases, but it may also lead to
+ * loss of quality, especially in images with high spatial frequency.
+ *
+ * @return true if subsampling of images is allowed, false otherwise.
+ */
+ public boolean isSubsamplingAllowed()
+ {
+ return subsamplingAllowed;
+ }
+
+ /**
+ * Sets a value instructing the renderer whether it is allowed to subsample images before
+ * drawing. The subsampling frequency is determined according to image size and requested scale.
+ *
+ * Subsampling may be faster and less memory-intensive in some cases, but it may also lead to
+ * loss of quality, especially in images with high spatial frequency.
+ *
+ * @param subsamplingAllowed The new value indicating if subsampling is allowed.
+ */
+ public void setSubsamplingAllowed(boolean subsamplingAllowed)
+ {
+ this.subsamplingAllowed = subsamplingAllowed;
+ }
+
+ /**
+ * Returns the given page as an RGB image at 72 DPI
+ * @param pageIndex the zero-based index of the page to be converted.
+ * @return the rendered page image
+ * @throws IOException if the PDF cannot be read
+ */
+ public BufferedImage renderImage(int pageIndex) throws IOException
+ {
+ return renderImage(pageIndex, 1);
+ }
+
+ /**
+ * Returns the given page as an RGB image at the given scale.
+ * A scale of 1 will render at 72 DPI.
+ * @param pageIndex the zero-based index of the page to be converted
+ * @param scale the scaling factor, where 1 = 72 DPI
+ * @return the rendered page image
+ * @throws IOException if the PDF cannot be read
+ */
+ public BufferedImage renderImage(int pageIndex, float scale) throws IOException
+ {
+ return renderImage(pageIndex, scale, ImageType.RGB);
+ }
+
+ /**
+ * Returns the given page as an RGB image at the given DPI.
+ * @param pageIndex the zero-based index of the page to be converted
+ * @param dpi the DPI (dots per inch) to render at
+ * @return the rendered page image
+ * @throws IOException if the PDF cannot be read
+ */
+ public BufferedImage renderImageWithDPI(int pageIndex, float dpi) throws IOException
+ {
+ return renderImage(pageIndex, dpi / 72f, ImageType.RGB);
+ }
+
+ /**
+ * Returns the given page as an RGB image at the given DPI.
+ * @param pageIndex the zero-based index of the page to be converted
+ * @param dpi the DPI (dots per inch) to render at
+ * @param imageType the type of image to return
+ * @return the rendered page image
+ * @throws IOException if the PDF cannot be read
+ */
+ public BufferedImage renderImageWithDPI(int pageIndex, float dpi, ImageType imageType)
+ throws IOException
+ {
+ return renderImage(pageIndex, dpi / 72f, imageType);
+ }
+
+ /**
+ * 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)
+ throws IOException
+ {
+ PDPage page = document.getPage(pageIndex);
+
+ PDRectangle cropbBox = page.getCropBox();
+ float widthPt = cropbBox.getWidth();
+ float heightPt = cropbBox.getHeight();
+ int widthPx = Math.round(widthPt * scale);
+ int heightPx = Math.round(heightPt * scale);
+ int rotationAngle = page.getRotation();
+
+ int bimType = imageType.toBufferedImageType();
+ if (imageType != ImageType.ARGB && hasBlendMode(page))
+ {
+ // PDFBOX-4095: if the PDF has blending on the top level, draw on transparent background
+ // Inpired from PDF.js: if a PDF page uses any blend modes other than Normal,
+ // PDF.js renders everything on a fully transparent RGBA canvas.
+ // Finally when the page has been rendered, PDF.js draws the RGBA canvas on a white canvas.
+ bimType = BufferedImage.TYPE_INT_ARGB;
+ }
+
+ // swap width and height
+ BufferedImage image;
+ if (rotationAngle == 90 || rotationAngle == 270)
+ {
+ image = new BufferedImage(heightPx, widthPx, bimType);
+ }
+ else
+ {
+ image = new BufferedImage(widthPx, heightPx, bimType);
+ }
+
+ pageImage = image;
+
+ // use a transparent background if the image type supports alpha
+ Graphics2D g = image.createGraphics();
+ if (image.getType() == BufferedImage.TYPE_INT_ARGB)
+ {
+ g.setBackground(new Color(0, 0, 0, 0));
+ }
+ else
+ {
+ g.setBackground(Color.WHITE);
+ }
+ g.clearRect(0, 0, image.getWidth(), image.getHeight());
+
+ transform(g, page, scale, scale);
+
+ // the end-user may provide a custom PageDrawer
+ PageDrawerParameters parameters = new PageDrawerParameters(this, page, subsamplingAllowed);
+ PageDrawer drawer = createPageDrawer(parameters);
+ drawer.drawPage(g, page.getCropBox());
+
+ g.dispose();
+
+ if (image.getType() != imageType.toBufferedImageType())
+ {
+ // PDFBOX-4095: draw temporary transparent image on white background
+ BufferedImage newImage =
+ new BufferedImage(image.getWidth(), image.getHeight(), imageType.toBufferedImageType());
+ Graphics2D dstGraphics = newImage.createGraphics();
+ dstGraphics.setBackground(Color.WHITE);
+ dstGraphics.clearRect(0, 0, image.getWidth(), image.getHeight());
+ dstGraphics.drawImage(image, 0, 0, null);
+ dstGraphics.dispose();
+ image = newImage;
+ }
+
+ return image;
+ }
+
+ /**
+ * Renders a given page to an AWT Graphics2D instance.
+ * @param pageIndex the zero-based index of the page to be converted
+ * @param graphics the Graphics2D on which to draw the page
+ * @throws IOException if the PDF cannot be read
+ */
+ public void renderPageToGraphics(int pageIndex, Graphics2D graphics) throws IOException
+ {
+ renderPageToGraphics(pageIndex, graphics, 1);
+ }
+
+ /**
+ * Renders a given page to an AWT Graphics2D instance.
+ * @param pageIndex the zero-based index of the page to be converted
+ * @param graphics the Graphics2D on which to draw the page
+ * @param scale the scale to draw the page at
+ * @throws IOException if the PDF cannot be read
+ */
+ public void renderPageToGraphics(int pageIndex, Graphics2D graphics, float scale)
+ throws IOException
+ {
+ renderPageToGraphics(pageIndex, graphics, scale, scale);
+ }
+
+ /**
+ * Renders a given page to an AWT Graphics2D instance.
+ *
+ * @param pageIndex the zero-based index of the page to be converted
+ * @param graphics the Graphics2D on which to draw the page
+ * @param scaleX the scale to draw the page at for the x-axis
+ * @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)
+ throws IOException
+ {
+ PDPage page = document.getPage(pageIndex);
+ // TODO need width/wight calculations? should these be in PageDrawer?
+
+ transform(graphics, page, scaleX, scaleY);
+
+ PDRectangle cropBox = page.getCropBox();
+ 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);
+ PageDrawer drawer = createPageDrawer(parameters);
+ drawer.drawPage(graphics, cropBox);
+ }
+
+ // scale rotate translate
+ private void transform(Graphics2D graphics, PDPage page, float scaleX, float scaleY)
+ {
+ graphics.scale(scaleX, scaleY);
+
+ // TODO should we be passing the scale to PageDrawer rather than messing with Graphics?
+ int rotationAngle = page.getRotation();
+ PDRectangle cropBox = page.getCropBox();
+
+ if (rotationAngle != 0)
+ {
+ float translateX = 0;
+ float translateY = 0;
+ switch (rotationAngle)
+ {
+ case 90:
+ translateX = cropBox.getHeight();
+ break;
+ case 270:
+ translateY = cropBox.getWidth();
+ break;
+ case 180:
+ translateX = cropBox.getWidth();
+ translateY = cropBox.getHeight();
+ break;
+ default:
+ break;
+ }
+ graphics.translate(translateX, translateY);
+ graphics.rotate((float) Math.toRadians(rotationAngle));
+ }
+ }
+
+ /**
+ * Returns a new PageDrawer instance, using the given parameters. May be overridden.
+ */
+ protected PageDrawer createPageDrawer(PageDrawerParameters parameters) throws IOException
+ {
+ PageDrawer pageDrawer = new PageDrawer(parameters);
+ pageDrawer.setAnnotationFilter(annotationFilter);
+ return pageDrawer;
+ }
+
+ private boolean hasBlendMode(PDPage page)
+ {
+ // check the current resources for blend modes
+ PDResources resources = page.getResources();
+ if (resources == null)
+ {
+ return false;
+ }
+ for (COSName name : resources.getExtGStateNames())
+ {
+ PDExtendedGraphicsState extGState = resources.getExtGState(name);
+ if (extGState == null)
+ {
+ // can happen if key exists but no value
+ // see PDFBOX-3950-23EGDHXSBBYQLKYOKGZUOVYVNE675PRD.pdf
+ continue;
+ }
+ BlendMode blendMode = extGState.getBlendMode();
+ if (blendMode != BlendMode.NORMAL)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the image to which the current page is being rendered.
+ * May be null if the page is rendered to a Graphics2D object
+ * instead of a BufferedImage.
+ */
+ BufferedImage getPageImage()
+ {
+ return pageImage;
+ }
+}
Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java?rev=1834752&r1=1834751&r2=1834752&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java Sat Jun 30 16:50:25 2018
@@ -45,8 +45,11 @@ import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pdfbox.contentstream.PDFGraphicsStreamEngine;
@@ -55,6 +58,7 @@ import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.common.function.PDFunction;
import org.apache.pdfbox.pdmodel.font.PDCIDFontType0;
@@ -65,6 +69,8 @@ import org.apache.pdfbox.pdmodel.font.PD
import org.apache.pdfbox.pdmodel.font.PDType1CFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.PDLineDashPattern;
+import org.apache.pdfbox.pdmodel.graphics.PDXObject;
+import org.apache.pdfbox.pdmodel.graphics.blend.BlendMode;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
@@ -76,6 +82,7 @@ import org.apache.pdfbox.pdmodel.graphic
import org.apache.pdfbox.pdmodel.graphics.pattern.PDShadingPattern;
import org.apache.pdfbox.pdmodel.graphics.pattern.PDTilingPattern;
import org.apache.pdfbox.pdmodel.graphics.shading.PDShading;
+import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.pdmodel.graphics.state.PDGraphicsState;
import org.apache.pdfbox.pdmodel.graphics.state.PDSoftMask;
import org.apache.pdfbox.pdmodel.graphics.state.RenderingMode;
@@ -137,6 +144,8 @@ public class PageDrawer extends PDFGraph
private final TilingPaintFactory tilingPaintFactory = new TilingPaintFactory(this);
+ private final Stack<TransparencyGroup> transparencyGroupStack = new Stack<TransparencyGroup>();
+
/**
* Default annotations filter, returns all annotations
*/
@@ -1501,6 +1510,8 @@ public class PageDrawer extends PDFGraph
private final int minX;
private final int minY;
+ private final int maxX;
+ private final int maxY;
private final int width;
private final int height;
@@ -1538,6 +1549,8 @@ public class PageDrawer extends PDFGraph
bbox = null;
minX = 0;
minY = 0;
+ maxX = 0;
+ maxY = 0;
width = 0;
height = 0;
return;
@@ -1552,8 +1565,8 @@ public class PageDrawer extends PDFGraph
minX = (int) Math.floor(bounds.getMinX());
minY = (int) Math.floor(bounds.getMinY());
- int maxX = (int) Math.floor(bounds.getMaxX()) + 1;
- int maxY = (int) Math.floor(bounds.getMaxY()) + 1;
+ maxX = (int) Math.floor(bounds.getMaxX()) + 1;
+ maxY = (int) Math.floor(bounds.getMaxY()) + 1;
width = maxX - minX;
height = maxY - minY;
@@ -1567,7 +1580,40 @@ public class PageDrawer extends PDFGraph
{
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
+
+ boolean needsBackdrop = !isSoftMask && !form.getGroup().isIsolated() &&
+ hasBlendMode(form, new HashSet<COSBase>());
+ BufferedImage backdropImage = null;
+ // Position of this group in parent group's coordinates
+ int backdropX = 0;
+ int backdropY = 0;
+ if (needsBackdrop)
+ {
+ if (transparencyGroupStack.isEmpty())
+ {
+ // Use the current page as the parent group.
+ backdropImage = renderer.getPageImage();
+ needsBackdrop = backdropImage != null;
+ backdropX = minX;
+ backdropY = (backdropImage != null) ? (backdropImage.getHeight() - maxY) : 0;
+ }
+ else
+ {
+ TransparencyGroup parentGroup = transparencyGroupStack.peek();
+ backdropImage = parentGroup.image;
+ backdropX = minX - parentGroup.minX;
+ backdropY = parentGroup.maxY - maxY;
+ }
+ }
+
Graphics2D g = image.createGraphics();
+ if (needsBackdrop)
+ {
+ // backdropImage must be included in group image but not in group alpha.
+ g.drawImage(backdropImage, 0, 0, width, height,
+ backdropX, backdropY, backdropX + width, backdropY + height, null);
+ g = new GroupGraphics(image, g);
+ }
if (isSoftMask && backdropColor != null)
{
// "If the subtype is Luminosity, the transparency group XObject G shall be
@@ -1615,7 +1661,12 @@ public class PageDrawer extends PDFGraph
}
else
{
+ transparencyGroupStack.push(this);
processTransparencyGroup(form);
+ if (!transparencyGroupStack.isEmpty())
+ {
+ transparencyGroupStack.pop();
+ }
}
}
finally
@@ -1629,6 +1680,11 @@ public class PageDrawer extends PDFGraph
pageSize = pageSizeOriginal;
xform = xformOriginal;
pageRotation = pageRotationOriginal;
+
+ if (needsBackdrop)
+ {
+ ((GroupGraphics) g).removeBackdrop(backdropImage, backdropX, backdropY);
+ }
}
}
@@ -1704,4 +1760,54 @@ public class PageDrawer extends PDFGraph
width, height);
}
}
+
+ private boolean hasBlendMode(PDTransparencyGroup group, Set<COSBase> groupsDone)
+ {
+ if (groupsDone.contains(group.getCOSObject()))
+ {
+ // The group was already processed. Avoid endless recursion.
+ return false;
+ }
+ groupsDone.add(group.getCOSObject());
+
+ PDResources resources = group.getResources();
+ if (resources == null)
+ {
+ return false;
+ }
+ for (COSName name : resources.getExtGStateNames())
+ {
+ PDExtendedGraphicsState extGState = resources.getExtGState(name);
+ if (extGState == null)
+ {
+ continue;
+ }
+ BlendMode blendMode = extGState.getBlendMode();
+ if (blendMode != BlendMode.NORMAL)
+ {
+ return true;
+ }
+ }
+
+ // Recursively process nested transparency groups
+ for (COSName name : resources.getXObjectNames())
+ {
+ PDXObject xObject;
+ try
+ {
+ xObject = resources.getXObject(name);
+ }
+ catch (IOException ex)
+ {
+ continue;
+ }
+ if (xObject instanceof PDTransparencyGroup &&
+ hasBlendMode((PDTransparencyGroup)xObject, groupsDone))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
}