change color of bitmap - java

I'm trying to create a function that gets a bitmap and destiny color and returns the colored bitmap (without using paint). I found few ways of doing it but nothing works like I want it to.
The closest solution I was able to find is:
public static Bitmap changeImageColor(Bitmap srcBmp, int dstColor) {
int width = srcBmp.getWidth();
int height = srcBmp.getHeight();
float srcHSV[] = new float[3];
float dstHSV[] = new float[3];
Bitmap dstBitmap = Bitmap.createBitmap(width, height, Config.RGB_565);
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
Color.colorToHSV(srcBmp.getPixel(col, row), srcHSV);
Color.colorToHSV(dstColor, dstHSV);
// If it area to be painted set only value of original image
dstHSV[2] = srcHSV[2]; // value
int color2=Color.HSVToColor(dstHSV);;
dstBitmap.setPixel(col, row, Color.HSVToColor(dstHSV));
}
}
return dstBitmap;
}
but It doesn't work very well on transparent images as can be seen here (before and after):
Anyone has any other solutions (again without using paint at all)?

You just need to extract alpha and re-apply it after transformation. And use ARGB_8888;
Edited your code to include alpha:
public Bitmap colorize(Bitmap srcBmp, int dstColor) {
int width = srcBmp.getWidth();
int height = srcBmp.getHeight();
float srcHSV[] = new float[3];
float dstHSV[] = new float[3];
Bitmap dstBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
int pixel = srcBmp.getPixel(col, row);
int alpha = Color.alpha(pixel);
Color.colorToHSV(pixel, srcHSV);
Color.colorToHSV(dstColor, dstHSV);
// If it area to be painted set only value of original image
dstHSV[2] = srcHSV[2]; // value
dstBitmap.setPixel(col, row, Color.HSVToColor(alpha, dstHSV));
}
}
return dstBitmap;
}

here is a sample code for change the color for a bitmap:
private BitmapDrawable getColoredBitmap(int color, Context context,
int drawableId) {
Bitmap source = BitmapFactory.decodeResource(context.getResources(),
drawableId);
final Bitmap bitmap = Bitmap.createBitmap(source.getWidth(),
source.getHeight(), Bitmap.Config.ARGB_8888);
for (int i = 0; i < source.getWidth(); i++) {
for (int j = 0; j < source.getHeight(); j++) {
int pixel = source.getPixel(i, j);
// if (pixel == Color.TRANSPARENT) {
//
// } else
if (pixel == Color.WHITE) {
pixel = Color.argb(Color.alpha(pixel),
Color.red(Color.WHITE), Color.green(Color.WHITE),
Color.blue(Color.WHITE));
} else {
pixel = Color.argb(Color.alpha(pixel), Color.red(color),
Color.green(color), Color.blue(color));
}
bitmap.setPixel(i, j, pixel);
}
}
return new BitmapDrawable(context.getResources(), bitmap);
}

You do this:
int alpha=srcBmp.getPixel(col, row);
dstBitmap.setPixel(col, row, Color.HSVToColor(dstHSV));
in which you calculate an alpha (probably incorrectly from the looks of that code) and then don't use it. You are probably going to have to create a Color with HSVToColor, then set the alpha of that color, then use it in setPixel. And you are probably going to have to get the alpha in a similar way because I find it hard to believe a getPixel function only returns the alpha :p

Related

Comparing 2 images with transparency area in Java

For an automation tool I'm working on I need to compare 2 images.
My code works perfectly when I have "normal" images, but it fails when one of the images has a transparent area.
I'm reducing the color between the images for each pixel and creating a negative image that shows the difference. For images with transparent area the negative image is whole white, I can't see any shape or other info.
How to ignore the transparent area (shown in the images as gray color)?
My code:
private static BufferedImage createDiffImage(BufferedImage img1, BufferedImage img2) {
BufferedImage result = new BufferedImage(img1.getWidth(), img1.getHeight(), img1.getType());
int color;
for(int x = 0; x < img1.getWidth(); x++)
for(int y = 0; y < img1.getHeight(); y++) {
color = Math.abs(img2.getRGB(x, y) - img1.getRGB(x, y));
result.setRGB(x, y, color);
}
return result;
}
I found the solution:
private static BufferedImage createDiffImage(BufferedImage img1, BufferedImage img2) {
BufferedImage result = new BufferedImage(img1.getWidth(), img1.getHeight(), img1.getType());
for(int x = 0; x < img1.getWidth(); x++)
for(int y = 0; y < img1.getHeight(); y++) {
Color c1 = new Color(img1.getRGB(x,y));
Color c2 = new Color(img2.getRGB(x,y));
int alpha = 255;
int red = Math.abs(c1.getRed() - c2.getRed());
int green = Math.abs(c1.getGreen() - c2.getGreen());
int blue = Math.abs(c1.getBlue() - c2.getBlue());
Color negativeColor = new Color(red,green,blue, alpha);
result.setRGB(x, y, negativeColor.getRGB());
}
return result;
}

