AffineTransform без преобразования Stroke? - PullRequest
12 голосов
/ 18 февраля 2011

При использовании функции Graphics2D scale() с двумя разными параметрами (масштабирование с различными соотношениями в направлении x и y) все, что нарисовано позже в этом объекте Graphics2D, также масштабируется. Это имеет странный эффект, что линии, нарисованные в одном направлении, толще, чем в другом направлении. Следующая программа производит этот эффект, она показывает это окно:

example screenshot

public class StrokeExample extends JPanel {


    public void paintComponent(Graphics context) {
        super.paintComponent(context);
        Graphics2D g = (Graphics2D)context.create();
        g.setStroke(new BasicStroke(0.2f));

        int height = getHeight();
        int width = getWidth();

        g.scale(width/7.0, height/4.0);

        g.setColor(Color.BLACK);
        g.draw(new Rectangle( 2, 1, 4, 2));
    }

    public static void main(String[] params) {
        EventQueue.invokeLater(new Runnable(){public void run() {

            StrokeExample example = new StrokeExample();

            JFrame f = new JFrame("StrokeExample");
            f.setSize(100, 300);
            f.getContentPane().setLayout(new BorderLayout());
            f.getContentPane().add(example);
            f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            f.setVisible(true);
        }});

    }

}

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

Я знаю, что создает этот эффект (объект Stroke создает заштрихованную форму прямоугольника, который должен быть нарисован в пользовательских координатах, которые затем переводятся в экранные координаты), но я не уверен, как решить эту проблему.

  • Должен ли я создать новую реализацию Stroke, которая по-разному штрихует Shapes в направлении X и Y (тем самым устраняя искажение здесь)? (Или кто-нибудь уже знает такую ​​реализацию?)
  • Должен ли я преобразовать свои фигуры в координаты экрана и обводить их там?
  • Какие-нибудь другие (лучшие) идеи?

Ответы [ 3 ]

8 голосов
/ 19 февраля 2011

Оказывается, мой вопрос не был настолько ужасно трудным, и что две мои идеи, приведенные в вопросе, на самом деле являются одной и той же идеей.Вот класс TransformedStroke, который реализует искаженный Stroke путем преобразования Shape.

import java.awt.*;
import java.awt.geom.*;


/**
 * A implementation of {@link Stroke} which transforms another Stroke
 * with an {@link AffineTransform} before stroking with it.
 *
 * This class is immutable as long as the underlying stroke is
 * immutable.
 */
public class TransformedStroke
    implements Stroke
{
    /**
     * To make this serializable without problems.
     */
    private static final long serialVersionUID = 1;

    /**
     * the AffineTransform used to transform the shape before stroking.
     */
    private AffineTransform transform;
    /**
     * The inverse of {@link #transform}, used to transform
     * back after stroking.
     */
    private AffineTransform inverse;

    /**
     * Our base stroke.
     */
    private Stroke stroke;


    /**
     * Creates a TransformedStroke based on another Stroke
     * and an AffineTransform.
     */
    public TransformedStroke(Stroke base, AffineTransform at)
        throws NoninvertibleTransformException
    {
        this.transform = new AffineTransform(at);
        this.inverse = transform.createInverse();
        this.stroke = base;
    }


    /**
     * Strokes the given Shape with this stroke, creating an outline.
     *
     * This outline is distorted by our AffineTransform relative to the
     * outline which would be given by the base stroke, but only in terms
     * of scaling (i.e. thickness of the lines), as translation and rotation
     * are undone after the stroking.
     */
    public Shape createStrokedShape(Shape s) {
        Shape sTrans = transform.createTransformedShape(s);
        Shape sTransStroked = stroke.createStrokedShape(sTrans);
        Shape sStroked = inverse.createTransformedShape(sTransStroked);
        return sStroked;
    }

}

Мой метод рисования, использующий его, выглядит следующим образом:

public void paintComponent(Graphics context) {
    super.paintComponent(context);
    Graphics2D g = (Graphics2D)context.create();

    int height = getHeight();
    int width = getWidth();

    g.scale(width/4.0, height/7.0);

    try {
        g.setStroke(new TransformedStroke(new BasicStroke(2f),
                                          g.getTransform()));
    }
    catch(NoninvertibleTransformException ex) {
        // should not occur if width and height > 0
        ex.printStackTrace();
    }

    g.setColor(Color.BLACK);
    g.draw(new Rectangle( 1, 2, 2, 4));
}

