2017-01-13 21 views
9

Я пытаюсь использовать алгоритм Fill Flood для создания инструмента Cube Painting для приложения.Получение неточных координат касания в ImageView FloodFill Algorithm

Это код алгоритма:

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; 
     } 
    }); 

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

Должен ли я обрабатывать координаты своих событий перед отправкой их алгоритму Filler? Я также попробовал полноэкранный образ, и это продолжается.

Любая помощь приветствуется!

ответ

2

Ваша ситуация напомнила мне this blog post, поскольку это может быть та же проблема, которую он исправил там. Он должен был принять во внимание преобразование, которое растровое изображение прошло, прежде чем оно было помещено в ImageView.

Вместо того, чтобы использовать только getX() и getY() на события в его OnTouchListener он использовал getPointerCoords(event)[0] и getPointerCoords(event)[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; 
} 

Обратите внимание, что если на самом деле это решение для вас - вы можете использовать getImageMatrix() метод на вашей переменной imagen внутри метода или сохранить матрицу в конечной переменной и использовать ее в 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; 
    } 
}); 

Надежда, которая решает эту проблему.

+0

Сохраните массив, полученный в результате 'getPointerCoords', вместо того, чтобы выполнять некоторые работы Matrix дважды. – AxelH

+0

@AxelH ты абсолютно прав. Изменено это в коде. –

+0

Благодарим за отзыв. К сожалению, проблема продолжает происходить. Часть, которую я касаюсь, не является той частью, которую нарисовали. Кстати, методы getScrollX() и getScrollY() не могут быть разрешены. Думаю, это imagen.getScrollX()? Ему не нужен вид, который нужно вызвать? –