0

I have a BufferedImage that I added to a JPanel. I was able to zoom in/out of the image using AffineTransform. I can write on top of the image but not on the location that I want. The line that I draw displays at the wrong location on the image.

I guess it has something to do with the scale but I can't figure out what part of my code is wrong.

This is the screenshot of what I mean.

Here is my code:


    public class MouseScaleTest {

        int xbegin = 0;
        int ybegin = 0;
        int xend = 0;
        int yend = 0;

        boolean isNewLine = true;

        int initx;
        int inity;

        int count = 0;
        Line2D linebuffer;
        Rectangle2D box;
        final ArrayList<Line2D> lineContainer = new ArrayList();
        final ArrayList<Rectangle2D> boxContainer = new ArrayList();

        public static void main(String[] args) {
            new MouseScaleTest();
        }

        public MouseScaleTest() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {

                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setLayout(new BorderLayout());

    //                JScrollPane
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }

        public class TestPane extends JPanel {

            private BufferedImage img;

            private float scale = 1f;
            private float scaleDelta = 0.05f;
            JLabel lbl;
            JLabel scalePoint;
            JLabel begin;
            JLabel end;
            JLabel aft;

            public TestPane() {

                try {
                    img = ImageIO.read(new File("C:\\Users\\John Ebarita\\Documents\\report.jpg"));
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
                lbl = new JLabel();
                lbl.setBounds(0, 0, 50, 10);
                scalePoint = new JLabel();
                scalePoint.setBounds(50, 0, 50, 10);
                begin = new JLabel();
                begin.setBounds(150, 0, 50, 10);
                end = new JLabel();
                end.setBounds(200, 0, 50, 10);
                aft = new JLabel();
                aft.setBounds(250, 0, 50, 10);

                add(lbl);
                add(scalePoint);
                add(begin);
                add(end);
                add(aft);

                addMouseWheelListener(new MouseWheelListener() {

                    @Override
                    public void mouseWheelMoved(MouseWheelEvent e) {
                        int rotation = e.getWheelRotation();
                        if (rotation < 0) {
                            scale -= scaleDelta;
                        } else {
                            scale += scaleDelta;
                        }
                        if (scale < 0) {
                            scale = 0;
                        }
    //                    else if (scale > 1.5) {
    //                        scale = 1;
    //                    }
                        scalePoint.setText("Scale: " + String.valueOf(scale));

                        repaint();

    //                    System.out.println(getSize());
                    }
                });

                addMouseMotionListener(new MouseMotionListener() {
                    @Override
                    public void mouseDragged(MouseEvent e) {
                        if (isNewLine == false) {
                            xend = e.getX();
                            yend = e.getY();

                            end.setText("End Values : " + xend + " " + yend);
                            repaint();
                        }
                    }

                    @Override
                    public void mouseMoved(MouseEvent e) {
                        lbl.setText("Mouse Point: " + e.getX() + " " + e.getY());
                        if (isNewLine == false) {
                            xend = e.getX();
                            yend = e.getY();
                            repaint();
                        }
                    }
                });

                addMouseListener(new MouseAdapter() {

                    @Override
                    public void mouseReleased(MouseEvent e) {
                        System.out.println(e.getX() + " " + e.getY() + "mouse");
                        if (isNewLine == true) {
                            xbegin = xend = e.getX();
                            ybegin = yend = e.getY();

                            isNewLine = false;
                            box = new Rectangle2D.Float(xend - 5, yend - 5, 12, 12);

                            boxContainer.add(box);
                        } else {

                            linebuffer = new Line2D.Float((float) xbegin, (float) ybegin, (float) xend, (float) yend);

                            System.out.println("xbegin: " + xbegin + " " + ybegin + " " + xend + " " + yend);
                            lineContainer.add(linebuffer);

                            repaint();

                            xbegin = e.getX();
                            ybegin = e.getY();

                            if (box.contains(xend, yend)) {
                                xend = (int) box.getCenterX();
                                yend = (int) box.getCenterY();
                                isNewLine = true;
                                return;
                            }

                            box = new Rectangle2D.Float(xend - 6, yend - 6, 12, 12);

                            boxContainer.add(box);
                            return;
                        }
                    }

                    @Override
                    public void mousePressed(MouseEvent e) {
                        begin.setText("Begin Values : " + xbegin + " " + ybegin);
                    }
                });
                setBorder(new BevelBorder(1));
            }

            @Override
            public Dimension getPreferredSize() {
                return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(), img.getHeight());
            }

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                if (img != null) {
    //
                    Graphics2D g2 = (Graphics2D) g.create();
    //
                    int x = (int) ((getWidth() - (img.getWidth() * scale)) / 2);
                    int y = (int) (getHeight() - (img.getHeight() * scale)) / 2;

                    aft.setText("AFT : " + x + " " + y);

                    AffineTransform at = new AffineTransform();
                    at.translate(x, y);
    //                System.out.println(scale);
                    at.scale(scale, scale);

                    g2.setTransform(at);
                    g2.drawImage(img, 0, 0, this);

                    g2.draw(new Line2D.Float(xbegin, ybegin, xend, yend));
                    for (int i = 0; i < lineContainer.size(); i++) {

                        g2.draw(lineContainer.get(i));
                    }
                    g2.setStroke(new BasicStroke(3));
                    for (int i = 0; i < boxContainer.size(); i++) {
                        g2.setColor(Color.BLUE);
                        g2.draw(boxContainer.get(i));
                    }

                    g2.dispose();
    //                System.out.println("int x and y " + x + " " + y);
    //                System.out.println("getWidth and getHeight " + getWidth() + " " + getHeight());
                }
            }
        }
    }

