Android: 动态创建遮罩
Android: dynamically create a mask
在我的 android 应用程序中,我有一辆用户可以点击的汽车和 select 不同的面板。该图像相对复杂(与此处粘贴的图像相反),因此很难将按钮覆盖在正确的位置。此外还有很多不同的图像。
我想尝试的解决方案:
- 使用此处建议的颜色遮罩检测 select 编辑了哪个面板:https://blahti.wordpress.com/2012/06/26/images-with-clickable-areas/
- 根据面板 selected(在我的例子中是蓝色和绿色)生成一个遮罩。
- 根据面罩的不同,在汽车上覆盖一层红色 - 只需一个滤色器就可以了。
(第一个图像代表用于确定哪个面板被点击的颜色,第二个图像代表生成的蒙版,最后一个图像 'result')。
我遇到的唯一问题是:如何动态创建遮罩?我想到了使用 floodfill 类型的方法来创建一个新的 canvas 和 selected 面板的 'mask'。但是,我担心它可能计算量太大。有没有更简单的建议?
[
更新:好的,我已经走了很远。正如预期的那样,创建蒙版的时间太长了(小图像需要 2-4 秒)。但是,后来我发现了 RenderScripts!!我想我仍然可以让它工作。我现在遇到的唯一小问题是:如何传递已按下的颜色?
我当前的代码如下所示:
// create a bitmap for the mask.
ImageView img = (ImageView) findViewById (mask);
img.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(img.getDrawingCache());
// Create a tiny bitmap to store the colours of the panels that are
//'selected'
Bitmap.Config conf = Bitmap.Config.ARGB_8888; // see other conf types
Bitmap myBitmap = Bitmap.createBitmap(pickedPanels.size(), 1, conf);
int [] myInts = new int[pickedPanels.size()];
for (int i = 0; i<pickedPanels.size(); i++){
myInts[i] = pickedPanels.get(i).intValue();
}
myBitmap.setPixels(myInts, 0, myBitmap.getWidth(), 0, 0,
myBitmap.getWidth(),0);
//Run thescript and set the output
final RenderScript rs = RenderScript.create(this);
final Allocation input = Allocation.createFromBitmap(rs, bitmap,
Allocation.MipmapControl.MIPMAP_NONE,Allocation.USAGE_SCRIPT);
final Allocation output = Allocation.createTyped(rs, input.getType());
final ScriptC_singlesource script = new
ScriptC_singlesource(rs);
script.set_image(Allocation.createFromBitmap(rs, myBitmap,
Allocation.MipmapControl.MIPMAP_NONE,Allocation.USAGE_SCRIPT));
script.set_imgWidth(pickedPanels.size());
script.forEach_root(input, output);
output.copyTo(bitmap);
img.setImageBitmap(bitmap);
ImageView destim = (ImageView) findViewById (dest);
destim.setDrawingCacheEnabled(true);
destim.setImageBitmap(bitmap);
这是脚本:
#pragma version(1)
#pragma rs java_package_name(za.co.overtake)
rs_allocation image;
int imgWidth;
uchar4 RS_KERNEL root(uchar4 in, uint32_t x, uint32_t y) {
for(int col = 0; col < imgWidth; col++){
const uchar4 colour = *(const uchar4*)rsGetElementAt(image, col,0);
if (in.r == colour.r && in.g == colour.g && in.b == colour.b){
in.r = 255;
in.g = 0;
in.b = 0;
break;
} else {
in.r = 0;
in.g = 255;
in.b = 0;
rsDebug("HELLLLLP>>", colour);
}
}
return in;
}
但是,当我尝试从 myBitmap(或脚本中的图像)读取像素值时,RGB 始终为 0。
(抱歉命名错误等。我一直在疯狂地想办法解决这个问题)
好的,终于明白了。
在我的 renderscript 代码中,我有:
#pragma version(1)
#pragma rs java_package_name(za.co.overtake)
int*reds;
int*greens;
int*blues;
int imgWidth;
uchar4 RS_KERNEL root(uchar4 in, uint32_t x, uint32_t y) {
bool colourme = false;
for(int col = 0; col < imgWidth; col++){
const int red = reds[col];
const int green = greens[col];
const int blue = blues[col];
if (in.r == red && in.g == green && in.b == blue){
colourme = true;
}
}
if (colourme) {
in.r = 255;
in.g = 0;
in.b = 0;
in.a = 50;
} else {
in.r = 0;
in.g = 0;
in.b = 0;
in.a = 0;
}
return in;
}
然后在Java
public void showDamagedPanels(int dest, int mask) {
int noOfColours = pickedPanels.size();
if (noOfColours > 0) {
ImageView img = (ImageView) findViewById (mask);
img.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(img.getDrawingCache());
img.setDrawingCacheEnabled(false);
int [] reds = new int[noOfColours];
int [] greens = new int[noOfColours];
int [] blues = new int[noOfColours];
for (int i = 0; i< noOfColours; i++){
int colour = pickedPanels.get(i);
reds[i] = (colour >> 16) & 0xFF;
greens[i] = (colour >> 8) & 0xFF;
blues[i] = (colour >> 0) & 0xFF;
}
final RenderScript rs = RenderScript.create(this);
final Allocation input = Allocation.createFromBitmap(rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
final Allocation output = Allocation.createTyped(rs, input.getType());
final ScriptC_singlesource script = new ScriptC_singlesource(rs);
Allocation red = Allocation.createSized(rs, Element.I32(rs), reds.length);
red.copyFrom(reds);
script.bind_reds(red);
Allocation green = Allocation.createSized(rs, Element.I32(rs), greens.length);
green.copyFrom(greens);
script.bind_greens(green);
Allocation blue = Allocation.createSized(rs, Element.I32(rs), blues.length);
blue.copyFrom(blues);
script.bind_blues(blue);
script.set_imgWidth(pickedPanels.size());
script.forEach_root(input, output);
output.copyTo(bitmap);
ImageView destim = (ImageView) findViewById (dest);
destim.setDrawingCacheEnabled(true);
destim.setImageBitmap(bitmap);
} else {
ImageView destim = (ImageView) findViewById (dest);
destim.setImageBitmap(null);
}
}
其中 dest 是叠加图像,图像中的遮罩充当遮罩。所以基本上,当一个面板被点击时——将它的颜色放在 pickedPanels 中。然后调用调用脚本的 showPanels 方法。该脚本检查颜色并将生成的图像设置为红色或透明。
更新:对于尝试使用它但遇到一些问题的任何人:可以在没有渲染脚本代码的情况下执行此操作,但它 运行 有点慢 - 尽管在我的情况下没问题对于小图像。
private Bitmap changeColor(Bitmap src, Set<Integer> pickedPanelsList) {
int fine = getResources().getColor(R.color.colorAccent);
int width = src.getWidth();
int height = src.getHeight();
int[] pixels = new int[width * height];
// get pixel array from source
src.getPixels(pixels, 0, width, 0, 0, width, height);
Bitmap bmOut = Bitmap.createBitmap(width, height, src.getConfig());
int AGood = 100, RGood = Color.red(fine), GGood = Color.green(fine), BGood = Color.blue(fine);
int ABad = 100, RBad = Color.red(Color.RED), GBad = Color.green(Color.RED), BBad = Color.blue(Color.RED);
int pixel;
// iteration through pixels
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
// get current index in 2D-matrix
int index = y * width + x;
pixel = pixels[index];
if(pickedPanelsList.contains(pixel)){
pixels[index] = Color.argb(ABad, RBad, GBad, BBad);
} else if (Color.alpha(pixel) > 0){
pixels[index] = Color.argb(AGood, RGood, GGood, BGood);
}
}
}
bmOut.setPixels(pixels, 0, width, 0, 0, width, height);
return bmOut;
}
在这里,picked panel set是所有应该被染成红色(或被选中)的颜色,位图是mask(如果我没记错的话,我刚才做过)。我还发现对结果进行轻微模糊处理会使图像看起来更好 - 因为它显然会减少锯齿。
在我的 android 应用程序中,我有一辆用户可以点击的汽车和 select 不同的面板。该图像相对复杂(与此处粘贴的图像相反),因此很难将按钮覆盖在正确的位置。此外还有很多不同的图像。 我想尝试的解决方案:
- 使用此处建议的颜色遮罩检测 select 编辑了哪个面板:https://blahti.wordpress.com/2012/06/26/images-with-clickable-areas/
- 根据面板 selected(在我的例子中是蓝色和绿色)生成一个遮罩。
- 根据面罩的不同,在汽车上覆盖一层红色 - 只需一个滤色器就可以了。
(第一个图像代表用于确定哪个面板被点击的颜色,第二个图像代表生成的蒙版,最后一个图像 'result')。
我遇到的唯一问题是:如何动态创建遮罩?我想到了使用 floodfill 类型的方法来创建一个新的 canvas 和 selected 面板的 'mask'。但是,我担心它可能计算量太大。有没有更简单的建议?
[
更新:好的,我已经走了很远。正如预期的那样,创建蒙版的时间太长了(小图像需要 2-4 秒)。但是,后来我发现了 RenderScripts!!我想我仍然可以让它工作。我现在遇到的唯一小问题是:如何传递已按下的颜色?
我当前的代码如下所示:
// create a bitmap for the mask.
ImageView img = (ImageView) findViewById (mask);
img.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(img.getDrawingCache());
// Create a tiny bitmap to store the colours of the panels that are
//'selected'
Bitmap.Config conf = Bitmap.Config.ARGB_8888; // see other conf types
Bitmap myBitmap = Bitmap.createBitmap(pickedPanels.size(), 1, conf);
int [] myInts = new int[pickedPanels.size()];
for (int i = 0; i<pickedPanels.size(); i++){
myInts[i] = pickedPanels.get(i).intValue();
}
myBitmap.setPixels(myInts, 0, myBitmap.getWidth(), 0, 0,
myBitmap.getWidth(),0);
//Run thescript and set the output
final RenderScript rs = RenderScript.create(this);
final Allocation input = Allocation.createFromBitmap(rs, bitmap,
Allocation.MipmapControl.MIPMAP_NONE,Allocation.USAGE_SCRIPT);
final Allocation output = Allocation.createTyped(rs, input.getType());
final ScriptC_singlesource script = new
ScriptC_singlesource(rs);
script.set_image(Allocation.createFromBitmap(rs, myBitmap,
Allocation.MipmapControl.MIPMAP_NONE,Allocation.USAGE_SCRIPT));
script.set_imgWidth(pickedPanels.size());
script.forEach_root(input, output);
output.copyTo(bitmap);
img.setImageBitmap(bitmap);
ImageView destim = (ImageView) findViewById (dest);
destim.setDrawingCacheEnabled(true);
destim.setImageBitmap(bitmap);
这是脚本:
#pragma version(1)
#pragma rs java_package_name(za.co.overtake)
rs_allocation image;
int imgWidth;
uchar4 RS_KERNEL root(uchar4 in, uint32_t x, uint32_t y) {
for(int col = 0; col < imgWidth; col++){
const uchar4 colour = *(const uchar4*)rsGetElementAt(image, col,0);
if (in.r == colour.r && in.g == colour.g && in.b == colour.b){
in.r = 255;
in.g = 0;
in.b = 0;
break;
} else {
in.r = 0;
in.g = 255;
in.b = 0;
rsDebug("HELLLLLP>>", colour);
}
}
return in;
}
但是,当我尝试从 myBitmap(或脚本中的图像)读取像素值时,RGB 始终为 0。
(抱歉命名错误等。我一直在疯狂地想办法解决这个问题)
好的,终于明白了。 在我的 renderscript 代码中,我有:
#pragma version(1)
#pragma rs java_package_name(za.co.overtake)
int*reds;
int*greens;
int*blues;
int imgWidth;
uchar4 RS_KERNEL root(uchar4 in, uint32_t x, uint32_t y) {
bool colourme = false;
for(int col = 0; col < imgWidth; col++){
const int red = reds[col];
const int green = greens[col];
const int blue = blues[col];
if (in.r == red && in.g == green && in.b == blue){
colourme = true;
}
}
if (colourme) {
in.r = 255;
in.g = 0;
in.b = 0;
in.a = 50;
} else {
in.r = 0;
in.g = 0;
in.b = 0;
in.a = 0;
}
return in;
}
然后在Java
public void showDamagedPanels(int dest, int mask) {
int noOfColours = pickedPanels.size();
if (noOfColours > 0) {
ImageView img = (ImageView) findViewById (mask);
img.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(img.getDrawingCache());
img.setDrawingCacheEnabled(false);
int [] reds = new int[noOfColours];
int [] greens = new int[noOfColours];
int [] blues = new int[noOfColours];
for (int i = 0; i< noOfColours; i++){
int colour = pickedPanels.get(i);
reds[i] = (colour >> 16) & 0xFF;
greens[i] = (colour >> 8) & 0xFF;
blues[i] = (colour >> 0) & 0xFF;
}
final RenderScript rs = RenderScript.create(this);
final Allocation input = Allocation.createFromBitmap(rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
final Allocation output = Allocation.createTyped(rs, input.getType());
final ScriptC_singlesource script = new ScriptC_singlesource(rs);
Allocation red = Allocation.createSized(rs, Element.I32(rs), reds.length);
red.copyFrom(reds);
script.bind_reds(red);
Allocation green = Allocation.createSized(rs, Element.I32(rs), greens.length);
green.copyFrom(greens);
script.bind_greens(green);
Allocation blue = Allocation.createSized(rs, Element.I32(rs), blues.length);
blue.copyFrom(blues);
script.bind_blues(blue);
script.set_imgWidth(pickedPanels.size());
script.forEach_root(input, output);
output.copyTo(bitmap);
ImageView destim = (ImageView) findViewById (dest);
destim.setDrawingCacheEnabled(true);
destim.setImageBitmap(bitmap);
} else {
ImageView destim = (ImageView) findViewById (dest);
destim.setImageBitmap(null);
}
}
其中 dest 是叠加图像,图像中的遮罩充当遮罩。所以基本上,当一个面板被点击时——将它的颜色放在 pickedPanels 中。然后调用调用脚本的 showPanels 方法。该脚本检查颜色并将生成的图像设置为红色或透明。
更新:对于尝试使用它但遇到一些问题的任何人:可以在没有渲染脚本代码的情况下执行此操作,但它 运行 有点慢 - 尽管在我的情况下没问题对于小图像。
private Bitmap changeColor(Bitmap src, Set<Integer> pickedPanelsList) {
int fine = getResources().getColor(R.color.colorAccent);
int width = src.getWidth();
int height = src.getHeight();
int[] pixels = new int[width * height];
// get pixel array from source
src.getPixels(pixels, 0, width, 0, 0, width, height);
Bitmap bmOut = Bitmap.createBitmap(width, height, src.getConfig());
int AGood = 100, RGood = Color.red(fine), GGood = Color.green(fine), BGood = Color.blue(fine);
int ABad = 100, RBad = Color.red(Color.RED), GBad = Color.green(Color.RED), BBad = Color.blue(Color.RED);
int pixel;
// iteration through pixels
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
// get current index in 2D-matrix
int index = y * width + x;
pixel = pixels[index];
if(pickedPanelsList.contains(pixel)){
pixels[index] = Color.argb(ABad, RBad, GBad, BBad);
} else if (Color.alpha(pixel) > 0){
pixels[index] = Color.argb(AGood, RGood, GGood, BGood);
}
}
}
bmOut.setPixels(pixels, 0, width, 0, 0, width, height);
return bmOut;
}
在这里,picked panel set是所有应该被染成红色(或被选中)的颜色,位图是mask(如果我没记错的话,我刚才做过)。我还发现对结果进行轻微模糊处理会使图像看起来更好 - 因为它显然会减少锯齿。