0

I have a PDF file that I converted into an image. Was able to write on top of the image but when I tried to save the shapes/lines into the pdf, the point are not their place and the shapes are inverted.

This is what I draw.

enter image description here

This is the saved PDF File. enter image description here

I'm really sorry but that is the shortest code I can make in order replicate the problem.

I know this question is not good but I hope someone can help.

Thank you Tilman Hausherr for your patience..

   package pdfwriter;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;

public class Example {

    private static class MyImagePanel extends JPanel {

        final BufferedImage image;
        final float scale = .38f;
        AffineTransform atg;
        Point start = new Point();
        Point end = new Point();
        boolean isNewLine = true;
        static ArrayList<Line2D> lines = new ArrayList<>();
        static PDDocument document;

        public MyImagePanel() throws IOException {

            document = PDDocument.load(new File("path"));
            PDFRenderer renderer = new PDFRenderer(document);
            image = renderer.renderImageWithDPI(0, 200, ImageType.RGB);

            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseReleased(MouseEvent e) {
                    if (end != start) {
                        Line2D line = new Line2D.Float(start.x, start.y, end.x, end.y);
                        lines.add(line);
                        Point2D p = calcCoordinates(e);
                        start = new Point();
                        start.x = (int) p.getX();
                        start.y = (int) p.getY();
                        repaint();
                    }

                }
            });
            addMouseMotionListener(new MouseMotionAdapter() {
                @Override
                public void mouseMoved(MouseEvent e) {
                    Point2D p = calcCoordinates(e);
                    end = new Point();

                    end.x = (int) p.getX();
                    end.y = (int) p.getY();
                    repaint();
                }
            });
        }

        private Point2D calcCoordinates(MouseEvent e) {
            Point p = new Point(e.getX(), e.getY());
            try {
                return atg.inverseTransform(p, null);
            } catch (NoninvertibleTransformException ex) {
                return p;
            }
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2D = (Graphics2D) g.create();

            double affineX = (getWidth() - scale * image.getWidth()) / 2;
            double affineY = (getHeight() - scale * image.getHeight()) / 2;

            AffineTransform at = new AffineTransform();
            at.translate(affineX, affineY);
            at.scale(scale, scale);

            AffineTransform atf = g2D.getTransform();
            atf.concatenate(at);

            atg = (AffineTransform) atf.clone();

            g2D.setTransform(atf);

            g2D.drawImage(image, 0, 0, this);
            try {
                g2D.drawLine(start.x, start.y, end.x, end.y);
            } catch (NullPointerException e) {

            }

            for (Line2D l : lines) {
                g2D.draw(l);
            }

            g2D.dispose();
        }

        public static void save() {

            try {
                PDPage page = document.getPage(0);

                PDPageContentStream contentStream = new PDPageContentStream(document, page, true, true, true);

                for (Line2D l : lines) {
                    contentStream.moveTo((float) l.getX1(), (float) l.getY1());
                    contentStream.lineTo((float) l.getX2(), (float) l.getY2());
                    contentStream.stroke();
                }

                contentStream.close();

                document.save(
                        new File("path"));
                document.close();
            } catch (IOException ex) {
                Logger.getLogger(Example.class.getName()).log(Level.SEVERE, null, ex);
            }

        }

        @Override
        public Dimension getPreferredSize() {
            int width = (int) (scale * image.getWidth());
            int height = (int) (scale * image.getHeight());
            return new Dimension(width, height);
        }
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("PDF");
        frame.setSize(1500, 1200);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);

        MyImagePanel imagePanel = null;
        try {
            imagePanel = new MyImagePanel();
        } catch (IOException ex) {
            Logger.getLogger(Example.class
                    .getName()).log(Level.SEVERE, null, ex);
        }

        JButton btn = new JButton("Save");

        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                MyImagePanel.save();
            }
        });

        btn.setBounds(10, 0, 70, 30);

        frame.add(btn);
        JPanel pnl = new JPanel();
        pnl.add(imagePanel);
        pnl.setBounds(0, 100, 1500, 1200);
        frame.add(pnl);
        frame.revalidate();
        frame.repaint();
        frame.setVisible(true);
    }

}
John
  • 45
  • 8
  • Check this link. https://issues.apache.org/jira/browse/PDFBOX-1307?attachmentOrder=desc – Sambit Aug 07 '19 at 06:02
  • Can you share the coordinates if an example drawing? You may simply forget to transform the coordinates. – mkl Aug 07 '19 at 12:48
  • I already updated the question. – John Aug 08 '19 at 02:54
  • 1
    Thank you. In the future, please prepare questions fully (i.e. more than a 3 liner), somebody downvoted it (I upvoted it), despite that now it is a very valid question. – Tilman Hausherr Aug 08 '19 at 09:25