Тогда мое окно выглядит так:

screenshot of undistorted stroke

Я вполне доволен этим, но если у кого-то есть больше идей, не стесняйтесь, тем не менее, отвечать.


Внимание: Этот g.getTransform() возвращает complete преобразование g относительно пространства устройства, а не только преобразование, примененное после .create().Так что, если кто-то сделал какое-то масштабирование перед тем, как передать Графику моему компоненту, это все равно рисовало бы с штрихом шириной в 2 устройства, а не 2 пикселя от графики, предоставленной моему методу.Если это будет проблемой, используйте это следующим образом:

public void paintComponent(Graphics context) {
    super.paintComponent(context);
    Graphics2D g = (Graphics2D)context.create();

    AffineTransform trans = new AffineTransform();

    int height = getHeight();
    int width = getWidth();

    trans.scale(width/4.0, height/7.0);
    g.transform(trans);

    try {
        g.setStroke(new TransformedStroke(new BasicStroke(2f),
                                          trans));
    }
    catch(NoninvertibleTransformException ex) {
        // should not occur if width and height > 0
        ex.printStackTrace();
    }

    g.setColor(Color.BLACK);
    g.draw(new Rectangle( 1, 2, 2, 4));
}

В Swing обычно ваша Графика, переданная paintComponent, переводится только (таким образом (0,0) - верхний левый угол вашего компонента), не масштабируется, поэтому нет никакой разницы.

3 голосов
/ 08 апреля 2016

Существует более простое и менее «хакерское» решение, чем оригинальный ответ TransformedStroke.

Я понял, когда прочитал, как работает конвейер рендеринга:

(из * 1006)*http://docs.oracle.com/javase/7/docs/technotes/guides/2d/spec/j2d-awt.html)

  • Если необходимо штриховать Shape, атрибут Stroke в контексте Graphics2D используется для генерации нового Shape, который охватывает пройденный путь.
  • Координаты пути Shape преобразуются из пространства пользователя в пространство устройства в соответствии с атрибутом преобразования в контексте Graphics2D.
  • Путь Shape обрезаетсяиспользуя атрибут clip в контексте Graphics2D.
  • Оставшиеся Shape, если они есть, заполняются с использованием атрибутов Paint и Composite в контексте Graphics2D.

В идеале вы и я ищем способ поменять местами первые два шага.

Если вы внимательно посмотрите на второй шаг, TransformedStroke уже содержит часть решения.

Shape sTrans = transform.createTransformedShape(s);

решение

Вместо:

g.scale( ... ), g.transform( ... ), что угодно,g.draw(new Rectangle( 1, 2, 2, 4));

Или, используя TransformedStroke:

g.setStroke(new TransformedStroke(new BasicStroke(2f), g.getTransform());g.draw(new Rectangle( 1, 2, 2, 4));

Я предлагаю вам сделать:

transform = что угодно,g.draw(transform.createTransformedShape(new Rectangle( 1, 2, 2, 4));

Не трансформируйте g больше.Когда-либо.Вместо этого преобразуйте фигуры, используя преобразование, которое вы делаете и модифицируете сами.

обсуждение

TransformedStroke больше похоже на «хак», чем на то, как авторы Stroke подразумевали интерфейсиспользоваться.Также требуется дополнительный класс.

Это решение хранит отдельный Transform и модифицирует Shape вместо преобразования объекта Graphics.Это, однако, ни в коем случае не хак, потому что я не злоупотребляю существующей функциональностью, а использую функциональность API именно так, как она должна использоваться.Я просто использую более явные части API вместо методов API «ярлык» / «удобство» (g.scale() и т. Д.).

С точки зрения производительности, это решение может быть только болееэффективный.Фактически один шаг теперь пропущен.В исходном решении TransformedStroke дважды преобразует фигуру и один раз поглаживает фигуру.Это решение явно преобразует фигуру, и * current * мазок обводит фигуру один раз.

0 голосов
/ 05 марта 2011

Вы только что пытались увеличить int и int в приложении, например, int x = 500 int y = 900 ??? Также я предлагаю, чтобы без переписывания весь код был реализован так, чтобы рэки были толще, когда приложение ближе друг к другу, как удвоение прямоугольника сверху и снизу, но когда приложение расширяется, рэки сверху и снизу идут вернуться к нормальной жизни ...

...