This what i want to achieve with my code. Where I can draw dynamically on top of an image and still can zoom in/out while maintaining the lines drawn.

Tilman Hausherr
  • 17,731
  • 7
  • 58
  • 97
John
  • 45
  • 8
  • Interesting, what happens if you try to draw at the origin? Does it end up where you expect. – IronMan Jul 31 '19 at 00:46
  • I tried drawing at the origin of the panel itself and it displays on the origin of the image. – John Jul 31 '19 at 00:49
  • 1
    1) For better help sooner, [edit] to add a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). The example seen above, with an entire screen full of imports, is definitely not an MRE / SSCCE. 2) One way to get image(s) for an example is to hot link to images seen in [this Q&A](http://stackoverflow.com/q/19209650/418556). E.G. [This answer](https://stackoverflow.com/a/10862262/418556) hot links to an image embedded in [this question](https://stackoverflow.com/q/10861852/418556). – Andrew Thompson Jul 31 '19 at 02:20
  • Can someone help me solve this problem. I just copy pasted my code so someone can just copy and paste them in their ide and run them directly. Sorry for not making it "MRE/SSCEE". Actually I did read it on wiki but I didnt bother changing the details. I was busy on finding a solution. I just approved an edit that was made by ThanhPhan – John Jul 31 '19 at 03:52
  • Upload the image to a sharehoster; or create the image on the fly (new BufferedImage , and then paint it white). – Tilman Hausherr Jul 31 '19 at 04:38
  • Please elaborate Sorry can't understand . – John Jul 31 '19 at 04:58
  • a sharehoster is a service like http://imgur.com. Creating an image on the fly means you create a BufferedImage, that one would be just black. To make it white, you'd have to call getGraphics() on it, then paint it white, then dispose() the graphics. https://stackoverflow.com/questions/1440750/set-bufferedimage-to-be-a-color-in-java – Tilman Hausherr Jul 31 '19 at 06:34
  • Sir I think you didn't undestand what I mean, sorry about that. It's really ambiguous ,it's hard to explain in just text. – John Jul 31 '19 at 07:16
  • 1
    it's not gonna be easy - you have to start with a simple test - one image - Thats it! no other garbage. By jumping into high water is not gonna help. – gpasch Jul 31 '19 at 09:06
  • By right clicking on the image (pVbcf.png) and loading that one I could not reproduce the effect. However I could reproduce it when changing the size of the window, or by moving the mouse wheel. – Tilman Hausherr Jul 31 '19 at 09:42

1 Answers1

2

IMHO the mistake is that you are storing absolute values when it is clicked, instead of storing relative positions. (that takes scale and translation into account - this is what you do when drawing the image)

1) keep the transformation from the paint 2) calculate the relative coordinates when you get them by doing a reverse transform

Add these two into your code below the MouseScaleTest constructor:

AffineTransform atg;

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

assign atg here in paintComponent:

g2.setTransform(at);
atg = (AffineTransform) at.clone(); // new!

now everywhere (mouseDragged, mouseMoved, mouseReleased) you have code like

xend = e.getX();
yend = e.getY();

do this instead:

Point2D p = calcCoordinates(e);
xend = (int) p.getX();
yend = (int) p.getY();

here are the segments if you're into "copy and paste development" (not good!):

addMouseWheelListener(new MouseWheelListener()
{

    @Override
    public void mouseWheelMoved(MouseWheelEvent e)
    {
        int rotation = e.getWheelRotation();
        if (rotation < 0)
        {
            scale -= scaleDelta;
        }
        else
        {
            scale += scaleDelta;
        }
        if (scale < 0)
        {
            scale = 0;
        }
        //                    else if (scale > 1.5) {
        //                        scale = 1;
        //                    }
        scalePoint.setText("Scale: " + String.valueOf(scale));

        repaint();

        //                    System.out.println(getSize());
    }
});

