当前位置: 首页 > 知识库问答 >
问题:

ImageView泛光填充算法中获取触摸坐标不准确

濮君植
2023-03-14

我正在尝试使用填充洪水算法为应用程序制作立方体绘制工具。

这是算法的代码:

public class QueueLinearFloodFiller {

protected Bitmap image = null;
protected int[] tolerance = new int[] { 0, 0, 0 };
protected int width = 0;
protected int height = 0;
protected int[] pixels = null;
protected int fillColor = 0;
protected int[] startColor = new int[] { 0, 0, 0 };
protected boolean[] pixelsChecked;
protected Queue<FloodFillRange> ranges;

// Construct using an image and a copy will be made to fill into,
// Construct with BufferedImage and flood fill will write directly to
// provided BufferedImage
public QueueLinearFloodFiller(Bitmap img) {
    copyImage(img);
}

public QueueLinearFloodFiller(Bitmap img, int targetColor, int newColor) {
    useImage(img);

    setFillColor(newColor);
    setTargetColor(targetColor);
}

public void setTargetColor(int targetColor) {
    startColor[0] = Color.red(targetColor);
    startColor[1] = Color.green(targetColor);
    startColor[2] = Color.blue(targetColor);
}

public int getFillColor() {
    return fillColor;
}

public void setFillColor(int value) {
    fillColor = value;
}

public int[] getTolerance() {
    return tolerance;
}

public void setTolerance(int[] value) {
    tolerance = value;
}

public void setTolerance(int value) {
    tolerance = new int[] { value, value, value };
}

public Bitmap getImage() {
    return image;
}

public void copyImage(Bitmap img) {
    // Copy data from provided Image to a BufferedImage to write flood fill
    // to, use getImage to retrieve
    // cache data in member variables to decrease overhead of property calls
    width = img.getWidth();
    height = img.getHeight();

    image = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
    Canvas canvas = new Canvas(image);
    canvas.drawBitmap(img, 0, 0, null);

    pixels = new int[width * height];

    image.getPixels(pixels, 0, width, 1, 1, width - 1, height - 1);
}

public void useImage(Bitmap img) {
    // Use a pre-existing provided BufferedImage and write directly to it
    // cache data in member variables to decrease overhead of property calls
    width = img.getWidth();
    height = img.getHeight();
    image = img;

    pixels = new int[width * height];

    image.getPixels(pixels, 0, width, 1, 1, width - 1, height - 1);
}

protected void prepare() {
    // Called before starting flood-fill
    pixelsChecked = new boolean[pixels.length];
    ranges = new LinkedList<>();
}

// Fills the specified point on the bitmap with the currently selected fill
// color.
// int x, int y: The starting coords for the fill
public void floodFill(int x, int y) {
    // Setup
    prepare();

    if (startColor[0] == 0) {
        // ***Get starting color.
        int startPixel = pixels[(width * y) + x];
        startColor[0] = (startPixel >> 16) & 0xff;
        startColor[1] = (startPixel >> 8) & 0xff;
        startColor[2] = startPixel & 0xff;
    }

    // ***Do first call to floodfill.
    LinearFill(x, y);

    // ***Call floodfill routine while floodfill ranges still exist on the
    // queue
    FloodFillRange range;

    while (ranges.size() > 0) {
        // **Get Next Range Off the Queue
        range = ranges.remove();

        // **Check Above and Below Each Pixel in the Floodfill Range
        int downPxIdx = (width * (range.Y + 1)) + range.startX;
        int upPxIdx = (width * (range.Y - 1)) + range.startX;
        int upY = range.Y - 1;// so we can pass the y coord by ref
        int downY = range.Y + 1;

        for (int i = range.startX; i <= range.endX; i++) {
            // *Start Fill Upwards
            // if we're not above the top of the bitmap and the pixel above
            // this one is within the color tolerance
            if (range.Y > 0 && (!pixelsChecked[upPxIdx])
                    && CheckPixel(upPxIdx))
                LinearFill(i, upY);

            // *Start Fill Downwards
            // if we're not below the bottom of the bitmap and the pixel
            // below this one is within the color tolerance
            if (range.Y < (height - 1) && (!pixelsChecked[downPxIdx])
                    && CheckPixel(downPxIdx))
                LinearFill(i, downY);

            downPxIdx++;
            upPxIdx++;
        }
    }

    image.setPixels(pixels, 0, width, 1, 1, width - 1, height - 1);
}

// Finds the furthermost left and right boundaries of the fill area
// on a given y coordinate, starting from a given x coordinate, filling as
// it goes.
// Adds the resulting horizontal range to the queue of floodfill ranges,
// to be processed in the main loop.

// int x, int y: The starting coords
protected void LinearFill(int x, int y) {
    // ***Find Left Edge of Color Area
    int lFillLoc = x; // the location to check/fill on the left
    int pxIdx = (width * y) + x;

    while (true) {
        // **fill with the color
        pixels[pxIdx] = fillColor;

        // **indicate that this pixel has already been checked and filled
        pixelsChecked[pxIdx] = true;

        // **de-increment
        lFillLoc--; // de-increment counter
        pxIdx--; // de-increment pixel index

        // **exit loop if we're at edge of bitmap or color area
        if (lFillLoc < 0 || (pixelsChecked[pxIdx]) || !CheckPixel(pxIdx)) {
            break;
        }
    }

    lFillLoc++;

    // ***Find Right Edge of Color Area
    int rFillLoc = x; // the location to check/fill on the left

    pxIdx = (width * y) + x;

    while (true) {
        // **fill with the color
        pixels[pxIdx] = fillColor;

        // **indicate that this pixel has already been checked and filled
        pixelsChecked[pxIdx] = true;

        // **increment
        rFillLoc++; // increment counter
        pxIdx++; // increment pixel index

        // **exit loop if we're at edge of bitmap or color area
        if (rFillLoc >= width || pixelsChecked[pxIdx] || !CheckPixel(pxIdx)) {
            break;
        }
    }

    rFillLoc--;

    // add range to queue
    FloodFillRange r = new FloodFillRange(lFillLoc, rFillLoc, y);

    ranges.offer(r);
}

// Sees if a pixel is within the color tolerance range.
protected boolean CheckPixel(int px) {
    int red = (pixels[px] >>> 16) & 0xff;
    int green = (pixels[px] >>> 8) & 0xff;
    int blue = pixels[px] & 0xff;

    return (red >= (startColor[0] - tolerance[0])
            && red <= (startColor[0] + tolerance[0])
            && green >= (startColor[1] - tolerance[1])
            && green <= (startColor[1] + tolerance[1])
            && blue >= (startColor[2] - tolerance[2]) && blue <= (startColor[2] + tolerance[2]));
}

// Represents a linear range to be filled and branched from.
protected class FloodFillRange {
    public int startX;
    public int endX;
    public int Y;

