在 Android 上逐像素更改 png 颜色的最佳方法是什么?
What is the best way to change png color pixel by pixel on Android?
我需要更改存储在 SDCard 中的 PNG 的特定颜色。我不能在位图或任何其他对象上使用 tintcolor,因为这会给所有图像着色,而不是特定像素颜色。
为什么我需要这样做?
我正在尝试开发一个头像应用程序,我希望能够使用调色板中 select 的任何颜色更改头像的头发。这头发有两种颜色,一种用于边框,另一种用于头发的其余部分。我只是想改变空气颜色并保持边框不变。
这是一个简单的例子,但图像中可能有不止一种颜色。
我正在寻找解决方案。这是我唯一找到的东西(可能还有更多,但我不走运):
Android Bitmap: Convert transparent pixels to a color
这就是它在那里暴露的内容:
Bitmap b = ...;
for(int x = 0; x<b.getWidth(); x++){
for(int y = 0; y<b.getHeight(); y++){
if(b.getPixel(x, y) == Color.TRANSPARENT){
b.setPixel(x, y, Color.WHITE);
}
}
}
我想知道是否有更好的方法来做到这一点。像这样的东西:
bipmapImage.changeColor(originalColor, newColor);
我不知道使用循环逐个像素地检查是否是一个好的性能。想象一个 1080 x 1080 的图像。
提前致谢。
您最好使用 OpenCV
矩阵 API,尤其是为了提高性能(和紧凑性)。
查看 OpenCV
Android 教程 here。
假设您已经安装了 OpenCV
功能,您可以更改图像中某些特定区域的颜色。
(您应该先了解 Mat
功能。)
实际上我还没有在 Android 中使用 OpenCV。
下面是一些在 C++ 中将头发颜色更改为红色的示例代码:
// 1. Loading
Mat src = imread("yourImagePath/yourOriginalImage.jpg"); // This code will automatically loads image to Mat with 3-channel(BGR) format
Mat mask = imread("yourImagePath/yourHairMaskImage.png", CV_GRAYSCALE); // This code will automatically loads mask image to Mat with 1-channel(grayscale) format
// 2. Splitting RGB channel into 3 separate channels
vector<Mat> channels; // C++ version of ArrayList<Mat>
split(src, channels); // Automatically splits channels and adds them to channels. The size of channels = 3
// 3-1. Adjusting red color
Mat adjustRed = channels[0]*1.5;
// 3-2. Copy the adjusted pixels into org Mat with mask
channels[2] = adjustRed & mask + channels[0] & ~mask;
// 4. Merging channels
Mat dst;
merge(channels, dst); // dst is re-created with 3-channel(BGR).
// Note that OpenCV applies BGR by default if your array size is 3,
// even if actual order is different. In this case this makes sense.
// 5. Saving
imwrite("yourImagePath/yourDstImage.jpg", dst);
我认为 Android 版本代码不会有太大不同。
通过调用 copyPixelsToBuffer()
将像素复制到缓冲区,然后修改缓冲区中的像素值,最后调用 getPixel()
和 setPixel()
可以获得更好的性能copyPixelsFromBuffer()
从缓冲区复制回位图:
boolean changeColor(Bitmap bitmap, int originalColor, int newColor)
{
// bitmap must be mutable and 32 bits per pixel:
if((bitmap.getConfig() != Bitmap.Config.ARGB_8888) || !bitmap.isMutable())
return false;
int pixelCount = bitmap.getWidth() * bitmap.getHeight();
IntBuffer buffer = IntBuffer.allocate(pixelCount);
bitmap.copyPixelsToBuffer(buffer);
int[] array = buffer.array();
for(int i = 0; i < pixelCount; i++)
{
if(array[i] == originalColor)
array[i] = newColor;
}
bitmap.copyPixelsFromBuffer(buffer);
return true;
}
然而,还有额外的复杂性:位图必须使用 ARGB_8888
像素格式(如果不同,您将需要编写额外的代码来处理)并且您需要捕获内存不足的异常,这些异常可以分配 IntBuffer
时发生。您应该首先使用 setPixels()
分析代码,然后查看速度是否不可接受。
这也可能不是最快的解决方案,可能是在库中使用本机函数调用。但它仍然比 setPixel()
快,而且您不需要向您的项目添加库。
我需要更改存储在 SDCard 中的 PNG 的特定颜色。我不能在位图或任何其他对象上使用 tintcolor,因为这会给所有图像着色,而不是特定像素颜色。
为什么我需要这样做?
我正在尝试开发一个头像应用程序,我希望能够使用调色板中 select 的任何颜色更改头像的头发。这头发有两种颜色,一种用于边框,另一种用于头发的其余部分。我只是想改变空气颜色并保持边框不变。
这是一个简单的例子,但图像中可能有不止一种颜色。
我正在寻找解决方案。这是我唯一找到的东西(可能还有更多,但我不走运):
Android Bitmap: Convert transparent pixels to a color
这就是它在那里暴露的内容:
Bitmap b = ...;
for(int x = 0; x<b.getWidth(); x++){
for(int y = 0; y<b.getHeight(); y++){
if(b.getPixel(x, y) == Color.TRANSPARENT){
b.setPixel(x, y, Color.WHITE);
}
}
}
我想知道是否有更好的方法来做到这一点。像这样的东西:
bipmapImage.changeColor(originalColor, newColor);
我不知道使用循环逐个像素地检查是否是一个好的性能。想象一个 1080 x 1080 的图像。
提前致谢。
您最好使用 OpenCV
矩阵 API,尤其是为了提高性能(和紧凑性)。
查看 OpenCV
Android 教程 here。
假设您已经安装了 OpenCV
功能,您可以更改图像中某些特定区域的颜色。
(您应该先了解 Mat
功能。)
实际上我还没有在 Android 中使用 OpenCV。
下面是一些在 C++ 中将头发颜色更改为红色的示例代码:
// 1. Loading
Mat src = imread("yourImagePath/yourOriginalImage.jpg"); // This code will automatically loads image to Mat with 3-channel(BGR) format
Mat mask = imread("yourImagePath/yourHairMaskImage.png", CV_GRAYSCALE); // This code will automatically loads mask image to Mat with 1-channel(grayscale) format
// 2. Splitting RGB channel into 3 separate channels
vector<Mat> channels; // C++ version of ArrayList<Mat>
split(src, channels); // Automatically splits channels and adds them to channels. The size of channels = 3
// 3-1. Adjusting red color
Mat adjustRed = channels[0]*1.5;
// 3-2. Copy the adjusted pixels into org Mat with mask
channels[2] = adjustRed & mask + channels[0] & ~mask;
// 4. Merging channels
Mat dst;
merge(channels, dst); // dst is re-created with 3-channel(BGR).
// Note that OpenCV applies BGR by default if your array size is 3,
// even if actual order is different. In this case this makes sense.
// 5. Saving
imwrite("yourImagePath/yourDstImage.jpg", dst);
我认为 Android 版本代码不会有太大不同。
通过调用 copyPixelsToBuffer()
将像素复制到缓冲区,然后修改缓冲区中的像素值,最后调用 getPixel()
和 setPixel()
可以获得更好的性能copyPixelsFromBuffer()
从缓冲区复制回位图:
boolean changeColor(Bitmap bitmap, int originalColor, int newColor)
{
// bitmap must be mutable and 32 bits per pixel:
if((bitmap.getConfig() != Bitmap.Config.ARGB_8888) || !bitmap.isMutable())
return false;
int pixelCount = bitmap.getWidth() * bitmap.getHeight();
IntBuffer buffer = IntBuffer.allocate(pixelCount);
bitmap.copyPixelsToBuffer(buffer);
int[] array = buffer.array();
for(int i = 0; i < pixelCount; i++)
{
if(array[i] == originalColor)
array[i] = newColor;
}
bitmap.copyPixelsFromBuffer(buffer);
return true;
}
然而,还有额外的复杂性:位图必须使用 ARGB_8888
像素格式(如果不同,您将需要编写额外的代码来处理)并且您需要捕获内存不足的异常,这些异常可以分配 IntBuffer
时发生。您应该首先使用 setPixels()
分析代码,然后查看速度是否不可接受。
这也可能不是最快的解决方案,可能是在库中使用本机函数调用。但它仍然比 setPixel()
快,而且您不需要向您的项目添加库。