在西北角

时间:2018-05-08 10:02:05

标签: java image resize crop aspect-ratio

我有一个Java应用程序,用户可以从其原始自我裁剪子图像。通过在原始图像上绘制矩形来选择裁剪区域。然后可以对角调整矩形的大小。到目前为止,一切正常!

用户还可以选择将矩形的纵横比锁定为4:3。我只需将宽度设置为 w = h / 4 * 3;

即可实现此目的

然而,当涉及使用锁定比率进行大小调整时,矩形表现得很奇怪,并且从西北角拖动时不再是静止的(请参阅下面的gif)。在西南角有同样的问题,但可以通过将高度设置为 h = w / 3 * 4; 来修复,但我无法弄清楚如何以数学方式为西北方向做角即可。我提供了一个可复制的实验演示:

public class CropDemo {
    public static void main(String[] args) {
        CropPanel cropPanel = new CropPanel();
        cropPanel.setPreferredSize(new Dimension(640, 480));

        JFrame jFrame = new JFrame("Crop Panel");
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jFrame.getContentPane().add(cropPanel);
        jFrame.setResizable(false);
        jFrame.pack();
        jFrame.setLocationRelativeTo(null);
        jFrame.setVisible(true);
    }
}

class CropPanel extends JPanel {
    private static final long serialVersionUID = 1L;

    private boolean fixedRatio = true;

    private Rectangle rectangle;
    private Point clickPoint;

    private static final int HOVERING = 0;
    private static final int MOVING = 1;
    private static final int RESIZING = 2;

    public CropPanel() {
        setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));

        MouseAdapter mouseHandler = new MouseAdapter() {
            private Point startPoint = null;

            @Override
            public void mouseClicked(MouseEvent e) {
                if (rectangle != null && getCursorState() == HOVERING) {
                    rectangle = null;

                    repaint();
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                clickPoint = e.getPoint();
                startPoint = e.getPoint();
            }

            @Override
            public void mouseMoved(MouseEvent e) {
                if (rectangle != null) {
                    Point mouse = e.getPoint();

                    int width = rectangle.x + rectangle.width;
                    int height = rectangle.y + rectangle.height;

                    final int off = 5;

                    if (mouse.x > rectangle.x - off && mouse.x < width + off && mouse.y > rectangle.y - off
                            && mouse.y < height + off) {
                        if (mouse.x <= rectangle.x + off && mouse.y >= height - off) {
                            setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
                        } else if (mouse.x >= width - off && mouse.y >= height - off) {
                            setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
                        } else if (mouse.x <= rectangle.x + off && mouse.y <= rectangle.y + off) {
                            setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
                        } else if (mouse.x >= width - off && mouse.y <= rectangle.y + off) {
                            setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
                        } else {
                            setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
                        }
                    } else {
                        setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
                    }
                }
            }

            @Override
            public void mouseDragged(MouseEvent e) {

                if (clickPoint != null) {
                    Point mouse = e.getPoint();

                    if (getCursorState() == MOVING) {
                        int dx = rectangle.x + mouse.x - clickPoint.x;
                        int dy = rectangle.y + mouse.y - clickPoint.y;

                        rectangle.setLocation(dx, dy);
                        clickPoint = e.getPoint();

                    } else if (getCursorState() == RESIZING) {
                        int dx = mouse.x - startPoint.x;
                        int dy = mouse.y - startPoint.y;

                        int height = rectangle.height;
                        int width = rectangle.width;

                        int x = 0;
                        int y = 0;
                        int w = 0;
                        int h = 0;

                        switch (getCursor().getType()) {
                        case Cursor.SW_RESIZE_CURSOR:
                            x = mouse.x + dx;
                            y = rectangle.y;
                            w = width - dx;
                            h = height + dy;

                            if (fixedRatio) {
                                h = w / 3 * 4;
                            }
                            break;
                        case Cursor.SE_RESIZE_CURSOR:
                            x = rectangle.x;
                            y = rectangle.y;
                            w = width + dx;
                            h = height + dy;

                            if (fixedRatio) {
                                w = h / 4 * 3;
                            }
                            break;
                        case Cursor.NW_RESIZE_CURSOR:
                            x = mouse.x + dx;
                            y = mouse.y + dy;
                            w = width - dx;
                            h = height - dy;

                            // This is where I'm lost
                            // something else needs to be done
                            if (fixedRatio) {
                                w = h / 4 * 3;
                            }
                            break;
                        case Cursor.NE_RESIZE_CURSOR:
                            x = rectangle.x;
                            y = mouse.y + dy;
                            w = width + dx;
                            h = height - dy;

                            if (fixedRatio) {
                                w = h / 4 * 3;
                            }
                            break;
                        }

                        rectangle.setBounds(x, y, w, h);
                        startPoint = mouse;
                    } else {
                        int x = Math.min(clickPoint.x, mouse.x);
                        int y = Math.min(clickPoint.y, mouse.y);
                        int w = Math.max(clickPoint.x - mouse.x, mouse.x - clickPoint.x);
                        int h = Math.max(clickPoint.y - mouse.y, mouse.y - clickPoint.y);

                        if (rectangle == null) {
                            rectangle = new Rectangle(x, y, w, h);
                        } else {
                            rectangle.setBounds(x, y, w, h);
                        }

                    }
                    repaint();
                }
            }
        };

        addMouseListener(mouseHandler);
        addMouseMotionListener(mouseHandler);
    }

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

        g.setColor(Color.DARK_GRAY);
        g.fillRect(0, 0, getWidth(), getHeight());

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

        if (rectangle != null) {
            Area fill = new Area(new Rectangle(new Point(0, 0), getSize()));
            fill.subtract(new Area(rectangle));

            if (clickPoint != null) {
                graphics2D.setColor(new Color(0, 0, 0, 0));
            } else {
                graphics2D.setColor(new Color(0, 0, 0, 200));
            }

            int x = rectangle.x;
            int y = rectangle.y;
            int w = rectangle.width;
            int h = rectangle.height;

            graphics2D.fill(fill);
            graphics2D.setColor(Color.WHITE);
            graphics2D.setStroke(
                    new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] { 6 }, 0));
            graphics2D.drawRect(x, y, w, h);

            if (w >= 30 && h >= 30) {
                graphics2D.setStroke(new BasicStroke(3));

                graphics2D.drawLine(x + 1, y + 1, x + 8, y + 1);
                graphics2D.drawLine(x + 1, y + 1, x + 1, y + 8);
                graphics2D.drawLine(x + w - 1, y + 1, x + w - 8, y + 1);
                graphics2D.drawLine(x + w - 1, y + 1, x + w - 1, y + 8);
                graphics2D.drawLine(x + 1, y + h - 1, x + 8, y + h - 1);
                graphics2D.drawLine(x + 1, y + h - 1, x + 1, y + h - 8);
                graphics2D.drawLine(x + w - 1, y + h - 1, x + w - 8, y + h - 1);
                graphics2D.drawLine(x + w - 1, y + h - 1, x + w - 1, y + h - 8);
            }
        }

        graphics2D.dispose();
        g.dispose();
    }

    private int getCursorState() {
        switch (getCursor().getType()) {
        case Cursor.CROSSHAIR_CURSOR:
            return HOVERING;
        case Cursor.MOVE_CURSOR:
            return MOVING;
        case Cursor.SW_RESIZE_CURSOR:
        case Cursor.SE_RESIZE_CURSOR:
        case Cursor.NW_RESIZE_CURSOR:
        case Cursor.NE_RESIZE_CURSOR:
        case Cursor.N_RESIZE_CURSOR:
        case Cursor.S_RESIZE_CURSOR:
        case Cursor.W_RESIZE_CURSOR:
        case Cursor.E_RESIZE_CURSOR:
            return RESIZING;
        default:
            return -1;
        }
    }
}