1 Answers1

1

PDF coordinates start at the bottom. So you have to deduct it from the page height. (And if there is a cropbox, then it's a bit more complex, you'd have to add the origins to the calculated coordinates, because the cropbox is what you see). And you need to reverse the inverse-transform from calcCoordinates => transform.

So the code should look like this:

for (Line2D l : lines)
{
    Point2D p1 = new Point2D.Double(l.getX1(), l.getY1());
    Point2D p2 = new Point2D.Double(l.getX2(), l.getY2());
    p1 = atg.transform(p1, null);
    p2 = atg.transform(p2, null);
    contentStream.moveTo((float) p1.getX(), page.getMediaBox().getHeight() - (float) p1.getY());
    contentStream.lineTo((float) p2.getX(), page.getMediaBox().getHeight() - (float) p2.getY());
    contentStream.stroke();
}

To make that work, atg must be accessible, for that I removed "static" from "save", and added a "return" in the exception handler, and changed MyImagePanel.save() to imagePanel.save();.

But wait, there's more... somehow the lines don't fit fully. There is a slight difference... the reason: scale is not .38, it is about .36 (72 / 200), 200 being your dpi. Your mistake was to have two fixed values that depend on each other, but this dependence wasn't in the code. Your code should have something like this:

final int DPI = 200;
final float scale = 72f / DPI;

Full code:

public class SO57387803DrawShapesInPDF
{

    private static class MyImagePanel extends JPanel
    {

        final BufferedImage image;
        //final float scale = .38f;
        final int DPI = 200;
        final float scale = 72f / DPI;

        AffineTransform atg;
        Point start = new Point();
        Point end = new Point();
        boolean isNewLine = true;
        static ArrayList<Line2D> lines = new ArrayList<>();
        static PDDocument document;

        public MyImagePanel() throws IOException
        {
            document = PDDocument.load(new File("XXXX.pdf"));
            PDFRenderer renderer = new PDFRenderer(document);
            image = renderer.renderImageWithDPI(0, DPI, ImageType.RGB);

            addMouseListener(new MouseAdapter()
            {
                @Override
                public void mouseReleased(MouseEvent e)
                {
                    if (end != start)
                    {
                        Line2D line = new Line2D.Float(start.x, start.y, end.x, end.y);
                        lines.add(line);
                        Point2D p = calcCoordinates(e);
                        start = new Point();
                        start.x = (int) p.getX();
                        start.y = (int) p.getY();

                        repaint();
                    }

                }
            });
            addMouseMotionListener(new MouseMotionAdapter()
            {
                @Override
                public void mouseMoved(MouseEvent e)
                {
                    Point2D p = calcCoordinates(e);
                    end = new Point();

                    end.x = (int) p.getX();
                    end.y = (int) p.getY();
                    repaint();
                }
            });
        }

        private Point2D calcCoordinates(MouseEvent e)
        {
            Point p = new Point(e.getX(), e.getY());
            try
            {
                return atg.inverseTransform(p, null);
            }
            catch (NoninvertibleTransformException ex)
            {
                return p;
            }
        }

        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);

            Graphics2D g2D = (Graphics2D) g.create();

            double affineX = (getWidth() - scale * image.getWidth()) / 2;
            double affineY = (getHeight() - scale * image.getHeight()) / 2;

            AffineTransform at = new AffineTransform();
            at.translate(affineX, affineY);
            at.scale(scale, scale);

            AffineTransform atf = g2D.getTransform();
            atf.concatenate(at);
            atg = (AffineTransform) at.clone();

            g2D.setTransform(atf);

            g2D.drawImage(image, 0, 0, this);
            try
            {
                g2D.drawLine(start.x, start.y, end.x, end.y);
            }
            catch (NullPointerException e)
            {

            }

            for (Line2D l : lines)
            {
                g2D.draw(l);
            }

