java.awt drawRect () неправильно интерпретирует «ширину» и «высоту» (создает +1 пиксель вправо и вниз) - PullRequest
0 голосов
/ 31 марта 2019

Я начал заниматься программированием на Swing и обнаружил одну действительно странную вещь, которую я считаю ошибкой в ​​основах.

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

import java.awt.*;
//  I create a rectangle 2 x 2 with height=2, width=2
Rectangle rec = new Rectangle(1, 1, 2, 2);

// I make sure that it contains only 4 points: (1,1), (1,2), (2,1), (2,2)
println("- " + rec.contains(1, 1) + ", " + rec.contains(1, 2) + ", " + rec.contains(1, 3));
println("- " + rec.contains(2, 1) + ", " + rec.contains(2, 2) + ", " + rec.contains(2, 3));
println("- " + rec.contains(3, 1) + ", " + rec.contains(3, 2) + ", " + rec.contains(3, 3));

Выход:

  • true , true , false
  • true , true , false
  • ложь, ложь, ложь

Тогда:

// I make sure again that the right-bottom point of my rectangle is (2,2)
// by checking the intersection result with another Rectangle
println("intersect (2,2): " + rec.intersects(new Rectangle(2, 2, 5, 5)));
println("intersect (3,3): " + rec.intersects(new Rectangle(3, 3, 5, 5)));

Выход:

  • пересечение (2,2): правда
  • пересечение (3,3): ложь

Таким образом, с математической точки зрения это действительно прямоугольник 2 x 2, который имеет 4 целых точки (то есть 4 пикселя): (1,1), (1,2), (2,1), (2,2 ). Но что, если мы попытаемся сделать это?

// Let's check what is get drawn
g.drawRect(1, 1, 2, 2); // displays 3 x 3 (as if width=3, height=3) !!
// Try it different way
((Graphics2D)g).draw(rec); // displays 3 x 3 again (as if width=3, height=3) !!

Желтый маленький прямоугольник обозначен как 3 x 3:

drawRect

Вывод: конструктор класса Rectangle () рассматривает ширину / высоту как реальную ширину / высоту фигуры в пикселях, в то время как метод рисования drawRect () учитывает ширину / высоту как сдвиг относительно левого верхнего пикселя.

Так java.awt представляет тот же прямоугольник, что и N x M для вычислений, но в виде (N + 1) x (M + 1), когда он отображается с drawRect () .

Я нахожу это совершенно неправильным, потому что человеческая логика по умолчанию предполагает, что мы рисуем именно то, что имеем!

Интересно, знает ли кто-то еще об этой проблеме и может ли дать некоторые ссылки на средства отслеживания ошибок или / и некоторые логические объяснения такого поведения. Просто поиск в Google мне ничего не дает.

Интересно также, как другие программисты решают эту проблему. Использовать обертки для рисования pushRect (x, y, ширина -1, высота -1)? Я просто не могу поверить, что для всех нормально, что это так странно работает.

ОБНОВЛЕНИЕ 1: публикация «минимального» фрагмента кода, который воспроизводит проблему:

import javax.swing.*;
import java.awt.*;

public class Main {
    public static void main(String args[]) {
        JFrame jFrame = new JFrame("Bug of drawRect() demo");
    JPanel jPanel = new JPanel() {
        @Override
        public void paintComponent(Graphics g) {
            g.drawRect(1,1,2,2);
        }
    };

    jFrame.add(jPanel);
    jFrame.setSize(400, 100);
    jFrame.setLocationRelativeTo(null);
    jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    jFrame.setVisible(true);
    }
}

ОБНОВЛЕНИЕ 2: я использую OpenJDK 8 из https://github.com/ojdkbuild/ojdkbuild/releases. Если я использую «Перейти к -> Реализация (и)» в моей IDE, я вижу реализацию drawRect () здесь: C: \ Program Files \ ojdkbuild \ Java-1.8.0-OpenJDK-1.8.0.181-1 \ src.zip! \ Java \ AWT \ Graphics.java.

public void drawRect(int x, int y, int width, int height) {
    if ((width < 0) || (height < 0)) {
        return;
    }

    if (height == 0 || width == 0) {
        drawLine(x, y, x + width, y + height);
    } else {
        drawLine(x, y, x + width - 1, y);
        drawLine(x + width, y, x + width, y + height - 1);
        drawLine(x + width, y + height, x + 1, y + height);
        drawLine(x, y + height, x, y + 1);
    }
}

В этой реализации я вижу, что она рисует вторую и третью строку в виде «x + width» вместо «x + width - 1», что вызывает ошибку.

1 Ответ

0 голосов
/ 15 июня 2019

Когда я столкнулся с той же проблемой, с приведенным ниже кодом (он более сложный), мне потребовалось некоторое время, чтобы понять, что я делал неправильно (я подчеркиваю здесь, как и вы, Я думаю, что API неверен: вы рисуете прямоугольник 100x100, это должно привести к 100x100, а не 101x101.):

    public class MyComponent extends JComponent {
      @Override
      public void paintComponent(final Graphics g) {
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, getWidth(), getHeight());
        g.setColor(Color.RED);
        g.fillRect(0, 0, 100, getHeight());
        g.setColor(Color.WHITE);
        g.drawRect(0, 0, 100, 100);
      }

      public static void main(final String[] args) {
        final JFrame frame = new JFrame("Test");
        frame.setSize(150, +156);
        frame.getContentPane().add(new MyComponent());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
      }
    }

Этот результат в окне примера, где мы можем видеть нарисованный прямоугольник, имеющий одиндополнительные пиксели по ширине / высоте.

Rectangle

Это проблематично в моем случае, потому что я звоню перекрасить с областью, в которой я рисуюпрямоугольник.Итак:

  • Я звоню перекрасить (0, 0, 100, 100)
  • Свинг перекрасить квадрат 100x100
  • Белая линия все еще там, потому что она былане перекрашивать.

Проблема в том, что класс Graphic не является однородным, поскольку он использует ширину и высоту, как можно увидеть в приведенном ниже фрагменте Javadoc:

  • С drawRect javadoc:

    Рисует контур указанного прямоугольника. Левый и правый края прямоугольника имеют ширину x и x +. Верхний и нижний края имеют высоту y и y +.Прямоугольник рисуется с использованием текущего цвета графического контекста.

  • С fillRect javadoc:

    Заполняет указанный прямоугольник. Левый и правый края прямоугольника имеют значения x и x + width - 1 .Верхний и нижний края имеют у и у + высота - 1. Полученный прямоугольник покрывает область шириной в пиксели и высотой в пиксель в высоту.Прямоугольник заполняется с использованием текущего цвета графического контекста.

Исправить такой API сложно, потому что код, зависящий от него, потерпит неудачу (потому что исправитьФактическая проблема с кодом выше, я бы передал 99x99, чтобы иметь прямоугольник 100x100: исправление API приведет к прямоугольнику 99x99).

Вы можете ожидать, что это никогда не будет исправлено, если класс не будет переоборудован в новый пакет (возможно, слияние Graphics2D по пути).

TL; DR: вам, вероятно, следует создать статический метод drawRect, выполняющий то же самое, что и fillRect, как показано ниже:

public static void drawRect(Graphics g, int x, int y, int w, int h) {
  g.drawRect(x, y, w - 1, h - 1);
}
...