How to iterate through pixels in a more efficient way?

I'm getting an Image and convert it into a Bitmap. To check if the color of a pixel is between two values, I iterate through every single pixel of the given Bitmap. I create a new Bitmap, change the pixel if it is between these values and set.Pixel() on the new Bitmap, or leave the Pixel as it is and set it also on the new Bitmap. I reduced the Bitmap.Config from ARGB-8888 to RGB_565 but it is still unbeleavable slow and shows in debug "The application may be doing too much work on its main thread."
Is there a better way to do that and make the app faster?
width = bitmap.getWidth();
heigth = bitmap.getHeight();
Bitmap newBitmap = Bitmap.createBitmap(width, heigth, Bitmap.Config.RGB_565);
for(int x = 0; x < width; x++){
for(int y = 0; y < heigth; y++){
int pixel = bitmap.getPixel(x,y);
float[] pixelHue = getHsv(pixel);
if(pixelHue[0] > 170 && pixelHue[0] < 250 ){
//Change the Pixel
pixelHue[0] = pixelHue[0] - 17;
int newColor = setRgb(pixelHue);
newBitmap.setPixel(x, y, newColor);
}
else{
//leave the Pixel as it is
newBitmap.setPixel(x, y, pixel);
}
}
}
return newBitmap;
}
private float[] getHsv (int pixel){
int a = Color.alpha(pixel);
int r = Color.red(pixel);
int g = Color.green(pixel);
int b = Color.blue(pixel);
float[] hsv = new float[3];
Color.RGBToHSV(r, g, b, hsv);
return hsv;
}
private int setRgb (float[] hsv){
int rgb;
rgb = Color.HSVToColor(hsv);
return rgb;
}
but I need to check the hue value of a pixels color to indicate, if the color is in my range
Correct, but you don't need to convert the pixel to an HSV value every time. You can do the conversion once and then store the result.
So the basic structure of your code might be something like:
HashMap<Integer, Integer> pixels = new HashMap<Integer, Integer>(); // instance variable of your class
for(…)
{
for (…)
{
int pixel = bitmap.getPixel(x,y)
int pixelHue = getPixelHue( pixel );
newBitmap.setPixel(x, y, pixelHue);
}
}
…
private int getPixelHue(int pixel)
{
Integer pixelHue = pixels.get(pixel);
if (pixelHue == null) // you haven't attempted to convert this pixel yet
{
float[] hsv = getHsv(pixel);
if(hsv[0] > 170 && hsv[0] < 250 )
{
// Change the hue value of the hsv color
hsv[0] = hsv[0] - 17;
pixelHue = setRgb(hsv); // conversion done
}
else
{
pixelHue = pixel; // no conversion needed
}
pixels.put(pixel, pixelHue); // store result so we don't need to reconvert
}
return pixelHue;
}
So the HashMap will originally be empty. As you process a pixel of a unique color you will do the HSV conversion and hue conversion, if necessary, and store the result in the HashMap. The next time you process this pixel color you don't need to repeat the conversion process.

how to edit a bitmap of the pixels in an image

