public void drawImage()

in pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java [1075:1262]


    public void drawImage(PDImage pdImage) throws IOException
    {
        if (pdImage instanceof PDImageXObject &&
            isHiddenOCG(((PDImageXObject) pdImage).getOptionalContent()))
        {
            return;
        }
        if (!isContentRendered())
        {
            return;
        }
        Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
        AffineTransform at = ctm.createAffineTransform();

        if (!pdImage.getInterpolate())
        {
            // if the image is scaled down, we use smooth interpolation, eg PDFBOX-2364
            // only when scaled up do we use nearest neighbour, eg PDFBOX-2302 / mori-cvpr01.pdf
            // PDFBOX-4930: we use the sizes of the ARGB image. These can be different
            // than the original sizes of the base image, when the mask is bigger.
            // PDFBOX-5091: also consider subsampling, the sizes are different too.
            BufferedImage bim;
            if (subsamplingAllowed)
            {
                bim = pdImage.getImage(null, getSubsampling(pdImage, at));
            }
            else
            {
                bim = pdImage.getImage();
            }
            boolean isScaledUp =
                    bim.getWidth() <= Math.abs(Math.round(ctm.getScalingFactorX() * xformScalingFactorX)) ||
                    bim.getHeight() <= Math.abs(Math.round(ctm.getScalingFactorY() * xformScalingFactorY));
            if (isScaledUp)
            {
                graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                        RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
            }
        }

        graphics.setComposite(getGraphicsState().getNonStrokingJavaComposite());
        setClip();

        if (pdImage.isStencil())
        {
            if (getGraphicsState().getNonStrokingColor().getColorSpace() instanceof PDPattern)
            {
                // The earlier code for stencils (see "else") doesn't work with patterns because the
                // CTM is not taken into consideration.
                // this code is based on the fact that it is easily possible to draw the mask and 
                // the paint at the correct place with the existing code, but not in one step.
                // Thus what we do is to draw both in separate images, then combine the two and draw
                // the result. 
                // Note that the device scale is not used. In theory, some patterns can get better
                // at higher resolutions but the stencil would become more and more "blocky".
                // If anybody wants to do this, have a look at the code in showTransparencyGroup().

                // draw the paint
                Paint paint = getNonStrokingPaint();
                Rectangle2D unitRect = new Rectangle2D.Float(0, 0, 1, 1);
                Rectangle2D bounds = at.createTransformedShape(unitRect).getBounds2D();
                int w = (int) Math.ceil(bounds.getWidth());
                int h = (int) Math.ceil(bounds.getHeight());
                BufferedImage renderedPaint = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
                Graphics2D g = (Graphics2D) renderedPaint.getGraphics();
                g.translate(-bounds.getMinX(), -bounds.getMinY());
                g.setPaint(paint);
                g.setRenderingHints(graphics.getRenderingHints());
                g.fill(bounds);
                g.dispose();

                // draw the mask
                BufferedImage mask = pdImage.getImage();
                AffineTransform imageTransform = new AffineTransform(at);
                imageTransform.scale(1.0 / mask.getWidth(), -1.0 / mask.getHeight());
                imageTransform.translate(0, -mask.getHeight());
                AffineTransform full = new AffineTransform(g.getTransform());
                full.concatenate(imageTransform);
                Matrix m = new Matrix(full);
                double scaleX = Math.abs(m.getScalingFactorX());
                double scaleY = Math.abs(m.getScalingFactorY());

                boolean smallMask = mask.getWidth() <= 8 && mask.getHeight() <= 8;
                if (mask.getWidth() == 1 && mask.getHeight() == 1)
                {
                    // PDFBOX-5802: force usage of the lookup table if it is only 1 pixel
                    // (See the comment for PDFBOX-5403 that it isn't done for some
                    // cases based purely on the rendering result of one file!)
                    smallMask = false;
                }
                if (!smallMask)
                {
                    // PDFBOX-5403:
                    // The mask is copied to RGB because this supports a smooth scaling, so we
                    // get a mask with 255 values instead of just 0 and 255.
                    // Inverting is done because when we don't do it, the getScaledInstance() call
                    // produces a black line in many masks. With the inversion we have a white line
                    // which is neutral. Because of the inversion we don't have to substract from 255
                    // in the "apply the mask" segment when rasterPixel[3] is assigned.

                    // The inversion is not done for very small ones, because of
                    // PDFBOX-2171-002-002710-p14.pdf where the "New Harmony Consolidated" and
                    // "Sailor Springs" patterns became almost invisible.
                    // (We may have to decide this differently in the future, e.g. on b/w relationship)
                    BufferedImage tmp = new BufferedImage(mask.getWidth(), mask.getHeight(), BufferedImage.TYPE_INT_RGB);
                    mask = new LookupOp(getInvLookupTable(), graphics.getRenderingHints()).filter(mask, tmp);
                }

                BufferedImage renderedMask = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
                g = (Graphics2D) renderedMask.getGraphics();
                g.translate(-bounds.getMinX(), -bounds.getMinY());
                g.setRenderingHints(graphics.getRenderingHints());

                if (smallMask)
                {
                    g.drawImage(mask, imageTransform, null);
                }
                else if (scaleX != 0 && scaleY != 0)
                {
                    while (scaleX < 0.25 || Math.round(mask.getWidth() * scaleX) < 1)
                    {
                        scaleX *= 2.0;
                    }
                    while (scaleY < 0.25 || Math.round(mask.getHeight() * scaleY) < 1)
                    {
                        scaleY *= 2.0;
                    }
                    int w2 = (int) Math.round(mask.getWidth() * scaleX);
                    int h2 = (int) Math.round(mask.getHeight() * scaleY);

                    Image scaledMask = mask.getScaledInstance(w2, h2, Image.SCALE_SMOOTH);
                    imageTransform.scale(1f / Math.abs(scaleX), 1f / Math.abs(scaleY));
                    g.drawImage(scaledMask, imageTransform, null);
                }
                g.dispose();

                // apply the mask
                int[] alphaPixel = null;
                int[] rasterPixel = null;
                WritableRaster raster = renderedPaint.getRaster();
                WritableRaster alpha = renderedMask.getRaster();
                for (int y = 0; y < h; y++)
                {
                    for (int x = 0; x < w; x++)
                    {
                        alphaPixel = alpha.getPixel(x, y, alphaPixel);
                        rasterPixel = raster.getPixel(x, y, rasterPixel);
                        rasterPixel[3] = alphaPixel[0];
                        raster.setPixel(x, y, rasterPixel);
                    }
                }

                // draw the image
                graphics.drawImage(renderedPaint,
                        AffineTransform.getTranslateInstance(bounds.getMinX(), bounds.getMinY()),
                        null);
            }
            else
            {
                // fill the image with stenciled paint
                BufferedImage image = pdImage.getStencilImage(getNonStrokingPaint());

                // draw the image
                drawBufferedImage(pdImage, image, at);
            }
        }
        else
        {
            if (subsamplingAllowed)
            {
                int subsampling = getSubsampling(pdImage, at);
                // draw the subsampled image
                drawBufferedImage(pdImage, pdImage.getImage(null, subsampling), at);
            }
            else
            {
                // subsampling not allowed, draw the image
                drawBufferedImage(pdImage, pdImage.getImage(), at);
            }
        }

        if (!pdImage.getInterpolate())
        {
            // JDK 1.7 has a bug where rendering hints are reset by the above call to
            // the setRenderingHint method, so we re-set all hints, see PDFBOX-2302
            setRenderingHints();
        }
    }