AffineTransform без преобразования Stroke?

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

пример скриншота

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, которая по-разному обводит фигуры в направлении X и Y (тем самым устраняя искажение здесь)? (Или кто-нибудь уже знает такую ​​реализацию?)
  • Должен ли я преобразовать свои фигуры в координаты экрана и обводить их там?
  • Любые другие (лучшие) идеи?

3 ответа

Решение

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

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

скриншот неискаженного инсульта

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


Внимание: это g.getTransform() возвращает полное преобразование 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) - верхний левый угол вашего компонента), не масштабируется, поэтому нет никакой разницы.

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

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

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

  • Если Shape должен быть поглажен, Stroke атрибут в Graphics2D контекст используется для генерации нового Shape это охватывает пройденный путь.
  • Координаты Shapeпути преобразуются из пространства пользователя в пространство устройства в соответствии с атрибутом transform в Graphics2D контекст.
  • Shapeпуть обрезается с помощью атрибута клипа в 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* штрих обводит фигуру один раз.

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

Другие вопросы по тегам