            g2D.dispose();
        }

        public void save()
        {

            try
            {
                PDPage page = document.getPage(0);

                PDPageContentStream contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, true, true);

                for (Line2D l : lines)
                {
                    Point2D p1 = new Point2D.Double(l.getX1(), l.getY1());
                    Point2D p2 = new Point2D.Double(l.getX2(), l.getY2());
                    p1 = atg.transform(p1, null);
                    p2 = atg.transform(p2, null);
                    //contentStream.moveTo((float) l.getX1(), (float) l.getY1());
                    //contentStream.lineTo((float) l.getX2(), (float) l.getY2());
                    contentStream.moveTo((float) p1.getX(), page.getMediaBox().getHeight() - (float) p1.getY());
                    contentStream.lineTo((float) p2.getX(), page.getMediaBox().getHeight() - (float) p2.getY());
                    contentStream.stroke();
                }

                contentStream.close();

                document.save(new File("saved.pdf"));
                document.close();
            }
            catch (IOException ex)
            {
                Logger.getLogger(SO57387803DrawShapesInPDF.class.getName()).log(Level.SEVERE, null, ex);
            }

        }

        @Override
        public Dimension getPreferredSize()
        {
            int width = (int) (scale * image.getWidth());
            int height = (int) (scale * image.getHeight());
            return new Dimension(width, height);
        }
    }

    public static void main(String[] args)
    {
        JFrame frame = new JFrame("PDF");
        frame.setSize(1500, 1200);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);

        // MyImagePanel imagePanel = null;
        MyImagePanel imagePanel;
        try
        {
            imagePanel = new MyImagePanel();
        }
        catch (IOException ex)
        {
            Logger.getLogger(SO57387803DrawShapesInPDF.class
                    .getName()).log(Level.SEVERE, null, ex);
            return; // or there would be an uninitialized variable
        }

        JButton btn = new JButton("Save");

        btn.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                imagePanel.save();
                //MyImagePanel.save();
            }
        });

        btn.setBounds(10, 0, 70, 30);

        frame.add(btn);
        JPanel pnl = new JPanel();
        pnl.add(imagePanel);
        pnl.setBounds(0, 100, 1500, 1200);
        frame.add(pnl);
        frame.revalidate();
        frame.repaint();
        frame.setVisible(true);
    }

}
Tilman Hausherr
  • 17,731
  • 7
  • 58
  • 97
  • Thanks, the shapes are int their place but still inverted – John Aug 09 '19 at 00:13
  • but you made it the accepted answer, or was this after posting the comment because you had forgotten something? I just tested again, copying the code from the answer, it still works?! Maybe try a different PDF. – Tilman Hausherr Aug 09 '19 at 04:17
  • I tested it with portrait PDF's and it worked but when it comes to landscapes it doesnt – John Aug 09 '19 at 05:15
  • Then the page is probably rotated. Check this with `page.getRotation()`. In that case you need to add an appropriate `transform()` call after opening your content stream. The transform needed is usually a rotation and a translation (because the rotation is around the (0,0) axis). The moveto / lineto calls may also have to be adjusted. – Tilman Hausherr Aug 09 '19 at 08:30
  • Yes the page is rotated because it is in landscape. I tried to apply the `transform` that I used on the points on to the page's `contentStream` but it is not working. I tried to read about it in the documentation but it is also confusing for me. What can I do to make the `contentStream ` rotate according to the page's rotation. As what I had observed , the `contentStream` always writes from the (0,0) rotation of the page. – John Aug 10 '19 at 07:37
  • "the page is rotated because it is in landscape" - no, the page size does not have anything to do with the rotation. For 90° add this after the content stream constructor: `if (page.getRotation() == 90) contentStream.transform(Matrix.getRotateInstance(Math.toRadians(90), page.getMediaBox().getHeight(), 0));` – Tilman Hausherr Aug 10 '19 at 11:50
  • Then please share the file. I tried it with a 90° rotated file. Maybe your file is 270° rotated, or there is something else. – Tilman Hausherr Aug 13 '19 at 03:37
  • My file sir is in 270 . I tried this `contentStream.transform(Matrix.getRotateInstance(Math.toRadians(page.getRotation()), page.getMediaBox().getHeight(), 0));` because I want it to work on all rotations. – John Aug 13 '19 at 04:19
  • for 270 it is `contentStream.transform(Matrix.getRotateInstance(Math.toRadians(270), 0, page.getMediaBox().getHeight()));` and the moveTo and lineTo calls must be changed to use the width, not the height. – Tilman Hausherr Aug 13 '19 at 08:27
  • (I got this with trial and error, so be sure to try several rotated files!) – Tilman Hausherr Aug 13 '19 at 08:29
  • This is not good at all, because now the original answer makes no sense, because you removed the code from the question and replaced it with the answer (which did work for non rotated files "I tested it with portrait PDF's and it worked"). The correct way to go would be to create a new question, and there to show the code used. So please revert your last edit and create a new question. – Tilman Hausherr Aug 14 '19 at 11:55
  • This is the new question -> https://stackoverflow.com/questions/57503325/shapes-drawn-on-an-image-is-inverted-when-written-into-pdf-file-part-2 – John Aug 14 '19 at 23:47