AffineTransform.rotate() - как одновременно выполнять xlate, вращение и масштабирование?

У меня есть следующий код, который делает (первую часть) то, что я хочу, рисуя шахматную доску с некоторыми фигурами на ней.

              Image pieceImage = getImage(currentPiece);
              int pieceHeight = pieceImage.getHeight(null);
              double scale = (double)side/(double)pieceHeight;
              AffineTransform transform = new AffineTransform();
              transform.setToTranslation(xPos, yPos);
              transform.scale(scale, scale);
              realGraphics.drawImage(pieceImage, transform, this);

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

Допустим, я хочу повернуть черные фигуры на 180 градусов. Где-то я ожидаю что-то вроде:

transform.rotate(Math.toRadians(180) /* ?, ? */);

Но я не могу понять, что вставить в качестве X и Y. Если я ничего не вставлю, изображение красиво повернется вокруг точки 0,0 своего квадрата шахматной доски, поместив фигуру вверх ногами в квадрат к северо-востоку от где это должно быть. Я догадался о различных других комбинациях x, y, но пока безуспешно.

Я уже использую перевод, чтобы поместить кусок в правильный квадрат, преобразование вращения требует еще одного x, y, вокруг которого можно повернуть вещи, но я не знаю, как сказать преобразованию повернуть кусок вокруг одного x, y и написать изображение к другому x, y. Может ли кто-нибудь помочь мне с параметрами вращения или указать мне что-то, что объясняет, как эти вещи работают? Я нашел примеры вещей, которые не объясняют, как они работают, и до сих пор я не понял, как изменить их для моей ситуации...


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

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

Если я войду в SmallChessboardComponent и удалю комментарий из первого оператора преобразования вращения, я получу ладью на ее исходное место вверх ногами, а конь не появится. Если я вместо этого удалю комментарий, отделяющий второй оператор преобразования, ни одна часть не появится вообще.

Я ищу способ перевернуть фигуры вверх ногами на квадрате, на котором они все равно появятся. Я хочу нарисовать каждую фигуру на доске; Мне не нужен код, который переворачивает доску.

основная программа:

package main;

import java.awt.BorderLayout;

import javax.swing.JFrame;

import directredraw.SmallChessboardComponent;

public class SmallChessboardMain
{
  private static void dbg (String message) { System.out.println(message); }

  public static void main(String[] args)
  {
    //Create the top-level container and add contents to it.
    final JFrame frame = new JFrame("Small Chessboard");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    // create the chessboard itself and set it in the component
    SmallChessboard chessboard = new SmallChessboard();

    // create the GUI component that will contain the chessboard
    SmallChessboardComponent chessboardComponent = new SmallChessboardComponent();
    chessboardComponent.setBoard (chessboard);

    frame.getContentPane().add(chessboardComponent, BorderLayout.CENTER);

    // pack and display all this
    frame.pack();
    frame.setVisible(true);
  }
}

класс шахматной доски:

package main;

public class SmallChessboard
{
  Piece [][] squares = new Piece[2][2];

  public SmallChessboard()
  {
    squares[0][0] = new Piece(Piece.WHITECOLOR, Piece.ROOK);
    squares[1][1] = new Piece(Piece.WHITECOLOR, Piece.KNIGHT);
  }

  /**
   * get the piece at the given rank and file; null if
   * no piece exists there.
   */
  public Piece getPiece(int rank, int file)
  { 
    if (0 > rank || rank > 2 || 0 > file || file > 2) { return null; }
      else { return squares[rank][file]; }
  }
}

класс компонента шахматной доски:

package directredraw;


import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;

import javax.swing.JPanel;

import main.Piece;
import main.PieceImages;
import main.SmallChessboard;


