我有一些静态图像如下:
现在,我想要的是,当我触摸脸部或手部时,选择的颜色应该填充在皮肤部分上。
见下面的结果图:
那么如何获得上面的结果? 重做和撤消功能也应该存在。
我尝试使用FloodFill颜色,但这样做我只能对特定部分进行着色。因为FloodFill只填充颜色,直到相同的pixwl颜色出现。如果触摸位置像素颜色发生变化,它将不会填充颜色。
所以Usinf FloodFill我得到了如下图像的结果,如果我按下手,那么只有手部分将填充颜色,而不是它我想要填充另一只手和脸的颜色。
所以请在这种情况下帮助我。
EDITED
经过一些回复,我得到了像this one这样的解决方案。
但仍有内存问题。它消耗大量内存来绘制颜色。所以,任何人都可以帮助我吗?
答案 0 :(得分:14)
您可以使用实际方式对完整图像进行着色,当您使用颜色填充某个区域时,它将替换要填充的颜色指定的所有区域。
Layman的条款:
我希望这会有所帮助。
如果你想要一个例子,请随意发表评论然后我可以用它更新答案,但我想你可以从这里得到它。
编辑:
基本上从这样的简单图像开始。我们可以将其称为大纲
然后作为开发人员,你必须做一些工作。在这里,您为 OUTLINE 设置颜色代码。结果我们称之为 MASK 。为此,我们使用您想要的相同颜色对区域进行颜色编码。这可以在油漆或其他任何事情上完成。我使用Photoshop很酷lol: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 并相应地绘制大纲。但是,你可以真正有趣和互动。
<强> RESULT 强>
当我碰到男人的头发时,它不仅染了头发,而且还用同样的颜色涂上了他的衬衫和手。将其与 MASK 进行比较,以便更好地了解所发生的事情。
这只是一个基本想法。我创建了多个位图,但实际上并不需要它。我曾将它用于测试目的并占用不必要的内存。而且您不需要在每次点击等时重新创建遮罩。
我希望这会对你有所帮助:D
祝你好运答案 1 :(得分:3)
使用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().
检查以上链接以获取floodfill算法android。这应该可以帮助您实现您想要的目标。 Android FingerPaint Undo/Redo implementation。这应该可以帮助您根据需要修改撤消和重做。
编辑:
stackoverflow上的一篇文章让我了解了一种使用泛洪填充算法的有效方法,没有延迟和OOM。
从SO帖子中挑选
使用上述填充算法填充小的封闭区域可以正常工作。但是对于大区域,算法运行缓慢并且消耗大量内存。最近我遇到了一个使用QueueLinear Flood Fill的帖子,它比上面的方式更快。</ p>
来源:
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;
}
}
}
答案 2 :(得分:1)
一种基本方式可能是floodfill algorythm。 维基百科的文章很好地描述了algorythm及其变体。
Here您可以在SO上找到实施。但根据您的具体需求,必须对其进行修改。