addMouseMotionListener(new MouseMotionListener()
{
    @Override
    public void mouseDragged(MouseEvent e)
    {
        if (isNewLine == false)
        {
            xend = e.getX();
            yend = e.getY();

            //NEW
            Point2D p = calcCoordinates(e);
            xend = (int) p.getX();
            yend = (int) p.getY();

            end.setText("End Values : " + xend + " " + yend);
            repaint();
        }
    }

    @Override
    public void mouseMoved(MouseEvent e)
    {
        lbl.setText("Mouse Point: " + e.getX() + " " + e.getY());
        if (isNewLine == false)
        {
            xend = e.getX();
            yend = e.getY();

            //NEW
            Point2D p = calcCoordinates(e);
            xend = (int) p.getX();
            yend = (int) p.getY();

            repaint();
        }
    }

});

addMouseListener(new MouseAdapter()
{

    @Override
    public void mouseReleased(MouseEvent e)
    {
        System.out.println(e.getX() + " " + e.getY() + "mouse");
        if (isNewLine == true)
        {
            xbegin = xend = e.getX();
            ybegin = yend = e.getY();

            //NEW
            Point2D p = calcCoordinates(e);
            xbegin = xend = (int) p.getX();
            ybegin = yend = (int) p.getY();

            isNewLine = false;
            box = new Rectangle2D.Float(xend - 5, yend - 5, 12, 12);

            boxContainer.add(box);
        }
        else
        {

            linebuffer = new Line2D.Float((float) xbegin, (float) ybegin, (float) xend, (float) yend);

            System.out.println("xbegin: " + xbegin + " " + ybegin + " " + xend + " " + yend);
            lineContainer.add(linebuffer);

            repaint();

            xbegin = e.getX();
            ybegin = e.getY();

            //NEW
            Point2D p = calcCoordinates(e);
            xbegin = (int) p.getX();
            ybegin = (int) p.getY();

            if (box.contains(xend, yend))
            {
                xend = (int) box.getCenterX();
                yend = (int) box.getCenterY();
                isNewLine = true;
                return;
            }

            box = new Rectangle2D.Float(xend - 6, yend - 6, 12, 12);

            boxContainer.add(box);
            return;
        }
    }
Tilman Hausherr
  • 17,731
  • 7
  • 58
  • 97
  • thanks for the idea of inverseTransform. really did the job. Honestly I thought about something like that. What i did was I reverse the translation of the graphics 2D and it worked but the points stay as is when I zoom in/out. – John Aug 01 '19 at 01:01
  • Does it mean that my solution works or not? My solution should have the points moving with the image. I wonder because I saw that the answer had a green checkmark this morning and now two hours later it doesn't. – Tilman Hausherr Aug 01 '19 at 06:45
  • It worked . But I change my implementation. I added a `JScrollPane` to the image and instead of using `drawImage` I used `drawRenderedImage ` to make the image still visible even whenever I scroll up or down. – John Aug 01 '19 at 07:56
  • So I removed the `AffineTrasform` to the `graphich2D ` instance and placed as a parameter to the `g2.drawRenderedImage(image,at);` and it messed up all the points. – John Aug 01 '19 at 07:58
  • Of course, because the points / line painting also used the changed transform. And using JScrollPane is something different (although interesting and useful). – Tilman Hausherr Aug 01 '19 at 08:26
  • yup thanks, Do have any idea about this [link] https://stackoverflow.com/questions/57303151/some-part-of-the-image-disappears-when-i-scroll-up-and-down-on-a-jscrollpane/57304763#57304763 – John Aug 01 '19 at 08:33
  • Remember that you told me to `inverseTransform`. how to retun it to the oroginal points before the `inverseTransform` – John Aug 02 '19 at 05:04
  • atg.transform is not working. maybe it is because of ` AffineTransform at = new AffineTransform(); at.translate(affineX, affineY); at.scale(scale, scale); AffineTransform atf = g2.getTransform(); atf.concatenate(at); atg = (AffineTransform) atf.clone(); ` – John Aug 02 '19 at 09:08
  • There is no "g2.getTransform();" in the code of the answer. There is a simple way to test what I wrote: change `calcCoordinates` to this (after "try{": `System.out.println("p: " + p); Point2D p2 = atg.inverseTransform(p, null); System.out.println("p2: " + p2); Point2D p3 = atg.transform(p2, null); System.out.println("p3: " + p3); return p2;` you will notice that p3 is the same as p. – Tilman Hausherr Aug 02 '19 at 09:52
  • I tried to transform the point but when I saved it is inverted on the pdf.. – John Aug 07 '19 at 00:29
  • Please create a new question. Apparently now you're doing something different, i.e. modifying a PDF. This can't be solved by a quick extra question in a comment. Note that PDF coordinates start at the bottom, not the top like in Java. – Tilman Hausherr Aug 07 '19 at 04:32