public class SmallChessboardComponent extends JPanel
  { 
    private static final long serialVersionUID = 1L;

    Color whiteSquareColor = Color.yellow;
    Color blackSquareColor = Color.blue;

    private static void dbg (String msg) { System.out.println(msg); }

    private SmallChessboard  chessboard = null;

    // currently playing with rotating images; this affine transform
    // should help
    AffineTransform rotationTransform = null;

    private final int DEFAULT_PREFERRED_SIDE = 400;
    int wholeSide = DEFAULT_PREFERRED_SIDE;
    int side = DEFAULT_PREFERRED_SIDE / 8;

    public void setBoard (SmallChessboard givenBoard)
    { chessboard = givenBoard;
    }

    /**
     * set either or both colors for this chessboard; if either of
     * the arguments are null, they do not change the existing color
     * setting.
     */
    public void setColors (Color darkSquare, Color lightSquare)
    {
      if (darkSquare != null) { blackSquareColor = darkSquare; }
      if (lightSquare != null) { whiteSquareColor = lightSquare; }
    }

    /**
     * return the preferred size for this component.s
     */
    public Dimension getPreferredSize()
    { return new Dimension(wholeSide, wholeSide);
    }

    /*
     * return the image object for the given piece
     */
    private Image getImage(Piece piece)
    { return PieceImages.getPieceImage(this, piece);
    }

    public void paintComponent (Graphics graphics)
    {
      Graphics2D realGraphics = (Graphics2D) graphics;

      // the image container might have been stretched.
      // calculate the largest square held by the current container,
      // and then 1/2 of that size for an individual square.
      int wholeWidth  = this.getWidth();
      int wholeHeight = this.getHeight();
      wholeSide   = (wholeWidth / 2) * 2;
      if (wholeHeight < wholeWidth) { wholeSide = (wholeHeight / 2) * 2; }
      side = wholeSide / 2; 

      Rectangle clip = realGraphics.getClipBounds();
      boolean firstColumnWhite = false;

      // for each file on the board:
      //    set whether top square is white
      //    set background color according to white/black square
      //    
      for (int fileIndex=0; fileIndex<8; fileIndex++)
        { boolean currentColorWhite = firstColumnWhite;
          firstColumnWhite = !firstColumnWhite;

          // draw the board and all the pieces
          int rankIndex = 2;
          for (rankIndex=2; rankIndex>=0; rankIndex--)
          { 

            currentColorWhite = !currentColorWhite;

            // x and y position of the top left corner of the square we're drawing,
            // and rect becomes the dimensions and position of the square itself.
            int xPos = fileIndex * side;
            int yPos = rankIndex * side;
            Rectangle rect = new Rectangle(xPos, yPos, side, side);

            // if this square intersects the clipping rectangle we're drawing,
            // then we'll draw the square and the piece on the square.
            if (rect.intersects(clip))
            {
              // this puts down the correct color of square 
              if (currentColorWhite) { realGraphics.setColor(whiteSquareColor); }
                                else { realGraphics.setColor(blackSquareColor); }
              realGraphics.fillRect(xPos, yPos, side, side); 

              // if there is a piece on this square and it isn't selected at the
              // moment, then draw it.
              Piece currentPiece = chessboard.getPiece(rankIndex, fileIndex);
              if (currentPiece != null)
                { 
                  Image pieceImage = getImage(currentPiece);
                  int pieceHeight = pieceImage.getHeight(null);
                  double scalePiece = (double)side/(double)pieceHeight;
                  AffineTransform transform = new AffineTransform();
//                  transform.setToRotation(Math.toRadians(180));
                  transform.setToRotation(Math.toRadians(180), side/2, side/2);
                  transform.scale(scalePiece, scalePiece);
                  transform.translate(xPos/scalePiece, yPos/scalePiece);
//                  if (currentPiece.isBlack()) 
//                  {
//                    transform.translate(xPos + (side+2), yPos + (side+2));
//                    transform.rotate(Math.toRadians(180) /*, ,*/ ); 
//                  }
//                  else
//                  {
//                    transform.translate(xPos, yPos);
//                  }
                  realGraphics.drawImage(pieceImage, transform, this);
                }
            }
          }
        }
    }
  }

Кусок.java

package main;

public class Piece
{ 
  // piece types; the sum of the piece type and the
  // color gives a number unique to both type and color,
  // which is used for things like image indices.
  public static final int PAWN   = 0;
  public static final int KNIGHT = 1;
  public static final int BISHOP = 2;
  public static final int ROOK   = 3;
  public static final int QUEEN  = 4;
  public static final int KING   = 5;

  // one of these is the color of the current piece
  public static final int NOCOLOR = -1;
  // the sum of the piece type and the
  // color gives a number unique to both type and color,
  // which is used for things like image indices.
  public static final int BLACKCOLOR = 0;
  public static final int WHITECOLOR = 6;

  int color = NOCOLOR;
  int imageIndex;

  public Piece(int color, int pieceType)
  { 
    // dbg -- all pieces are white rooks for now...
    this.color  = color;
    imageIndex  = color + pieceType;
  }

  /**
   * return the integer associated with this piece's color;
   */
  int getPieceColor()
  { return color;
  }

  /**
   * return true if the piece is black
   */
  public boolean isBlack() 
  { 
    return (color == BLACKCOLOR); 
  }

  /**
   * set the color associated with this piece; constants
   * found in this class.
   */
  public void setPieceColor(int givenColor)
  { color = givenColor;
  }

  /**
   * return the integer designated for the image used for this piece.
   */
  int getImageIndex()
  { return imageIndex;
  }

}

и PieceImages.java

package main;

import java.awt.Component;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Toolkit;
import java.net.URL;

