Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java: GUI и Java FX приложения > Толщина линий после scale()


Автор: Mirkes 9.12.2013, 16:15
День добрый!

Мне надоело в разных вариантах реализовывать приведение координат из одной системы в другую и я решил воспользоваться стандартным функционалом Java: AffineTransform. Сначала запутался как преобразовывать, но разобрался. А вот потом налетел по крупному.
Код

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        int w = this.getWidth() - 2 * margin.x; //Ширина без учета полей
        int h = this.getHeight() - 2 * margin.y; //Высота без учета полей
        Graphics2D gg = (Graphics2D)g.create(margin.x, margin.y, w, h);  //Получаем копию поляны для рисования
        gg.setStroke(new BasicStroke(1f)); //устанавливаем ширину линии в один пиксел
        gg.translate(0, h); //сдвигаем начало координат вниз
        gg.scale(1, -1);  //Переходим в нормальную систему координат инвертируя ось Y
        gg.scale(w / (maxX - minX), h / (maxY - minY)); //Переходим к пользовательским координатам
        gg.translate(minX, minY); //Нормализуем положение начала координат
        gg.setColor(Color.BLACK);
        GeneralPath gp = new GeneralPath();  //Создаем простую линию из точки 0,0 в точку 1,1
        gp.moveTo(0, 0);
        gp.lineTo(1, 1);
        gg.draw(gp); //Рисуем
    }


Все бы ничего, но в моем случае пользовательские координаты меняются от 0 до 1.
А толщина линии оказалась в 1 пользовательских координат!
То есть вместо диагонали квадрата я получил почти закрашенный квадрат! Осталось чуть-чуть не закрашенного места в двух углах.
Теперь вопрос. Как преобразовать координаты и при этом иметь линию по прежнему в 1 пиксель?
Можно конечно посчитать как изменилась единица отсчета, но если пользовательские координаты будут сильно разными по осям? тогда вертикальные линии будут толще/тоньше горизонтальных?

Автор: Mirkes 9.12.2013, 16:36
Вообще то нашел решение, но оно мне сильно не нравится. Вместо преобразования координат в Graphics я создал отдельное преобразование координат которым потом обработал то, что было в пользовательских координатах и результат уже отрисовал. Но если рисовать нужно что-то сложное придется каждый раз все преобразовывать в Shape, преобразовывать координаты и потом рисовать.

Может есть более простой и естественный способ?

Код

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        int w = this.getWidth() - 2 * margin.x;
        int h = this.getHeight() - 2 * margin.y;
        Graphics2D gg = (Graphics2D)g.create(margin.x, margin.y, w, h);
        AffineTransform at=new AffineTransform();
        at.setToIdentity();
        at.translate(0, h);
        at.scale(1, -1);
        at.scale(w / (maxX - minX), h / (maxY - minY));
        at.translate(minX, minY);
        gg.setColor(Color.BLACK);
        GeneralPath gp = new GeneralPath();
        gp.moveTo(0, 0);
        gp.lineTo(1, 1);
        Shape ss=gp.createTransformedShape(at);
        gg.draw(ss);
    }


И опять мордой об забор  smile : метод createTransformedShape() определен только для путей, а я имею готовые Shape. Как преобразовать Shape?

Автор: Mirkes 13.12.2013, 17:16
Интересно, отсутствие какой либо реакции означает:
1. Никто не пробовал рисовать после Affine transform
2. У всех все работает и такого эффекта не возникает
3. Решение давно известно и не хочется тратить время на очевидное

Укажите хотя бы почему нет никакой реакции smile 

Автор: Amp 13.12.2013, 17:36
У AffineTransform есть же собственный createTransformedShape, который Shape принимает.

Автор: Mirkes 13.12.2013, 18:40
Цитата(Amp @  13.12.2013,  17:36 Найти цитируемый пост)
У AffineTransform есть же собственный createTransformedShape, который Shape принимает. 

 Спасибо! Действительно я обшарил все со стороны Shape но не увидел метода у AffineTransform

