0

I need to change a particular color of a PNG stored in the SDCard. I can't use tintcolor over a bitmap or over any other object because this will tint all the image not a particular pixel color.

Why do I need to do this?
I'm trying to develop an avatar application and I want to be able to change the hair of the avatar with any color I select from a palette. This Hair have two colors, one for the border and the other for the rest of the hair. I just want to change the air color and keep the border one.

This a simple case but there could be more than one color in the image.

I was looking up for a solution. This is the only thing I found (meaby there could be more but I am not lucky):

Android Bitmap: Convert transparent pixels to a color

And this is what it is exposed there:

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

I want to know if there is a better way to do this. Something like a:

bipmapImage.changeColor(originalColor, newColor);

I don't know if using a loop to check pixel a pixel is a good performance. Imagine a 1080 x 1080 image.

Thanks in advance.

Community
  • 1
  • 1
Deneb Chorny
  • 391
  • 3
  • 19
  • Are you using transparency to indicate which pixels are hair? – samgak Aug 30 '16 at 01:49
  • You'd better use OpenCV Matrix APIs especially for performance (and compactness). – Suhyeon Lee Aug 30 '16 at 01:56
  • @samgak I'm not using transparency to it because I can change multiple colors in the same image. – Deneb Chorny Aug 30 '16 at 02:00
  • @SuhyeonLee, do you have any example of it?, please – Deneb Chorny Aug 30 '16 at 02:01
  • Before you decide it's bad performance to use the example, you should implement it like that and measure the time taken. – samgak Aug 30 '16 at 02:02
  • Thanks @samgak. I'm not saying it is a bad performance. Actually, I will implement this. But, as I don't know if there is another way to do it, maybe a better one, and don't know if this is a good way then I made the question. – Deneb Chorny Aug 30 '16 at 02:17
  • You can get better performance if you use copyPixelsFromBuffer() then iterate over the buffer and change the pixels, the call copyPixelsToBuffer(). However then you have to worry about checking the bitmap format, and also possibly getting out of memory errors when you allocate the buffer. Likewise, if you use a library for faster performance, now your APK size will be larger. And in both cases, your code will be more complex. There's almost always a tradeoff. BTW I'm not criticizing your question, just giving some advice. – samgak Aug 30 '16 at 02:24
  • @samgak let me read about copyPixelsToBuffer. It is new for me and I need to read a bit to understand it. As I said, I am working on an avatar app then imagine that I need to open an created avatar, this means I have to check every item on the avatar, change the color specified by the user when it was saved. I don't know how this function works but if get the composed image from the buffer and it has the same color over different items that could be a problem. I don't know if I understandable. – Deneb Chorny Aug 30 '16 at 02:51
  • Again, let me read a bit to be clear about this answer that looks the perfect idea and as you said it is a better performance than get and set pixels. One thing more, You said: " if you use a library for faster performance", do this mean you know any library or utils for this? – Deneb Chorny Aug 30 '16 at 02:52
  • If your avatars can be broken down into components, you could use masking/compsiting to color the relevant parts of each each piece before combining them into a final image. Here's a S/O question that describes the compositing stuff: http://stackoverflow.com/questions/8280027/what-does-porterduff-mode-mean-in-android-graphics-what-does-it-do . – scottt Aug 30 '16 at 06:35

2 Answers2

2

You can get better performance compared to getPixel() and setPixel() by calling copyPixelsToBuffer() to copy the pixels to a buffer, then modifying the pixel values in the buffer, and finally calling copyPixelsFromBuffer() to copy from the buffer back to the bitmap:

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

However, there are extra complications: the bitmap must use the ARGB_8888 pixel format (if it's different you will need to write extra code to handle that) and you need to catch out of memory exceptions that can occur when allocating the IntBuffer. You should first profile the code using setPixels() and see if the speed is unacceptable.

This is also likely not the fastest solution, which will probably be to use a native function call in a library. But it will still be faster than setPixel() and you don't need to add a library to your project.

samgak
  • 23,944
  • 4
  • 60
  • 82
  • make your example, but get the image of a different color, instead of the expected, do you think what is the reason? – Сергей Mar 22 '17 at 20:15
  • @Сергей the most likely reason is the rgba order in your int value is different to what the Android Bitmap class expects. The color should be formed like this int color = (a << 24) | (r << 16) | (g << 8) | b; Where a r g b are in the range 0-255. See here for more info: https://developer.android.com/reference/android/graphics/Color.html – samgak Mar 22 '17 at 20:26
1

You'd better use OpenCV Matrix APIs especially for performance (and compactness).

Check the OpenCV Android tutorial here.

Assume you've installed the OpenCV features, you can change the color of some particular areas in your image.

(You should understand the Mat feature first.)

Actually I haven't used OpenCV with Android.
Here's some example codes to change your hair color to red in 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);


The Android version code will be not that different I think.

Suhyeon Lee
  • 569
  • 4
  • 18
  • I accept samgak's answer (for now) because it is a direct way to do what I want it. I am evaluating yours, maybe my idea is not as exactly as you shown, but your idea could be a good one how to start and this could be the best way talking about performance. Thanks for all. – Deneb Chorny Sep 01 '16 at 16:03