Java swt - 在缩放和缩放后从图像中获取实际的 x,y 坐标
Java swt - Get actual x,y coordinates from Image after scaling and zooming
我有一张图片已缩放以适合。用户正在从缩放后的图像中选择一个矩形。
然后我根据这个选择重新绘制:
gc.drawImage(imageDisplayed, minX, minY, width, height, imageDisplayed.getBounds().x, imageDisplayed.getBounds().y, imageDisplayed.getBounds().width, imageDisplayed.getBounds().height );
所以现在我希望能够从缩放和缩放图像中获取原始坐标。这是正确的吗?:
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 x.y 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);
}
注意:我想要一种适用于多种缩放的算法,而不仅仅是一种缩放。
更新:
理想情况下,我想要一个采用屏幕点 X、Y 和 return 原始图像 X、Y 的函数。在缩放和缩放
之后,该函数仍然 return 正确的 X、Y
SWT 有一个未绑定到图形子系统的专用 class Transform
for performing coordinate translations (I'd rather say transformations, since translation in such context is just a special case, the other transformations being scaling, rotation and shearing). AWT has a more convenient AffineTransform
class。
使用这些 classes 之一可以简化如下。一旦构建了一个方向映射坐标的变换对象(例如源图像坐标到显示坐标),您就可以轻松获得逆变换(用于从显示坐标返回到源图像坐标)。为此,请使用 invert()
或 createInverse()
(后者,仅与 AffineTransform
一起使用)方法。
用transform()
方法进行实际的坐标转换。在 SWT.Transform
的情况下,如果您需要转换单个点,它的签名有点不方便,但您可以轻松地将其包装在辅助函数中。
为了您的目的,您将只需要使用 scale()
和 translate()
方法来定义坐标转换。您很可能希望根据源矩形和目标矩形来定义变换(类似于您对 drawImage()
方法的使用); this answer 展示了如何做到这一点。然后,当您缩放或以其他方式操纵图像的显示方式时,您必须使变换对象保持最新。
更新
@code_onkel has provided 使用这种方法。
方法 selectionToOriginal
应该 return Rectangle
上次缩放选择相对于原始图像的位置和尺寸。
收到:
scaledDimensions
:Point
缩放图像的尺寸,这是执行缩放选择的地方
levels
:List
连续缩放Rectangle
个选择;在第一级你把原始图像的尺寸
此测试程序展示了其在尺寸为 800x600 和缩放尺寸为 400x300 的原始图像中的使用。对其应用了两次连续的缩放选择。
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(String[] args) {
Point scaledDimensions = new Point(400, 300);
List<Rectangle> levels = new ArrayList<Rectangle>();
// 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<Rectangle> 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);
}
}
程序return一个Rectangle
,位置(200, 150),大小(200, 150),图片显示情况:
备注:
- 在您的代码中,您使用了 class
Coordinate
,它似乎等于我在方法中使用的 SWT
class Point
return 指令中的转换
return new Rectangle((int) scaledX, (int) scaledY, (int) scaledWidth,
(int) scaledHeight);
将截断双精度值,如果您更喜欢舍入值,请考虑使用 Math.round
代替
这是一个使用 SWT 放大图像的完整工作示例,它实现了 . Using affine transformations 背后的想法,这是在 2D 图形中使用单独坐标系绘制元素的默认方法。
- 使用
Transform
在正确的位置和比例绘制图片
- 使用
Transform
的倒数获取所选缩放区域的图像坐标。
- 计算一个新的
Transform
来显示缩放区域。
下面的 class 执行以下操作:
-
Transform
存储在 paintTransform
中。
- 缩放区域的屏幕坐标存储在
zoomStart
和zoomEnd
中
- 所选区域的图像坐标在
setVisibleImageAreaInScreenCoordinates
中从拖动的缩放矩形计算。
- 新的
Transform
在setVisibleImageAreaInImageCoordinates
中计算
- 其余大部分都可以视为样板代码。
请注意,图片绝不会被缩放后的版本替换。它是使用 paintTransform
绘制的。这意味着图形上下文负责绘制缩放后的图像。实际绘画代码变得如此简单
ev.gc.setTransform(paintTransform);
ev.gc.drawImage(img, 0, 0);
所有的计算都是在鼠标事件触发的状态转换处理期间完成的,即在mouseUp()
处理程序中调用的zoom()
方法。
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(String[] 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(e.x, e.y);
dragState = MOUSE_DOWN;
}
@Override
public void mouseUp(MouseEvent e) {
if (e.button != 1) {
return;
}
if (dragState == DRAGGING) {
zoomEnd = new Point(e.x, e.y);
}
dragState = NOT_DRAGGING;
zoom();
shell.redraw();
}
@Override
public void mouseMove(MouseEvent e) {
if (dragState == NOT_DRAGGING) {
return;
}
if (e.x == zoomStart.x && e.y == zoomStart.y) {
dragState = MOUSE_DOWN;
} else {
dragState = DRAGGING;
zoomEnd = new Point(e.x, e.y);
}
shell.redraw();
}
}
当 window 调整大小时,转换目前没有改变。这可以通过与缩放相同的方式实现:使用旧 window 大小计算先前可见的图像坐标,使用新 window 大小计算新变换。
这是我的尝试。
private static Point transformPoint(ArrayList<RectF> 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));
}
所以这意味着我只需要跟踪缩放和缩放,然后传入屏幕 X、Y 以从原始图像获取 x、y:
ArrayList<RectF> rectangleLevels = new ArrayList<RectF>();
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);
我有一张图片已缩放以适合。用户正在从缩放后的图像中选择一个矩形。
然后我根据这个选择重新绘制:
gc.drawImage(imageDisplayed, minX, minY, width, height, imageDisplayed.getBounds().x, imageDisplayed.getBounds().y, imageDisplayed.getBounds().width, imageDisplayed.getBounds().height );
所以现在我希望能够从缩放和缩放图像中获取原始坐标。这是正确的吗?:
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 x.y 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);
}
注意:我想要一种适用于多种缩放的算法,而不仅仅是一种缩放。
更新:
理想情况下,我想要一个采用屏幕点 X、Y 和 return 原始图像 X、Y 的函数。在缩放和缩放
之后,该函数仍然 return 正确的 X、YSWT 有一个未绑定到图形子系统的专用 class Transform
for performing coordinate translations (I'd rather say transformations, since translation in such context is just a special case, the other transformations being scaling, rotation and shearing). AWT has a more convenient AffineTransform
class。
使用这些 classes 之一可以简化如下。一旦构建了一个方向映射坐标的变换对象(例如源图像坐标到显示坐标),您就可以轻松获得逆变换(用于从显示坐标返回到源图像坐标)。为此,请使用 invert()
或 createInverse()
(后者,仅与 AffineTransform
一起使用)方法。
用transform()
方法进行实际的坐标转换。在 SWT.Transform
的情况下,如果您需要转换单个点,它的签名有点不方便,但您可以轻松地将其包装在辅助函数中。
为了您的目的,您将只需要使用 scale()
和 translate()
方法来定义坐标转换。您很可能希望根据源矩形和目标矩形来定义变换(类似于您对 drawImage()
方法的使用); this answer 展示了如何做到这一点。然后,当您缩放或以其他方式操纵图像的显示方式时,您必须使变换对象保持最新。
更新
@code_onkel has provided
方法 selectionToOriginal
应该 return Rectangle
上次缩放选择相对于原始图像的位置和尺寸。
收到:
scaledDimensions
:Point
缩放图像的尺寸,这是执行缩放选择的地方levels
:List
连续缩放Rectangle
个选择;在第一级你把原始图像的尺寸
此测试程序展示了其在尺寸为 800x600 和缩放尺寸为 400x300 的原始图像中的使用。对其应用了两次连续的缩放选择。
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(String[] args) {
Point scaledDimensions = new Point(400, 300);
List<Rectangle> levels = new ArrayList<Rectangle>();
// 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<Rectangle> 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);
}
}
程序return一个Rectangle
,位置(200, 150),大小(200, 150),图片显示情况:
备注:
- 在您的代码中,您使用了 class
Coordinate
,它似乎等于我在方法中使用的SWT
classPoint
return 指令中的转换
return new Rectangle((int) scaledX, (int) scaledY, (int) scaledWidth, (int) scaledHeight);
将截断双精度值,如果您更喜欢舍入值,请考虑使用
Math.round
代替
这是一个使用 SWT 放大图像的完整工作示例,它实现了
- 使用
Transform
在正确的位置和比例绘制图片 - 使用
Transform
的倒数获取所选缩放区域的图像坐标。 - 计算一个新的
Transform
来显示缩放区域。
下面的 class 执行以下操作:
-
Transform
存储在paintTransform
中。 - 缩放区域的屏幕坐标存储在
zoomStart
和zoomEnd
中
- 所选区域的图像坐标在
setVisibleImageAreaInScreenCoordinates
中从拖动的缩放矩形计算。 - 新的
Transform
在setVisibleImageAreaInImageCoordinates
中计算
- 其余大部分都可以视为样板代码。
请注意,图片绝不会被缩放后的版本替换。它是使用 paintTransform
绘制的。这意味着图形上下文负责绘制缩放后的图像。实际绘画代码变得如此简单
ev.gc.setTransform(paintTransform);
ev.gc.drawImage(img, 0, 0);
所有的计算都是在鼠标事件触发的状态转换处理期间完成的,即在mouseUp()
处理程序中调用的zoom()
方法。
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(String[] 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(e.x, e.y);
dragState = MOUSE_DOWN;
}
@Override
public void mouseUp(MouseEvent e) {
if (e.button != 1) {
return;
}
if (dragState == DRAGGING) {
zoomEnd = new Point(e.x, e.y);
}
dragState = NOT_DRAGGING;
zoom();
shell.redraw();
}
@Override
public void mouseMove(MouseEvent e) {
if (dragState == NOT_DRAGGING) {
return;
}
if (e.x == zoomStart.x && e.y == zoomStart.y) {
dragState = MOUSE_DOWN;
} else {
dragState = DRAGGING;
zoomEnd = new Point(e.x, e.y);
}
shell.redraw();
}
}
当 window 调整大小时,转换目前没有改变。这可以通过与缩放相同的方式实现:使用旧 window 大小计算先前可见的图像坐标,使用新 window 大小计算新变换。
这是我的尝试。
private static Point transformPoint(ArrayList<RectF> 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));
}
所以这意味着我只需要跟踪缩放和缩放,然后传入屏幕 X、Y 以从原始图像获取 x、y:
ArrayList<RectF> rectangleLevels = new ArrayList<RectF>();
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);