Rectangle resizing gif

1 个答案:

答案 0 :(得分:1)

首先请注意,您使用的宽高比为3:4而不是4:3

3:4表示每3个宽度单位有4个单位的高度。

4:3表示每4个宽度单位,有3个单位的高度。

w = h / 4 * 3正在计算3:4,而不是4:3

w = h / 3 * 4h = w / 4 * 3计算4:3

继续调整大小调整的原因,当你创建一个Rectangle时,你会提供左上角的x,y坐标,以及它的宽度和高度:

Rectangle rectangle = new Rectangle(x, y, width, height)

然后,矩形将从x, y绘制到x + width, y + height

您的代码调整大小部分可以正常工作,拖动鼠标时您可以正确更新xywidthheight

应用宽高比的原因是因为您要更新widthheight,但是您没有更新xy

假设用户执行了西北调整大小,现在您有一个矩形如下:

x => 10
y => 10
width => 5
height => 10

enter image description here

然后,您应用宽高比w = h / 4 * 3

x => 10
y => 10
width => 8
height => 10

enter image description here

因为您是从左上角绘制的,所以矩形现在从左向右增长,但您希望它从右向左增长。当您在 Northwest 方向调整大小时,您始终希望矩形的右下角保持在同一位置。你的代码没有发生这种情况的原因是因为当你将宽高比应用于矩形的宽度时,你不会更新矩形的起始x,y点。

使用上面的例子,x和y应该按如下方式更新:

x => 7
y => 10
width => 8
height => 10

enter image description here

这是我提出的解决方案:

else if (getCursorState() == RESIZING) {
    Point startPoint = null;
    Point endPoint = null;

    switch(getCursor().getType()) {
        case Cursor.SW_RESIZE_CURSOR:
            startPoint = new Point((int) mouse.getX(), (int) rectangle.getMinY());
            endPoint = new Point((int) rectangle.getMaxX(), (int) mouse.getY());
            break;
        case Cursor.NW_RESIZE_CURSOR:
            startPoint = new Point((int) mouse.getX(), (int) mouse.getY());
            endPoint = new Point((int) rectangle.getMaxX(), (int) rectangle.getMaxY());
            break;
        case Cursor.NE_RESIZE_CURSOR:
            startPoint = new Point((int) rectangle.getMinX(), (int) mouse.getY());
            endPoint = new Point((int) mouse.getX(), (int) rectangle.getMaxY());
            break;
        case Cursor.SE_RESIZE_CURSOR:
            startPoint = new Point((int) rectangle.getMinX(), (int) rectangle.getMinY());
            endPoint = new Point((int) mouse.getX(), (int) mouse.getY());
            break;
    }

    rectangle.setFrameFromDiagonal(startPoint, endPoint);    

    if (fixedRatio) {
        // Calculate 3:4 aspect ratio
        rectangle.height = rectangle.width / 3 * 4;

        // If this is a NW or NE resize, we need to adjust the start y coordinate to account for the new height
        // This keeps the bottom right corner in the same place for a NW resize
        // and the bottom left corner in the same place for a NE resize
        if (getCursor().getType() == Cursor.NW_RESIZE_CURSOR || getCursor().getType() == Cursor.NE_RESIZE_CURSOR) {
            rectangle.y = endPoint.y - rectangle.height;
        }
    }
} 

因此,当在西北或东北方向调整矩形大小并应用宽高比时,我还会更新矩形的起始坐标以考虑高度的变化。