Java swt - 缩放和缩放后从Image获取实际的x,y坐标

Rob*_*boy 16 java swt image image-processing coordinates

我有一个缩放到适合的图像.从缩放的图像中,用户正在选择矩形.

然后我根据这个选择重新绘制:

gc.drawImage(imageDisplayed, minX, minY, width, height, imageDisplayed.getBounds().x,  imageDisplayed.getBounds().y, imageDisplayed.getBounds().width, imageDisplayed.getBounds().height );
Run Code Online (Sandbox Code Playgroud)

所以现在我希望能够从缩放和缩放的图像中获得原始坐标.它是否正确?:

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);
}
Run Code Online (Sandbox Code Playgroud)

注意:我想要一种适用于许多缩放的算法,而不仅仅是一个缩放.

更新:

理想情况下,我想要一个采用屏幕点X,Y的函数,并返回原始图像X,Y.在缩放和缩放后,该函数仍将返回正确的X,Y

Lor*_*uro 9

该方法selectionToOriginal应返回a 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);
    }

}
Run Code Online (Sandbox Code Playgroud)

程序返回Rectangle位置(200,150)和大小(200,150),图像显示情况:

计划结果

笔记:

  • 在你的代码中,你使用了Coordinate类似于我在我的方法中使用的SWTPoint
  • 返回指令中的演员

    return new Rectangle((int) scaledX, (int) scaledY, (int) scaledWidth,
            (int) scaledHeight);
    
    Run Code Online (Sandbox Code Playgroud)

    将截断双精度值,Math.round如果您希望舍入值,请考虑使用


Leo*_*eon 8

SWT有一个专门的类Transform用于进行坐标转换(我宁愿说转换,因为转换在这样的背景下只是一个特例,其他的变换是缩放,旋转剪切).AWT有一个更方便的AffineTransform类,它没有绑定到图形子系统.

使用这些类之一简化了如下操作.一旦构造了在一个方向上映射坐标的变换对象(例如,源图像坐标到显示坐标),就可以轻松获得逆变换(从显示坐标返回到源图像坐标).为此使用invert()createInverse()(后者,仅与AffineTransform)方法.

使用transform()方法执行实际坐标转换.在的情况下,SWT.Transform其标志是有点不方便,如果你需要变换一个点,但你可以很容易地将它包装在一个辅助功能.

出于您的目的,您只需要使用scale()translate()方法来定义坐标转换.很可能你会想要根据源矩形和目标矩形来定义变换(类似于你对drawImage()方法的使用); 这个答案显示了如何做到这一点.然后,在缩放或以其他方式操纵图像的显示方式时,必须使变换对象保持最新.

UPDATE

@code_onkel使用这种方法提供了一个示例程序.


cod*_*kel 8

这是一个完整的工作示例,使用SWT放大图像,实现了Leon的答案背后的想法.使用仿射变换是在2D图形中使用单独坐标系绘制元素的默认方法.

  1. 使用a Transform在正确的位置和比例绘制图片
  2. 使用相反的方法Transform来获取所选缩放区域的图像坐标.
  3. 计算新的Transform以显示缩放区域.

下面的课程如下:

  • Transform存储在paintTransform.
  • 缩放区域的屏幕坐标存储在zoomStart和中zoomEnd
  • setVisibleImageAreaInScreenCoordinates从拖动的缩放矩形计算所选区域的图像坐标.
  • Transform的计算方法是setVisibleImageAreaInImageCoordinates
  • 其余大多数可以被认为是样板代码.

请注意,图像永远不会被缩放版本替换.它是使用paintTransform.这意味着图形上下文负责绘制缩放的图像.实际的绘画代码变得如此简单

ev.gc.setTransform(paintTransform);
ev.gc.drawImage(img, 0, 0);
Run Code Online (Sandbox Code Playgroud)

所有计算都是在鼠标事件触发的状态转换期间处理期间完成的,即处理程序中zoom()调用的方法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(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();
    }
}
Run Code Online (Sandbox Code Playgroud)

调整窗口大小时,当前不会更改转换.这可以通过与缩放相同的方式实现:使用旧窗口大小计算先前可见的图像坐标,使用新窗口大小计算新变换.