I need a way to adjust hue/sat of a Bitmap. So far I found this
public static Bitmap colorize(Bitmap src, float hue, float saturationDelta, float valueDelta) {
Bitmap b = src.copy(Bitmap.Config.ARGB_8888, true);
for (int x = 0; x < b.getWidth(); x++) {
for (int y = 0; y < b.getHeight(); y++) {
int color = b.getPixel(x, y);
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
hsv[0] = hue;
hsv[1] += saturationDelta;
hsv[2] += valueDelta;
int newColor = Color.HSVToColor(Color.alpha(color), hsv);
b.setPixel(x, y, newColor);
}
}
return b;
}
But it takes like 10 seconds to work on a 400x500 bitmap. Are there any faster ways?
Thanks! :)
The link I've posted above should help with hue adjustment. In general, the reason the above code is so slow is because you're calling getPixel() and setPixel() for EVERY PIXEL in the image. You should instead use the getPixels() and setPixels() methods to get all of the pixels as an array, loop over that array and do the modification, then set the modified array back to the bitmap all at once. You'll notice an enormous speed improvement.
Related
I am currently making an app that involves altering the RGB values of pixels in a bitmap and creating a new bitmap after.
My problem is I need help increasing speed of this process. (It can take minutes to process a bitmap with inSampleSize = 2 and forever to process an inSampleSize = 1) Right now, I am using the getPixel and setPixel methods to alter the pixels and believe these two methods are the root of the problem as they are very inefficient. The getPixels method isn't suitable as I am not altering each pixel in order (ex. getting a pixel and changing a radius of 5 pixels around it to the same colour) unless anyone knows of a way to use getPixels (perhaps be able to put the pixels in a 2D array).
This is part of my code:
public static final alteredBitmp(Bitmap bp)
{
//initialize variables
// ..................
Bitmap bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
for (int x = 0; x < width; x++) {
int left = Math.max(0, x - RADIUS);
int right = Math.min(x + RADIUS, width - 1);
for (int y = 0; y < height; ++y) {
int top = Math.max(0, y - RADIUS);
int bottom = Math.min(y + RADIUS, height - 1);
int maxIndex = -1;
for (int j = top; j <= bottom; j++) {
for (int i = left; i <= right; i++) {
pixelColor = bitmap.getPixel(i, j);
//get rgb values
//make changes to those values
}
}
}
}
//set new rgb values
bitmap.setPixel(x, y, Color.rgb(r, g, b));
//return new bitmap
Much thanks in advance!
Consider looking at RenderScript, which is Android's high performance compute framework. As you are iterating over width x height number of pixels and altering each one which in a modern device could be around a million pixels or higher, doing it in a single thread can take minutes. RenderScript can parallelize operations over CPU or the GPU where possible.
http://android-developers.blogspot.com/2012/01/levels-in-renderscript.html
http://developer.android.com/guide/topics/renderscript/index.html
Google IO 2013 session:
https://youtu.be/uzBw6AWCBpU
RenderScript compatibility library: http://android-developers.blogspot.com/2013/09/renderscript-in-android-support-library.html
I'm trying to make an app that will take two pictures you specify via editText, compare the colors of each pixel on both images and create a new picture (bitmap) (that you can save to the sd card) containing the differences between the two original pictures.
I'm having a problem with creating this new bitmap. How can I achieve my goal? I don't really know how to do this, do I create the new bitmap first and then write into it, or do I get the differences first and then draw a bitmap from that? The pictures will be approx. 300x300 px.
this code is just out of my head and untested but it should get you on the right track.
final int w1 = b1.getWidth();
final int w2 = b2.getWidth();
final int h1 = b1.getHeight();
final int h2 = b2.getHeight();
final int w = Math.max(w1, w2);
final int h = Math.max(h2, h2);
Bitmap compare = Bitmap.createBitmap(w, h, Config.ARGB_8888);
int color1, color2, a, r, g, b;
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
if (x < w1 && y < h1) {
color1 = b1.getPixel(x, y);
} else {
color1 = Color.BLACK;
}
if (x < w2 && y < h2) {
color2 = b2.getPixel(x, y);
} else {
color2 = Color.BLACK;
}
a = Math.abs(Color.alpha(color1) - Color.alpha(color2));
r = Math.abs(Color.red(color1) - Color.red(color2));
g = Math.abs(Color.green(color1) - Color.green(color2));
b = Math.abs(Color.blue(color1) - Color.blue(color1));
compare.setPixel(x, y, Color.argb(a, r, g, b));
}
}
b1.recycle();
b2.recycle();
I would create the bitmap first and compute the differences between each pixel, but you're welcome to compute the differences first and then use Bitmap.copyPixels, but I think it's easier to understand the first way. Here is an example:
// Load the two bitmaps
Bitmap input1 = BitmapFactory.decodeFile(/*first input filename*/);
Bitmap input2 = BitmapFactory.decodeFile(/*second input filename*/);
// Create a new bitmap. Note you'll need to handle the case when the two input
// bitmaps are not the same size. For this example I'm assuming both are the
// same size
Bitmap differenceBitmap = Bitmap.createBitmap(input1.getWidth(),
input1.getHeight(), Bitmap.Config.ARGB_8888);
// Iterate through each pixel in the difference bitmap
for(int x = 0; x < /*bitmap width*/; x++)
{
for(int y = 0; y < /*bitmap height*/; y++)
{
int color1 = input1.getPixel(x, y);
int color2 = input2.getPixel(x, y);
int difference = // Compute the difference between pixels here
// Set the color of the pixel in the difference bitmap
differenceBitmap.setPixel(x, y, difference);
}
}
I'm looking to change the hue of my background image (PNG) programmatically. How can this be done on Android?
I tested the accepted answer, unfortunately it returns a wrong result. I found and modified this code from here which works fine:
// hue-range: [0, 360] -> Default = 0
public static Bitmap hue(Bitmap bitmap, float hue) {
Bitmap newBitmap = bitmap.copy(bitmap.getConfig(), true);
final int width = newBitmap.getWidth();
final int height = newBitmap.getHeight();
float [] hsv = new float[3];
for(int y = 0; y < height; y++){
for(int x = 0; x < width; x++){
int pixel = newBitmap.getPixel(x,y);
Color.colorToHSV(pixel,hsv);
hsv[0] = hue;
newBitmap.setPixel(x,y,Color.HSVToColor(Color.alpha(pixel),hsv));
}
}
bitmap.recycle();
bitmap = null;
return newBitmap;
}
The linked post has some good ideas, but the matrix math used for ColorFilter may be (a) complex overkill, and (b) introduce perceptible shifts in the resulting colors.
Modifying the solution given by janin here - https://stackoverflow.com/a/6222023/1303595 - I've based this version on Photoshop's 'Color' blend mode. It seems to avoid the image-darkening caused by PorterDuff.Mode.Multiply, and works very well for color-tinting desaturated/artificial-Black & White images without losing much contrast.
/*
* Going for perceptual intent, rather than strict hue-only change.
* This variant based on Photoshop's 'Color' blending mode should look
* better for tinting greyscale images and applying an all-over color
* without tweaking the contrast (much)
* Final color = Target.Hue, Target.Saturation, Source.Luma
* Drawback is that the back-and-forth color conversion introduces some
* error each time.
*/
public void changeHue (Bitmap bitmap, int hue, int width, int height) {
if (bitmap == null) { return; }
if ((hue < 0) || (hue > 360)) { return; }
int size = width * height;
int[] all_pixels = new int [size];
int top = 0;
int left = 0;
int offset = 0;
int stride = width;
bitmap.getPixels (all_pixels, offset, stride, top, left, width, height);
int pixel = 0;
int alpha = 0;
float[] hsv = new float[3];
for (int i=0; i < size; i++) {
pixel = all_pixels [i];
alpha = Color.alpha (pixel);
Color.colorToHSV (pixel, hsv);
// You could specify target color including Saturation for
// more precise results
hsv [0] = hue;
hsv [1] = 1.0f;
all_pixels [i] = Color.HSVToColor (alpha, hsv);
}
bitmap.setPixels (all_pixels, offset, stride, top, left, width, height);
}
If you wrap your Bitmap in an ImageView there is a very simple way:
ImageView circle = new ImageView(this);
circle.setImageBitmap(yourBitmap);
circle.setColorFilter(Color.RED);
My guess is this will be faster than modifying each pixel individually.
I tried to adjust saturation using below code, but it is not working properly. I have used saturation value range between 0 to 1.
//deg value range is from 0 to 1.
public static Bitmap adjustSaturation(Bitmap o, float deg)
{
Bitmap srca = o;
Bitmap bitmap = srca.copy(Bitmap.Config.ARGB_8888, true);
for(int x = 0;x < bitmap.getWidth();x++)
for(int y = 0;y < bitmap.getHeight();y++){
int newPixel = saturationChange(bitmap.getPixel(x,y),deg);
bitmap.setPixel(x, y, newPixel);
}
return bitmap;
}
private static int saturationChange(int startpixel,float deg){
float[] hsv = new float[3]; //array to store HSV values
Color.colorToHSV(startpixel,hsv); //get original HSV values of pixel
hsv[1]=hsv[1]+deg; //add the shift to the HUE of HSV array
hsv[1]=hsv[1]%1; //confines hue to values:[0,360]
return Color.HSVToColor(Color.alpha(startpixel),hsv);
}
Please suggest a solution to this problem.
I have a bitmap whose pixels contain only two argb values: pure black and pure transparent. I then scale the bitmap up in Android, now the bitmap has many argb values: pure black and pure transparent and black with various levels of transparency (i.e half transparent black); this is due to the interpolation done automatically by android. I would like the bitmaps pixels to contain only the original two argb values.
Currently I accomplish this with the following process:
my_bitmap = Bitmap.createScaledBitmap(BitmapFactory
.decodeResource(context.getResources(),
R.drawable.my_resource),
new_width, new height, false);
for (int i = 0; i < my_bitmap.getWidth(); i++) {
for (int j = 0; j < my_bitmap.getWidth(); j++) {
if (my_bitmap.getPixel(i, j) != Color.TRANSPARENT) {
my_bitmap.setPixel(i, j, Color.BLACK);
}
}
}
This is achingly slow on a cheaper phone for even a small bitmap, does anyone know how to either A) do this much faster or B) scale a bitmap up with no new argb values appearing?
I think the answer here is an algorithmic one.
Bitmap operations are very expensive... perhaps you can cache the bitmap somewhere and only draw it when the interpolation is specifically requested?
My other idea would be to group some amount of pixels together and have a flag "hasChanged" or something like that, and set it to true when something is changed so the system knows it has to redraw that pixel group. This way you don't redraw things more often than necessary.
Hope this helps!
Do the scale in code since the built-in algorithm isn't what you want. You'll avoid the interpolation that you don't want and you won't have to undo it. (Excuse any coding errors -- I wrote this without access to an IDE or compiler.)
my_bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.my_resource);
int[] src = new int[my_bitmap.getWidth() * my_bitmap.getHeight()];
my_bitmap.getPixels(src, 0, my_bitmap.getWidth(), 0, 0, my_bitmap.getWidth(), my_bitmap.getHeight());
int[] dst = new int[new_width * new_height];
float scaleX = my_bitmap.getWidth() / new_width;
float scaleY = my_bitmap.getHeight() / new_height;
for (int y = 0; y < new_height; y++) {
for (int x = 0; x < new_width; x++) {
int srcY = (int) (y * scaleY);
int srcX = (int) (x * scaleX);
dst[y*new_height + x] = src[srcY*my_bitmap.getHeight() + srcX];
}
}
Bitmap newBitmap = Bitmap.createBitmap(dst, 0, new_width, new_width, new_height, Bitmap.Config.ARGB_8888);