Перетащите прямоугольник с фиксированным соотношением сторон северо-западного угла - PullRequest
0 голосов
/ 08 мая 2018

У меня есть приложение на Java, где пользователь может обрезать подизображение из своего первоначального я. Область обрезки выбирается путем рисования прямоугольника поверх исходного изображения. Размер прямоугольника можно изменить по диагонали. И пока все работает!

У пользователя также есть возможность зафиксировать соотношение сторон прямоугольника 4: 3. Я могу добиться этого просто установив ширину w = h / 4 * 3;

Однако, когда дело доходит до изменения размера с заблокированным отношением, прямоугольник ведет себя странно и больше не стоит на месте при перетаскивании из северо-западного угла (см. Рисунок ниже). У меня была та же проблема с юго-западным углом, но это можно было бы исправить, установив вместо этого высоту 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 голосов
/ 10 мая 2018

Во-первых, просто отметим, что соотношение сторон, которое вы используете, равно 3:4, а не 4:3:

3:4 означает, что на каждые 3 единицы ширины приходится 4 единицы высоты.

4:3 означает, что на каждые 4 единицы ширины приходится 3 единицы высоты.

w = h / 4 * 3 рассчитывает 3:4, а не 4:3.

w = h / 3 * 4 или h = w / 4 * 3 вычисляет 4:3

Переходя к тому, почему ваше изменение размера нарушается, при создании Rectangle вы указываете координаты x, y его верхнего левого угла, а также ширину и высоту:

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

Прямоугольник будет нарисован от x, y до x + width, y + height

Компонент изменения размера вашего кода работает нормально, когда мышь перетаскивают, вы корректно обновляете x, y, width и height.

Причина, по которой применение соотношения сторон нарушает его, заключается в том, что вы обновляете width и height, но не обновляете x и y.

Допустим, пользователь выполнил изменение размера на северо-запад, и теперь у вас есть прямоугольник:

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

Поскольку вы рисуете из левого верхнего угла, прямоугольник теперь вырос слева направо, но вы хотите, чтобы он рос справа налево. При изменении размера в направлении Северо-запад вы всегда хотите, чтобы нижний правый угол прямоугольника оставался на том же месте. Причина, по которой этого не происходит с вашим кодом, заключается в том, что когда вы применяете соотношение сторон к ширине прямоугольника, вы не обновляете начальную точку 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;
        }
    }
} 

Поэтому, когда размер прямоугольника изменяется в северо-западном или северо-восточном направлениях и применяется соотношение сторон, я также обновляю начальную координату y прямоугольника, чтобы учесть изменение высоты.

...