I have written a steganography algorithm, but it takes a long time to complete. This is because I create a new instance of bitmap, BitmapStegan, and I take each pixel from my old bitmap, bitmap. Whether I modify it or not, I have to set it in the new bitmap object. Therefore, I end up looping through all of the pixels, even though I only need to edit a few of them.
How can I address that problem?
Bitmap BitmapStegan = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
for(int i=0; i<bitmap.getWidth(); i++){
for(int j=0; j<bitmap.getHeight(); j++){
int pixel=bitmap.getPixel(i, j);
int red= Color.red(pixel);
int green=Color.green(pixel);
int blue=Color.blue(pixel);
if (NumberBitsInMessage>0) {
/*
I put here my bit to red and greed and blue with LSB method
*/
}
BitmapStegan.setPixel(i, j, Color.argb(Color.alpha(pixel), red, green, blue));
}
}
imageView.setImageBitmap(BitmapStegan);
First things first, do you really need a copy of your original image? If yes, because you want to compare statistical differences between the original and the stego image, you want to create a copy of your bitmap. This way, you create all the pixels in one go, which is faster. If you don't need a copy, just apply your changes directly to the original image object. Either way, you need to modify only one image, which from now on I will call image.
Now, you have two choices about how to iterate through only enough pixels for embedding. Either use loops for the rows and columns of your image and break out of them after you have embedded the whole secret, or create a counter for NumberBitsInMessage and explicitly change the pixel coordinates as you embed your bits.
1. Breaking out of the loops
embedding:
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
if (NumberBitsInMessage == 0) {
break embedding;
}
int pixel = image.getPixel(i, j);
int red = Color.red(pixel);
int green = Color.green(pixel);
int blue = Color.blue(pixel);
/*
modify pixel logic here
*/
image.setPixel(i, j, Color.argb(Color.alpha(pixel), red, green, blue));
}
}
2. Embedding bits counter
int width = 0;
int height = 0;
int maxHeight = image.getHeight();
for (int embeddedBits = 0; embeddedBits < NumberBitsInMessage; ) {
int pixel = image.getPixel(width, height);
int red = Color.red(pixel);
int green = Color.green(pixel);
int blue = Color.blue(pixel);
/*
modify pixel logic here
don't forget to increase `embeddedBits` for each colour you modify
*/
image.setPixel(width, height, Color.argb(Color.alpha(pixel), red, green, blue));
height++;
if (height == maxHeight) {
width++;
height = 0;
}
}

Android: dynamically create a mask

In my android app, I have a car from which the user can click and select different panels. The image is relatively complicated (as opposed to the one pasted here) so its difficult to overlay the buttons in the correct spots. In addition there are a lot of different images.
The solution I would like to try:
Detect which panel was selected by using a colour mask as suggested here: https://blahti.wordpress.com/2012/06/26/images-with-clickable-areas/
Depending on the panels selected (in my example the blue and green) generate a mask.
Depending on the mask, have a red overlay on the car - just a colour filter will be fine.
(First image represents the colours used to determine which panel was clicked, second image represents the mask generated and the last image the 'result').
The only problem I'm having is: How do I dynamically create the mask? I thought of using a floodfill type method to create a new canvas with the 'mask' of the selected panels. But, I worry that it might be too computationally heavy. Any simpler suggestions?
[
UPDATE: Ok so I've come pretty far. As expected, the creation of the mask too way too long (2-4 seconds for a small image). But, then I discovered RenderScripts!! I think I can still get this to work. The only little snag that I have now is: How do I pass in the colours that have been pressed?
My current code looks like this:
// 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);
and this is the script:
#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;
}
But, when I try and read the pixel values from myBitmap (or image in the script), RGB is always 0.
(Sorry for the bad naming, etc. I've been going crazy trying to figure this out)
Ok, finally got this figured out.
In my renderscript code I have:
#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;
}
Then 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);
}
}
where dest is the overlay image and mask in the image acting as the mask. So basically, when a panel is clicked - place its colour in pickedPanels. Then call the showPanels method, which calls the script. The script checks the colours and sets the resulting image red or clear.
Update: For anyone trying to use this, but having some issues: It is possible to do this without the renderscript code, but it does run a bit slower - though it has been ok in my case for small images.
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;
}
Here, the picked panel set is all the colours that should be coloured red (or chosen) and the bitmap is the mask (if I remember correctly, I did this a while ago). I've also found that doing a slight blur on the result makes the image look nicer - since it will obviously be less jagged.

bufferedimage image.getRGB and image.setRGB not refer to same pixel

i have problem with setRGB() method. After get int color=getRGB(x,y) then setRGB(x,y,color) the image had changed.
File file=new File(fileName);
image = ImageIO.read(file);
int width=image.getWidth();
int high=image.getHeight();
for (int xPixel = 0; xPixel < width; xPixel++)
{
for (int yPixel=0; yPixel<high; yPixel++)
{
int color = image.getRGB(xPixel, xPixel);
image.setRGB(xPixel, yPixel, color);
}
}
Then i write the image to a bmp file. the new image is not same with old image.
What is problem?
You call the getRGB- Function with xPixel and xPixel.
and the set function with xPixel and yPixel as arguments.
I think your code must be
int color = image.getRGB(xPixel, yPixel);
image.setRGB(xPixel, yPixel, color);

Categories