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
Related
I'm trying to clear unwanted area from picture, in the most efficient way,
My code for now:
Bitmap picture = ...;
ByteBuffer mask = ...;
int height = picture.getHeight();
int width = picture.getWidth();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float currFloat = mask.getFloat();
if (currFloat > 0.7) {
req.setPixel(x, y, Color.TRANSPARENT);
}
}
}
My mask (ByteBuffer of (for example) 8*8):
My picture (Bitmap of 8*8):
Excepted result:
Thankfully, it works,
but unfortunately it takes too long since there are a lot of images to process..
There is any way to make it work faster? (something like matrix multiplication?)
I can transform the bitmap into buffer using
picture.copyPixelsToBuffer(buff)
Thanks in advance.
I was working on a "draw with mask" app. When the user drag on the screen , it cleans part of the mask.
I implemented it through cavans and with setXfermode Clear
// Specify that painting will be with fat strokes:
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeWidth(canvas.getWidth() / 15);
// Specify that painting will clear the pixels instead of paining new ones:
drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
cv.drawPath(path, drawPaint);
The problem is , how can I get the percentage of space cleaned?, it doesn't necessary to be accurate, just roughly detect when more than half of screen size is clean. Thanks for helping
What you need to do is to convert your canvas in to bitmap and count the number of black pixels in it. Using simple math you can divide the number of black pixels to the number of pixels in the canvas which will give you the percentage of black pixels.
sample taken from this post:
public float percentTransparent(Bitmap bm) { //pass the converted bitmap of canvas
final int width = bm.getWidth();
final int height = bm.getHeight();
int totalBlackPixels = 0;
for(int x = 0; x < width; x++) {
for(int y = 0; y < height; y++) {
if (bm.getPixel(x, y) == Color.BLACK) {
totalBlackPixels ++;
}
}
}
return ((float)totalBlackPixels )/(width * height); //returns the percentage of black pixel on screen
}
I'm creating a heatzone bitmap from raw data. To do that i convert each value of my rawdata into a color then i affect it to a pixel of a bitmap :
for(int i = 0; i < this.heatDatas.length; i++)
{
for(int j = 0; j < this.maxY; j++)
{
ratio = this.heatDatas[i][j] / (double) this.maxValue;
ratio = ratio * this.nbIndexColors;
idxColor1 = (int) Math.floor(ratio);
idxColor2 = idxColor1 + 1;
distance = ratio - idxColor1;
r = (int) ((colors[idxColor2][0] - colors[idxColor1][0]) * distance + colors[idxColor1][0]);
g = (int) ((colors[idxColor2][1] - colors[idxColor1][1]) * distance + colors[idxColor1][1]);
b = (int) ((colors[idxColor2][2] - colors[idxColor1][2]) * distance + colors[idxColor1][2]);
bmp.setPixel(i, j, Color.argb(this.alpha, r, g, b));
}
}
This is working , but it's really slow (around 800ms for 512*512 bitmap on nexus 5). After some investigation it seems that bmp.setPixel(i, j, Color.argb(this.alpha, r, g, b)); took almost 50% of the total execution time. Color.argb() seems to be negligible.
What should i do to get better performances ?
Thanks
Note : The aim of this code is to display a heatzone hover an imageview
SetPixel has a big overhead. It is usually much faster to get a copy of the bitmap (getPixels) or create it from scratch, modify it and copy back (setPixels).
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 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);