【问题标题】:Android: How to do this framing paint?Android:这个框架油漆怎么做?
【发布时间】:2012-04-02 15:24:18
【问题描述】:

我有一些静态图像,如下所示:

现在,我想要的是,当我触摸脸部或手部时,应在该皮肤部分填充所选颜色。

见下图结果:

那么如何得到上面的结果呢?? 重做和撤消功能也应该在那里。

我尝试过使用 FloodFill 颜色,但这样做我只能在特定部分进行颜色处理。因为 FloodFill 只填充颜色,直到出现相同的 pixwl 颜色。如果触摸位置像素颜色发生变化,它将不会在其上填充颜色。

所以 Usinf FloodFill 我得到了如下图所示的结果,如果我按下手,那么只有手部分会填充颜色,而不是我想将颜色填充到另一只手和脸。

所以请在这种情况下帮助我。

已编辑

经过一些回复,我得到了类似this one的解决方案。

但仍然存在内存问题。绘制颜色会消耗大量内存。那么请任何人都可以帮助我吗?

【问题讨论】:

    标签: android android-canvas draw flood-fill


    【解决方案1】:

    你可以让一个完整的图像以实际的方式着色,当你用一种颜色填充某个区域时,它将替换由该颜色指定的所有要填充的区域。

    外行术语:

    1. 用户将点击 OUTLINE 的手
    2. 将使用具有完美颜色编码区域的另一张图像检查该点击位置。在这种情况下,我们称它为 MASK。所有皮肤区域都将具有相同的颜色。衬衫区域将是另一种颜色。
    3. 无论用户在何处单击,用户选择的颜色都将应用于 MASK 中具有相似颜色的每个像素,但不是直接在 MASK 上绘制,而是在 OUTLINE 的像素上绘制。

    我希望这会有所帮助。

    如果您想要一个示例,请随时发表评论,然后我可以更新答案,但我认为您可以从这里获得。

    编辑:

    基本上从这样一个简单的图像开始。我们可以称之为OUTLINE

    那么作为开发者,你必须做一些工作。在这里,您对 OUTLINE 进行颜色编码。我们称之为MASK的结果。为此,我们使用您想要的相同颜色对区域进行颜色编码。这可以在油漆或任何东西上完成。我用 Photoshop 很酷,哈哈:D.

    然后是 ALGORITHM 让它在手机上运行。在阅读代码之前,请先查看此变量。

    int ANTILAISING_TOLERANCE = 70; //Larger better coloring, reduced sensing
    

    如果您放大图像,特别注意边框的黑色区域,您实际上可以看到,有时计算机会稍微混合颜色。为了解释这种变化,我们使用了这个容差值。

    COLORINGANDROIDACTIVITY.JAVA

    package mk.coloring;
    
    import android.app.Activity;
    import android.graphics.Bitmap;
    import android.graphics.Bitmap.Config;
    import android.graphics.BitmapFactory;
    import android.graphics.Canvas;
    import android.os.Bundle;
    import android.view.MotionEvent;
    import android.view.View;
    import android.widget.ImageView;
    import android.view.View.OnTouchListener;
    
    public class ColoringAndroidActivity extends Activity implements OnTouchListener{
        /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        findViewById(R.id.imageView1).setOnTouchListener(this);
    }
    
    int ANTILAISING_TOLERANCE = 70;
    public boolean onTouch(View arg0, MotionEvent arg1) {
        Bitmap mask = BitmapFactory.decodeResource(getResources(), R.drawable.mask);
        int selectedColor = mask.getPixel((int)arg1.getX(),(int)arg1.getY());           
        int sG = (selectedColor & 0x0000FF00) >> 8;
        int sR = (selectedColor & 0x00FF0000) >> 16;
        int sB = (selectedColor & 0x000000FF);
    
        Bitmap original = BitmapFactory.decodeResource(getResources(), R.drawable.empty);       
        Bitmap colored = Bitmap.createBitmap(mask.getWidth(), mask.getHeight(), Config.ARGB_8888);
        Canvas cv = new Canvas(colored);
        cv.drawBitmap(original, 0,0, null);
    
        for(int x = 0; x<mask.getWidth();x++){
            for(int y = 0; y<mask.getHeight();y++){
                int g = (mask.getPixel(x,y) & 0x0000FF00) >> 8;
                int r = (mask.getPixel(x,y) & 0x00FF0000) >> 16;
                int b = (mask.getPixel(x,y) & 0x000000FF);
                if(Math.abs(sR - r) < ANTILAISING_TOLERANCE && Math.abs(sG - g) < ANTILAISING_TOLERANCE && Math.abs(sB - b) < ANTILAISING_TOLERANCE)
                    colored.setPixel(x, y, (colored.getPixel(x, y) & 0xFF000000) | 0x00458414);
            }
        }
        ((ImageView)findViewById(R.id.imageView1)).setImageBitmap(colored);
    
        return true;
    }
    

    }

    此代码没有为用户提供很多颜色选择。相反,如果用户触摸一个区域,它会查看 MASK 并相应地绘制 OUTLINE。但是,您可以制作出真正有趣和互动的作品。

    结果

    当我抚摸那个男人的头发时,它不仅染上了头发,而且把他的衬衫和手染上了相同的颜色。将其与 MASK 进行比较,以更好地了解发生了什么。

    这只是一个基本的想法。我创建了多个位图,但实际上并不需要。我将它用于测试目的并占用了不必要的内存。而且您无需在每次点击时重新创建蒙版等。

    希望对你有帮助:D

    祝你好运

    【讨论】:

      【解决方案2】:

      使用 FloodFill 算法。 Fill the complete canvas but keep the bound fill area as it is like circle, rectangle。您也可以查看此链接。 Android: How to fill color to the specific part of the Image only?。一般的想法是在点击时获得 x 和 y 坐标。

       final Point p1 = new Point(); 
       p1.x=(int) x; p1.y=(int) y; X and y are co-ordinates when user clicks on the screen
       final int sourceColor= mBitmap.getPixel((int)x,(int) y);
       final int targetColor =mPaint.getColor();
       new TheTask(mDrawingManager.mDrawingUtilities.mBitmap, p1, sourceColor, targetColor).execute(); //Use AsyncTask and do floodfillin the doinBackground().
      

      查看上述链接以了解 android 中的 Floodfill 算法。这应该可以帮助你实现你想要的。 Android FingerPaint Undo/Redo implementation。这应该可以帮助您根据您的撤消和重做需求进行修改。

      编辑:

      stackoverflow 上的一篇文章让我找到了一种使用洪水填充算法的有效方法,没有延迟和 OOM。

      从 SO 帖子中挑选

      使用上述洪水填充算法可以很好地填充一个小的封闭区域。然而,对于大面积,该算法运行缓慢并且消耗大量内存。最近我遇到了一个使用 QueueLinear Flood Fill 的帖子,它比上面的要快。

      来源:

      http://www.codeproject.com/Articles/16405/Queue-Linear-Flood-Fill-A-Fast-Flood-Fill-Algorith

      代码:

      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<FloodFillRange>();
          }
      
          // 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;
              }
          }
      }
      

      【讨论】:

        【解决方案3】:

        一种基本方法是floodfill algorythm。 维基百科的文章很好地描述了算法及其变体。

        Here 你可以在 SO 上找到一个实现。但根据您的具体需求,这一项必须进行修改。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-07-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-07-09
          • 1970-01-01
          • 2013-02-12
          相关资源
          最近更新 更多