Java swt – Obtenir les coordonnées x, y réelles de l’image après la mise à l’échelle et le zoom

J’ai une image qui a été adaptée pour s’adapter. À partir de l’image mise à l’échelle, un utilisateur sélectionne un rectangle.

Je re-dessine ensuite en fonction de cette sélection:

gc.drawImage(imageDisplayed, minX, minY, width, height, imageDisplayed.getBounds().x, imageDisplayed.getBounds().y, imageDisplayed.getBounds().width, imageDisplayed.getBounds().height ); 

Alors maintenant, je veux pouvoir obtenir les coordonnées originales à partir de l’image redimensionnée ET agrandie. Est-ce correct?:

 public Coordinate GetScaledXYCoordinate(int oldX, int oldY, int width, int height, int scaledWidth, int scaledHeight) { int newX = (int)(oldX * width)/scaledWidth; int newY = (int)(oldY * height)/scaledHeight; Coordinate retXY = new Coordinate(newX, newY); return retXY; } public Coordinate GetZoomedXYCoordinate(int oldX, int oldY, int startX, int endX, int startY, int endY, int width, int height,int scaledWidth, int scaledHeight) { // First get x,y after scaling Coordinate xy = GetScaledXYCoordinate(oldX, oldY, width, height, scaledWidth, scaledHeight); // Now get xy after zooming int minX = Math.min(startX, endX); int minY = Math.min(startY, endY); int maxX = Math.max(startX, endX); int maxY = Math.max(startY, endY); int rectWidth = maxX - minX; int rectHeight = maxY - minY; return GetScaledXYCoordinate(xy.getX(), xy.getY(), width, height, scaledWidth, scaledHeight); } 

Note: je voudrais un algorithme qui fonctionnerait pour beaucoup de zooms, pas juste un zoom.

Mettre à jour:

Idéalement, je voudrais une fonction qui prend un écran Point X, Y et renvoie l’image originale X, Y. La fonction renverrait toujours le X, Y correct après la mise à l’échelle et le zoom

Voici un exemple de travail complet permettant de zoomer sur une image à l’aide de SWT, qui met en œuvre l’idée qui sous-tend la réponse de Leon . L’utilisation de transformations affines est l’approche par défaut pour dessiner des éléments avec des systèmes de coordonnées individuels dans des graphiques 2D.

  1. Utilisez une Transform pour dessiner l’image au bon endroit et à la bonne échelle
  2. Utilisez l’inverse de cette Transform pour obtenir les coordonnées d’image de la région de zoom sélectionnée.
  3. Calculez une nouvelle Transform pour afficher la région agrandie.

La classe ci-dessous fait ce qui suit:

  • La Transform est stockée dans paintTransform .
  • Les coordonnées d’écran de la zone zoomée sont stockées dans zoomStart et zoomEnd
  • Les coordonnées de l’image de la zone sélectionnée sont calculées dans setVisibleImageAreaInScreenCoordinates partir du rectangle de zoom déplacé.
  • La nouvelle Transform est calculée dans setVisibleImageAreaInImageCoordinates
  • Le rest peut être considéré comme un code standard.

Veuillez noter que l’image n’est jamais remplacée par une version à l’échelle. Il est dessiné à l’aide de paintTransform . Cela signifie que le contexte graphique prend soin de peindre l’image mise à l’échelle. Le code de peinture devient aussi simple que

 ev.gc.setTransform(paintTransform); ev.gc.drawImage(img, 0, 0); 

Tout le calcul est effectué lors du traitement lors de la transition d’état déclenchée par les événements de la souris, c’est-à-dire la méthode zoom() appelée dans le gestionnaire mouseUp() .

 import java.io.InputStream; import java.net.URL; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Transform; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; public class Zoom implements PaintListener, MouseMoveListener, MouseListener { private static final int MOUSE_DOWN = 1; private static final int DRAGGING = 2; private static final int NOT_DRAGGING = 3; int dragState = NOT_DRAGGING; Point zoomStart; Point zoomEnd; ImageData imgData; Image img; Transform paintTransform; Shell shell; Color rectColor; public Zoom(ImageData image, Shell shell) { imgData = image; img = new Image(shell.getDisplay(), image); this.shell = shell; rectColor = new Color(shell.getDisplay(), new RGB(255, 255, 255)); } void zoom() { int x0 = Math.min(zoomStart.x, zoomEnd.x); int x1 = Math.max(zoomStart.x, zoomEnd.x); int y0 = Math.min(zoomStart.y, zoomEnd.y); int y1 = Math.max(zoomStart.y, zoomEnd.y); setVisibleImageAreaInScreenCoordinates(x0, y0, x1, y1); } void setVisibleImageAreaInImageCoordinates(float x0, float y0, float x1, float y1) { Point sz = shell.getSize(); double width = x1 - x0; double height = y1 - y0; double sx = (double) sz.x / (double) width; double sy = (double) sz.y / (double) height; float scale = (float) Math.min(sx, sy); // compute offset to center selected rectangle in available area double ox = 0.5 * (sz.x - scale * width); double oy = 0.5 * (sz.y - scale * height); paintTransform.identity(); paintTransform.translate((float) ox, (float) oy); paintTransform.scale(scale, scale); paintTransform.translate(-x0, -y0); } void setVisibleImageAreaInScreenCoordinates(int x0, int y0, int x1, int y1) { Transform inv = invertPaintTransform(); // points in screen coordinates // to be transformed to image coordinates // (top-left and bottom-right corner of selection) float[] points = { x0, y0, x1, y1 }; // actually get image coordinates // (in-place operation on points array) inv.transform(points); inv.dispose(); // extract image coordinates from array float ix0 = points[0]; float iy0 = points[1]; float ix1 = points[2]; float iy1 = points[3]; setVisibleImageAreaInImageCoordinates(ix0, iy0, ix1, iy1); } Transform invertPaintTransform() { // clone paintTransform float[] elems = new float[6]; paintTransform.getElements(elems); Transform inv = new Transform(shell.getDisplay()); inv.setElements(elems[0], elems[1], elems[2], elems[3], elems[4], elems[5]); // invert clone inv.invert(); return inv; } void fitImage() { Point sz = shell.getSize(); double sx = (double) sz.x / (double) imgData.width; double sy = (double) sz.y / (double) imgData.height; float scale = (float) Math.min(sx, sy); paintTransform.identity(); paintTransform.translate(sz.x * 0.5f, sz.y * 0.5f); paintTransform.scale(scale, scale); paintTransform.translate(-imgData.width*0.5f, -imgData.height*0.5f); } @Override public void paintControl(PaintEvent ev) { if (paintTransform == null) { paintTransform = new Transform(shell.getDisplay()); fitImage(); } ev.gc.setTransform(paintTransform); ev.gc.drawImage(img, 0, 0); if (dragState == DRAGGING) { drawZoomRect(ev.gc); } } void drawZoomRect(GC gc) { int x0 = Math.min(zoomStart.x, zoomEnd.x); int x1 = Math.max(zoomStart.x, zoomEnd.x); int y0 = Math.min(zoomStart.y, zoomEnd.y); int y1 = Math.max(zoomStart.y, zoomEnd.y); gc.setTransform(null); gc.setAlpha(0x80); gc.setForeground(rectColor); gc.fillRectangle(x0, y0, x1 - x0, y1 - y0); } public static void main(Ssortingng[] args) throws Exception { URL url = new URL( "https://upload.wikimedia.org/wikipedia/commons/thumb/" + "6/62/Billy_Zoom.jpg/800px-Billy_Zoom.jpg"); InputStream input = url.openStream(); ImageData img; try { img = new ImageData(input); } finally { input.close(); } Display display = new Display(); Shell shell = new Shell(display); shell.setSize(800, 600); Zoom zoom = new Zoom(img, shell); shell.open(); shell.addPaintListener(zoom); shell.addMouseMoveListener(zoom); shell.addMouseListener(zoom); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } @Override public void mouseDoubleClick(MouseEvent e) { } @Override public void mouseDown(MouseEvent e) { if (e.button != 1) { return; } zoomStart = new Point(ex, ey); dragState = MOUSE_DOWN; } @Override public void mouseUp(MouseEvent e) { if (e.button != 1) { return; } if (dragState == DRAGGING) { zoomEnd = new Point(ex, ey); } dragState = NOT_DRAGGING; zoom(); shell.redraw(); } @Override public void mouseMove(MouseEvent e) { if (dragState == NOT_DRAGGING) { return; } if (ex == zoomStart.x && ey == zoomStart.y) { dragState = MOUSE_DOWN; } else { dragState = DRAGGING; zoomEnd = new Point(ex, ey); } shell.redraw(); } } 

Lorsque la fenêtre est redimensionnée, la transformation n’est actuellement pas modifiée. Cela pourrait être implémenté de la même façon que le zoom: calculez les coordonnées d’image précédemment visibles avec l’ancienne taille de la fenêtre, calculez la nouvelle transformation avec la nouvelle taille de la fenêtre.

La méthode selectionToOriginal doit renvoyer un Rectangle avec la position et la dimension de la dernière sélection de zoom par rapport à l’image d’origine.

Il reçoit:

  • scaledDimensions : scaledDimensions avec la dimension de votre image redimensionnée, qui correspond à la sélection du zoom
  • levels : List avec le zoom consécutif Sélection de Rectangle ; dans le premier niveau vous mettez la dimension de l’image originale

Ce programme de test montre son utilisation avec une image originale de dimension 800×600 et de dimension réduite à 400×300. Deux zooms consécutifs lui sont appliqués.

 import java.util.ArrayList; import java.util.List; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; public class ScaleTest { public static void main(Ssortingng[] args) { Point scaledDimensions = new Point(400, 300); List levels = new ArrayList(); // first level is the original image dimension levels.add(new Rectangle(0, 0, 800, 600)); // other levels are the zooming selection inside the scaled image levels.add(new Rectangle(0, 0, 200, 150)); levels.add(new Rectangle(200, 150, 200, 150)); Rectangle selectionToOriginal = selectionToOriginal(scaledDimensions, levels); System.out.println(selectionToOriginal); } public static Rectangle selectionToOriginal(Point scaledDimensions, List levels) { int numberOfLevels = levels.size(); double scaledX = 0; double scaledY = 0; // we will work with the size of the last selection double scaledWidth = levels.get(numberOfLevels - 1).width; double scaledHeight = levels.get(numberOfLevels - 1).height; // start from the last selection to the first for (int currentLevel = numberOfLevels - 1; currentLevel > 0; currentLevel--) { // get the width of the level N - 1 double previousSelectionWidth = levels.get(currentLevel - 1).width; // convert the width of 1 unit in level N to its width in level N - 1 double unitaryWidth = previousSelectionWidth / scaledDimensions.x; // convert the X position in level N in its X position in level N - 1 scaledX = unitaryWidth * (levels.get(currentLevel).x + scaledX); // convert the width in level N in its width in level N - 1 scaledWidth *= unitaryWidth; // get the height of the level N - 1 double previousSelectionHeight = levels.get(currentLevel - 1).height; // convert the height of 1 unit in level N to its height in level N - 1 double unitaryHeight = previousSelectionHeight / scaledDimensions.y; // convert the Y position in level N in its Y position in level N - 1 scaledY = unitaryHeight * (levels.get(currentLevel).y + scaledY); // convert the height in level N in its height in level N - 1 scaledHeight *= unitaryHeight; } return new Rectangle((int) scaledX, (int) scaledY, (int) scaledWidth, (int) scaledHeight); } } 

Le programme renvoie un Rectangle avec la position (200, 150) et la taille (200, 150), l’image montre la situation:

Résultat du programme

Remarques:

  • dans votre code, vous avez utilisé la classe Coordinate qui semble être égale à la classe SWT Point que j’ai utilisée dans ma méthode
  • les moulages dans l’instruction de retour

     return new Rectangle((int) scaledX, (int) scaledY, (int) scaledWidth, (int) scaledHeight); 

    tronquera la valeur des doublons, envisagez plutôt d’utiliser Math.round si vous préférez arrondir les valeurs

SWT a une classe dédiée, Transform pour effectuer les conversions de coordonnées (je dirais plutôt transformations, car la traduction dans un tel contexte n’est qu’un cas particulier, les autres transformations étant la mise à l’échelle , la rotation et le cisaillement ). AWT a une classe AffineTransform plus pratique qui n’est pas liée au sous-système graphique.

L’utilisation de l’une de ces classes simplifie les choses comme suit. Une fois que vous avez construit l’object de transformation qui mappe les coordonnées dans une direction (par exemple, les coordonnées de l’image source pour afficher les coordonnées), vous pouvez facilement obtenir la transformation inverse (pour revenir des coordonnées d’affichage aux coordonnées de l’image source). Utilisez les createInverse() invert() ou createInverse() (cette dernière, uniquement avec AffineTransform ) à cette fin.

Effectuez la conversion de coordonnées réelle avec la méthode transform() . Dans le cas de SWT.Transform sa signature est un peu gênante si vous devez transformer un seul point, mais vous pouvez facilement l’envelopper dans une fonction d’assistance.

Pour vos besoins, vous devrez utiliser uniquement les méthodes scale() et translate() pour définir votre transformation de coordonnées. Vous voudrez probablement définir votre transformation en fonction des rectangles source et cible (similaire à votre utilisation de la méthode drawImage() ); cette réponse montre comment cela peut être fait. Ensuite, lorsque vous effectuez un zoom ou que vous manipulez autrement votre image, vous devez conserver l’object de transformation à jour.

METTRE À JOUR

@code_onkel a fourni un exemple de programme utilisant cette approche.

Voici ma tentative.

 private static Point transformPoint(ArrayList rectangleLevels, PointF intPoint) { RectF sourceRec = rectangleLevels.get(rectangleLevels.size()-1); Point sourcePoint = new Point((int)intPoint.X, (int)intPoint.Y); Point retPoint = sourcePoint; for (int i = rectangleLevels.size()-2; i >=0; i--) { RectF destRec = rectangleLevels.get(i); retPoint = transformPoint(sourceRec, destRec, sourcePoint); // Current destination point and rec become source for next round sourcePoint = retPoint; sourceRec = destRec; } return retPoint; } /* Rectangle 1 has (x1, y1) origin and (w1, h1) for width and height, and Rectangle 2 has (x2, y2) origin and (w2, h2) for width and height, then Given point (x, y) in terms of Rectangle 1 co-ords, to convert it to Rectangle 2 co-ords: xNew = ((x-x1)/w1)*w2 + x2; yNew = ((y-y1)/h1)*h2 + y2; */ private static Point transformPoint(RectF source, RectF destination, Point intPoint) { PointF point = new PointF(); point.X = intPoint.x; point.Y = intPoint.y; return transformPoint(source, destination, point); } private static Point transformPoint(RectF source, RectF destination, PointF point) { return new Point( (int) (((point.X - source.X) / source.Width) * destination.Width + destination.X), (int) (((point.Y - source.Y) / source.Height) * destination.Height + destination.Y)); } 

Donc, je dois juste garder une trace de mon redimensionnement et de mon zoom, puis passer l’écran X, Y pour obtenir mes x, y de l’image originale:

  ArrayList rectangleLevels = new ArrayList(); RectF origImage = getRectangle(0,0,320,200); RectF scaledImage = getRectangle(0,0,800,800); RectF zoomedImage = getRectangle(310,190,10,10); RectF scaledZoomedImage = getRectangle(0,0,800,800); rectangleLevels.add(origImage); rectangleLevels.add(scaledImage); rectangleLevels.add(zoomedImage); rectangleLevels.add(scaledZoomedImage); PointF pointInZoomedImg = getPoint(799, 799); Point retPoint = transformPoint(rectangleLevels, pointInZoomedImg);