public class PieceImages
{ static Image images[] = null;

private static void dbg (String msg) { System.out.println(msg); } 

  public static Image getPieceImage (Component target, Piece piece)
  {
    if (images == null)
    try
    { 
      MediaTracker tracker = new MediaTracker(target);
      images = new Image[12];
      images[Piece.BLACKCOLOR + Piece.PAWN] = getImage(tracker, "bPawn.gif");
      images[Piece.BLACKCOLOR + Piece.KNIGHT] = getImage(tracker, "bKnight.gif");
      images[Piece.BLACKCOLOR + Piece.BISHOP] = getImage(tracker, "bBishop.gif");
      images[Piece.BLACKCOLOR + Piece.ROOK] = getImage(tracker, "bRook.gif");
      images[Piece.BLACKCOLOR + Piece.QUEEN] = getImage(tracker, "bQueen.gif");
      images[Piece.BLACKCOLOR + Piece.KING] = getImage(tracker, "bKing.gif");

      images[Piece.WHITECOLOR + Piece.PAWN] = getImage(tracker, "wPawn.gif");
      images[Piece.WHITECOLOR + Piece.KNIGHT] = getImage(tracker, "wKnight.gif");
      images[Piece.WHITECOLOR + Piece.BISHOP] = getImage(tracker, "wBishop.gif");
      images[Piece.WHITECOLOR + Piece.ROOK] = getImage(tracker, "wRook.gif");
      images[Piece.WHITECOLOR + Piece.QUEEN] = getImage(tracker, "wQueen.gif");
      images[Piece.WHITECOLOR + Piece.KING] = getImage(tracker, "wKing.gif");
      if (!tracker.waitForAll(10000))
      { System.out.println("ERROR: not all piece main.images loaded");
      }
      dbg("piece images loaded");
    }
    catch (Exception xcp)
    { System.out.println("Error loading images");
      xcp.printStackTrace();
    }
    return images[piece.getImageIndex()];
  }

  private static Image getImage(MediaTracker tracker, String file)
  {
    URL url = PieceImages.class.getResource("images/" + file);
    Image image = Toolkit.getDefaultToolkit().getImage(url);
    tracker.addImage(image,  1);
    return image;
  }
}

person arcy    schedule 11.08.2012    source источник


Ответы (2)


Ладно, это небольшая хитрость. Пример кода будет работать только для приращений на 90 градусов (он был разработан только таким образом), чтобы делать меньшие приращения, вы должны использовать некоторый триггер для вычисления ширины и высоты изображения (где-то для этого есть ответ;))

public class ImagePane extends JPanel {

    private BufferedImage masterImage;
    private BufferedImage renderedImage;

    public ImagePane(BufferedImage image) {
        masterImage = image;
        applyRotation(0);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(renderedImage.getWidth(), renderedImage.getHeight());
    }

    @Override
    public Dimension getMinimumSize() {
        return getPreferredSize();
    }

    protected int getVirtualAngle(int angle) {
        float fRotations = (float) angle / 360f;
        int rotations = (int) (fRotations - (fRotations / 1000));

        int virtual = angle - (rotations * 360);

        if (virtual < 0) {
            virtual = 360 + virtual;
        }

        return virtual;
    }

    public void applyRotation(int angle) {
        // This will only work for angles of 90 degrees...

        // Normalize the angle to make sure it's only between 0-360 degrees
        int virtualAngle = getVirtualAngle(angle);
        Dimension size = new Dimension(masterImage.getWidth(), masterImage.getHeight());
        int masterWidth = masterImage.getWidth();
        int masterHeight = masterImage.getHeight();

        double x = 0; //masterWidth / 2.0;
        double y = 0; //masterHeight / 2.0;

        switch (virtualAngle) {
            case 0:
                break;
            case 180:
                break;
            case 90:
            case 270:
                size = new Dimension(masterImage.getHeight(), masterImage.getWidth());
                x = (masterHeight - masterWidth) / 2.0;
                y = (masterWidth - masterHeight) / 2.0;
                break;
        }
        renderedImage = new BufferedImage(size.width, size.height, masterImage.getTransparency());
        Graphics2D g2d = renderedImage.createGraphics();

        AffineTransform at = AffineTransform.getTranslateInstance(x, y);

        at.rotate(Math.toRadians(virtualAngle), masterWidth / 2.0, masterHeight / 2.0);
        g2d.drawImage(masterImage, at, null);

        g2d.dispose();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g;
        int width = getWidth() - 1;
        int height = getHeight() - 1;

        int x = (width - renderedImage.getWidth()) / 2;
        int y = (height - renderedImage.getHeight()) / 2;

        g2d.drawImage(renderedImage, x, y, this);
    }

}