    public FloodFillRange(int startX, int endX, int y) {
        this.startX = startX;
        this.endX = endX;
        this.Y = y;
    }
}

}

这是我在ImageView上处理触摸事件的部分:

filler.setTolerance(150);

    imagen.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            int x = (int)motionEvent.getX();
            int y = (int)motionEvent.getY();
            filler.prepare();
            filler.floodFill(x, y);
            imagen.setImageBitmap(filler.getImage());
            return false;
        }
    });

问题是坐标不准确。我的意思是,无论我在哪里触摸图像,它都会被画在我没有画的其他部分。

在将事件坐标发送到填充算法之前,是否需要处理它们?我也尝试过全屏图像,这种情况一直在发生。

共有1个答案

羊舌源
2023-03-14

你的情况提醒了我这篇博文,因为这可能与他在那里解决的问题相同。在将位图放入ImageView之前,他必须考虑位图经过的转换。

他没有在他的OnTouchListener中的事件上仅使用getX()getY(),而是分别使用了getPointerCo的(事件)[0]getPointerCo的(事件)[1]并创建了这个方法:

 final float[] getPointerCoords(MotionEvent e)
{
    final int index = e.getActionIndex();
    final float[] coords = new float[] { e.getX(index), e.getY(index) };
    Matrix matrix = new Matrix();
    getImageMatrix().invert(matrix); //his drawable view extends ImageView 
                                  //so it has access to the getImageMatrix.
    matrix.postTranslate(getScrollX(), getScrollY());
    matrix.mapPoints(coords);
    return coords;
}