Однако вопрос с толщиной линии для меня так и не понятен. Как выполнить преобразование координат а потом рисовать нормальной толщиной?
Или преобразования координат надо применять к рисуемым объектам а не к панели, на которой рисуют?

Я посмотрел примеры на сайте оракла - результат тот же. Они применяют преобразования к панели и тольщина линий меняется. К сожалению примера как рисовать линией в один пиксел на преобразованной панели я не нашел. 

Может кто-нибудь подскажет хотя бы куда смотреть?

Автор: Amp 14.12.2013, 15:46
Да пересчитывай руками все сам. Больше возни, но в перспективе меньше проблем.

Автор: Mirkes 14.12.2013, 17:44
Цитата(Amp @  14.12.2013,  15:46 Найти цитируемый пост)
Да пересчитывай руками все сам. Больше возни, но в перспективе меньше проблем. 


Проблему то я решил, но хочется без наворотов обойтись  smile 

Рассмотрим такой пример. Я произвел шкалирование scale(20,10). После этого я установил толщину линии в 0.1. При этом вертикальная линия будет в два раза толще горизонтальной. А вот отслеживать разность толщин линий в зависимости от направления - это уже головняк серьезный.

Автор: Mirkes 17.12.2013, 19:30
Я хочу уточнить вопрос:
Правильно ли я понимаю, что если я произведу аффинные преобразования Graphics2D, то рисовать в нем нормально я уже не смогу?
Я провел физически эксперимент и получил ожидаемый эффект: при неравном шкалировании по осям в измененном Graphics2D я никогда не смогу получить горизонтальные, вертикальные и наклонные линии одной толщины.
Текст программы приложен, он очень короткий и думаю понятный.

Буду благодарен за ответ, даже из одного слова. Я все же надеюсь, что это я чего-то недопонял и возможность рисовать нормально все же существует.
Заранее спасибо.

Код

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JFrame;
import javax.swing.JPanel;


public class MyFrame extends JFrame {
    @SuppressWarnings("compatibility:-1742121239004895315")
    private static final long serialVersionUID = 8904641527445622030L;

    public MyFrame() {
        super();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel pan = new MyPanel();
        getContentPane().add(pan);
        Dimension d = new Dimension(200, 200);
        pan.setSize(d);
        pan.setPreferredSize(d);
        this.pack();
        this.setLocationRelativeTo(null);
    }

    public static void main(String[] args) {
        new MyFrame().setVisible(true);
    }

    public static class MyPanel extends JPanel {
        @SuppressWarnings("compatibility:3907988143307254501")
        private static final long serialVersionUID = 7381605947373850304L;

        @Override
        protected void paintComponent(Graphics g) {
            //            AffineTransform at = new AffineTransform();
            Graphics2D gg = (Graphics2D)g;
            gg.scale(20, 10);
            gg.drawLine(7, 0, 7, 20);
            gg.drawLine(0, 10, 10, 10);
            gg.drawRect(1, 1, 4, 4);
            gg.drawOval(1, 12, 4, 4);
        }
    }
}

Автор: kamre 18.12.2013, 10:24
В Qt имеется вот такое http://qt-project.org/doc/qt-5/qpen.html#isCosmetic:
Цитата

Cosmetic pens are used to draw strokes that have a constant width regardless of any transformations applied to the QPainter they are used with. Drawing a shape with a cosmetic pen ensures that its outline will have the same thickness at different scale factors.


Давно в Java ничего не рисовать не приходилось, но может быть что-то аналогичное имеется и в ней.

Автор: Mirkes 18.12.2013, 13:38
Цитата(kamre @  18.12.2013,  10:24 Найти цитируемый пост)
В Qt имеется вот такое свойство у кисти:
Цитата

Cosmetic pens are used to draw strokes that have a constant width regardless of any transformations applied to the QPainter they are used with. Drawing a shape with a cosmetic pen ensures that its outline will have the same thickness at different scale factors.


Давно в Java ничего не рисовать не приходилось, но может быть что-то аналогичное имеется и в ней. 


Спасибо, попробую поискать.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)