Теперь вы можете просто «перевернуть» изображение по вертикали, если вам так удобнее.

public class FlipPane extends JPanel {

    private BufferedImage masterImage;
    private BufferedImage renderedImage;

    public FlipPane(BufferedImage image) {
        masterImage = image;
        flipMaster();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(renderedImage.getWidth(), renderedImage.getHeight());
    }

    @Override
    public Dimension getMinimumSize() {
        return getPreferredSize();
    }

    protected void flipMaster() {
        renderedImage = new BufferedImage(masterImage.getWidth(), masterImage.getHeight(), masterImage.getTransparency());
        Graphics2D g2d = renderedImage.createGraphics();
        g2d.setTransform(AffineTransform.getScaleInstance(1, -1));
        g2d.drawImage(masterImage, 0, -masterImage.getHeight(), this);
        g2d.dispose();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g;
        int width = getWidth() - 1;
        int height = getHeight() - 1;

        int x = (width - renderedImage.getWidth()) / 2;
        int y = (height - renderedImage.getHeight()) / 2;

        g2d.drawImage(renderedImage, x, y, this);
    }
}

В основном это приводит к:

Пример поворота изображения

Оригинал | вращение на 180 градусов | Вертикальная инверсия...

Теперь, если вы измените метод flipMaster на чтение:

g2d.setTransform(AffineTransform.getScaleInstance(-1, -1));
g2d.drawImage(masterImage, -masterImage.getWidth(), -masterImage.getHeight(), this);

Вы получите тот же эффект, что и при вращении на 180 градусов ;)

person MadProgrammer    schedule 11.08.2012
comment
Я ДЕЙСТВИТЕЛЬНО надеюсь на менее сложное решение, что-то, связанное с преобразованием, которое я могу поместить в существующий код, не удваивая его. Я не оценил это полностью, но сделаю это, когда у меня будет больше времени. Тем временем я добавил рабочий пример к исходному вопросу. - person arcy; 11.08.2012
comment
3 строки, необходимые для переворачивания изображений, вероятно, являются самым простым из двух решений, если вы хотите повернуть их только на 180 градусов. - person MadProgrammer; 11.08.2012
comment
Вы привели меня к тому, что я собираюсь сделать - я хотел бы знать больше об этом, должен найти приличный общий учебник по рендерингу изображений или что-то в этом роде, хотелось бы, чтобы его было легче найти. Теперь у меня есть код, который будет переворачивать только изображение; Я вызову это отдельно, а затем переведу и масштабирую в другой операции. Я все еще думаю, что может быть какой-то способ сделать то, что я изначально пытался сделать, но я думаю, я убедился, что это не очевидно для коллег-постеров. Теперь, если вы просто опубликуете комментарий, рассказывающий мне, как вставлять изображения в мои ответы, я буду готов 8›). - person arcy; 12.08.2012
comment
@rcook Я знаю это чувство: P - Лично я бы сначала масштабировал изображения, особенно если им нужно только оставаться в постоянном размере. Это упростит матрицу перевода и поворота, а память, необходимую для выполнения операции, уменьшит ... ИМХО - person MadProgrammer; 12.08.2012

Попробуйте выполнить вращение, прежде чем переводить его в правильное положение. Просто измените порядок преобразований, чтобы сначала масштабировать, затем вращать (вокруг центральной точки изображения), а затем переводить:

transform.scale(scale, scale);
transform.rotate(Math.PI, pieceWidth / 2, pieceHeight /2);
transform.translation(xPos, yPos);

Кстати, черные фигуры на шахматной доске обычно не меняются. :)

Обновить

Каким образом это не работает? Предоставленное мной решение также отличается от вашего кода тем, что масштабирование выполняется перед переводом. Вы можете попробовать вращать, переводить, а затем масштабировать.

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

transform.scale(scale, scale); // or transform.scale(scale, -scale); to rotate
transform.translate(xPos, yPos);
person Zong    schedule 11.08.2012
comment
Интересно, но это не работает - очевидно, необходим SSCE, я редактирую исходный вопрос, чтобы опубликовать все 400 с лишним строк кода. - person arcy; 11.08.2012
comment
Начнем с того, что, насколько я могу судить по javadoc и методом проб и ошибок, выполнение setToTranslation стирает эффект масштабирования и поворота, поэтому то, что у вас есть, не масштабирует и не поворачивает изображение. Если я делаю «масштабировать, вращать, переводить», моя белая ладья (в СССЕ) не появляется, а конь появляется в самом центре доски, а не на каком-либо поле. Это как если бы он (они) вращались, но не вокруг своей центральной точки. Вот чего я не понимаю во всем этом, и мой первоначальный вопрос - что это за позиция x, y должна быть в этом случае? - person arcy; 12.08.2012