请注意,如果这确实是您的解决方案-您可以在方法内部的Imagen变量上使用getImageMatrix()方法,或将矩阵保存为最终变量并在OnTouchListener中使用它。

另请注意,如果这是问题-您还应该在设置FillFlood以构建相应的维度时考虑转换(这意味着您之前的尝试导致矩阵太大/太小与原始位图相比)。

为您提供“一体式”(使用此解决方案的一种方法):

final Matrix transformationMatrix = new Matrix();
imagen.getImageMatrix().invert(transformationMatrix);
transformationMatrix.postTranslate(imagen.getScrollX(), imagen.getScrollY());
imagen.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        float[] transformedCoords = getPointerCoords(event);
        int x = (int)transformedCoords[0];
        int y = (int)transformedCoords[1];
        filler.prepare();
        filler.floodFill(x, y);
        imagen.setImageBitmap(filler.getImage());
        return false;
    }

    final float[] getPointerCoords(MotionEvent e) {
        final int index = e.getActionIndex();
        final float[] coords = new float[] { e.getX(index), e.getY(index) };
        transformationMatrix.mapPoints(coords);
        return coords;
    }
});

希望能解决这个问题。

 类似资料:
  • 问题内容: 所以我一直在四处寻找屏幕上触摸的坐标。到目前为止,我可以通过以下方式获得一触的坐标: 但是当用两根手指触摸时,我只会得到第一次触摸的坐标。多点触控功能(我用这个小教程进行了测试:http : //www.techotopia.com/index.php/An_Example_Swift_iOS_8_Touch,_Multitouch_and_Tap_Application)。所以我的问

  • 问题内容: 我尝试从点击触摸屏的位置获取坐标,以在此时放置特定的UIImage。 我怎样才能做到这一点? 问题答案: 在子类中,例如: 这将返回一个视图坐标。 使用Swift 3语法更新 使用Swift 4语法更新

  • 我正在编程一个简单的绘画应用程序使用Java。我试图使用洪水填充算法的递归实现作为我的“桶填充”工具。 我想知道是否有一种方法仍然使用递归与这个算法,而不得到这个错误。 如果没有,这个算法有哪些可能的非递归实现,我可以在我的程序中使用?

  • 我想知道是否有可能在GLSurfaceView上进行ImageView并获得两者各自的触摸事件。我的问题是,我想知道ImageView何时被按下(因此我将使用OnTouchListener)。但是,我仍然希望能够获得我的glsurfaceview的触碰事件。 提前感谢, 编辑:这是我的activity_main.xml

  • 问题内容: 我在Android应用程序中向用户显示图像。 我希望能够告诉他们“触摸”图像的位置。 我可以通过实现OnTouchListener轻松获取屏幕坐标 但是,这些是绝对坐标。我真正需要的是一个将其转换为相对于View的坐标的函数。 我做了一些挖掘工作,但是除了尝试自己实现ScreenToClient方法之外,我似乎什么也没做。 有谁知道一个好的解决方案,还是我只需要自己动手? 似乎可以使用

  • 问题内容: 拥有3m的microtouch显示器。它通过USB连接到我的debian系统,并重新识别为人机界面(hid)。我正在尝试访问和推送实时信息…如果被触摸,我想知道(x,y)的位置,并将其通过netcat通过管道传送到另一台主机。 不幸的是,我只能使用 要么 您会得到似乎无处可查的十六进制代码… 有人知道如何获取这些信息吗?必须有一种从十六进制代码中提取它的方法。不幸的是,我不知道如何解释