let's say i have this bitmap, which is a random shape painted all black, and say i want to be bale to change it color, does my bitmap have to be all painted white first or is there something else to it?
If you're using Canvas the way to alter the bitmap's color is to alter the bitmap itself. The steps involved are as follows:
Say you want to load an existing Bitmap you have somewhere and you want to tint it red somehow.
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap);
After that you want to modify the bitmap's pixels before you paint it onto the Canvas. You create an int array that will holds all your pixels.
int[] pixels;
bitmap.getPixels(pixels, 0, 0, 0, 0, width, height);
After that you need to modify the array (say, adding to the red component). However, right now all we have is int values inside a pixel array. R,G and B are all packed inside. How to retrieve them?
int red = Color.red(pixels[n]);
int green = Color.green(pixels[n]);
int blue= Color.blue(pixels[n]);
Then you modify the pixel's value by whatever you want, you could put it in a loop or however you like, and then put it back to the pixels array. Also, RGB values go from 0-255 because they are 8-bit values.
Right after that you would put them back using exactly the opposite function.
bitmap.setPixels(pixels, 0, 0, 0, 0, width, height);
And then you're ready to go calling Canvas.drawBitmap();
Keep in mind that this process ought to be slow if you do it frequently, besides Canvas is a slow way of doing thing's if you're interested in real-time apps such as games.
Hope it helped!
Related
I've created a simple custom control in android and on it I place a background image. I'm having problems when the control is placed on a layout at different sizes (i.e. when it is stretched), specifically:
I wish to overlay a rectangle at a specific position and size, which I know the pixel position for the original image. How can I do this with my control. I suspect something like this is impossible given it's a 9-patch. Is my best bet to work out the percentage from the top/left on the original or is that pointless given some parts stretch and some don't?
In the custom control I set the image like this in the constructor:
setBackgroundResource(R.drawable.buttonbt);
Which is working just fine, however I wanted to originally draw it in the onDraw event as I might want to change it depending on property changes, e.g.
Bitmap b=BitmapFactory.decodeResource(getResources(), R.drawable.buttonbt);
canvas.drawBitmap(b, 0, 0, null);
But this does not resize according to the size of its bounding box, it is simply trying to show it at it's original size without scaling to fit. How would you do this (whether the former method is better or not).
thanks.
You can create a scaled bitmap as below
Bitmap bitmap = Bitmap.createScaledBitmap(b, width, height, true);
Hope it will work for you. Please let me know!
ok when your View is say 100x100 px and your Bitmap is 300x300 you can try the following (pseudo code here) in inDraw method:
# src is a Bitmap 300x300
# dst is a View 100x100
mMatrix.setRectToRect(RectF src, RectF dst, Matrix.ScaleToFit stf)
canvas.save()
canvas.concat(mMatrix)
canvas.drawBitmap(mBitmap, 0, 0, null)
// actually it will draw a rect with left/top edge at (10, 10) and right/bottom at (20, 20)
canvas.drawRect(30, 30, 60, 60, paint)
canvas.restore()
I need to do something which exactly the opposite of Bitmap.extractAlpha: Apply an alpha map (which loaded from a file) onto a RGB bitmap (which also loaded from a file).
Yes! Just like "CGImageCreateWithMask" in iOS!
The Square blog had a tutorial about this just last week: http://corner.squareup.com/2013/01/transparent-jpegs.html :).
I dont know what exactly CGImageCreateWithMask does, but if you want another picture to serve as the alpha channel for your Bitmap, you can create one as described in this question, which combines four images. I haven't tried it now, but I think for two colors it would look something like this:
Paint colorPaint = new Paint();
redPaint.setShader(new BitmapShader(redChanImg, TileMode.CLAMP, TileMode.CLAMP));
Paint alphaPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
alphaPaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
c.setBitmap(resultImage);
c.drawRect(0, 0, width, height, colorPaint);
c.drawBitmap(alphaImg, 0, 0, alphaPaint);
// save result somewhere
You can also always just work on the raw pixel data of a bitmap using Bitmap.getPixels
In my application I have a custom view that renders some bitmaps and draws them to the view's canvas using onDraw(). The canvas is filled with a color at first. Essentially I have the following code:
public static int COLOR = Color.rgb(200, 50, 50);
#Override
public void onDraw(Canvas canvas) {
canvas.drawColor(COLOR);
Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);
Canvas c = new Canvas(bitmap);
c.drawColor(COLOR);
canvas.drawBitmap(bitmap, 0, 0, null);
}
I'm expecting the code to create a continuously red screen; the bitmap is rendered in a different shade of red though, so its position is visible. To analyze the colors I made a screenshot of it - the bitmap is drawn with (206,48,49) instead of (200,50,50).
Obviously this must have to do with the bitmap using RGB_565 instead of ARGB_8888 (which I don't want to use though). So my question is, how can I fill the view's canvas with a RGB_565 color in order to work around those color issues?
I tried converting (200,50,50) to RGB_565 by dropping the least significant bits (red >> 3, green >> 2, blue >> 3), but of course that doesn't make a difference here. What does Android do internally to get (206,48,49) from (200,50,50)? Where is my error in reasoning?
Finally figured this out myself...
In my onDraw() method, there are two implicit color space conversions:
Draw (200,50,50) (RGB_888) on RGB_565 bitmap.
Draw RGB_565 bitmap on RGB_888 canvas.
(200,50,50) is equal to (25,12,6) in RGB_565 (red >> 3, green >> 2, blue >> 3). Besides,
not surprisingly, (206,48,49) is also (25,12,6) in RGB_565.
Now when you convert (25,12,6) back to RGB_888, e.g. using this algorithm, you get (206,49,49) - close enough. I really can't explain why Android returns 48 instead of 49 for the green channel, though. Maybe it's a rounding error or floating point imprecision. A look at the Android source might help, but since this is not an earth-shattering problem, I'll give it a pass.
am creating temp Bitmap to draw a Text on it, and the i want to get it Pixels so i can manipulate these pixels (i don't show this image on screen).
this is the code
Bitmap tempBitmap=Bitmap.createBitmap(200, 400, Bitmap.Config.ARGB_8888);//i've tested all Configs
Canvas tempCanvas=new Canvas(tempBitmap);
tempCanvas.drawColor(Color.WHITE);
tempCanvas.drawText("Hello", 0, 0, mPaint);//mPaint color set to Black
int[] pixels=new int[tempBitmap.getWidth() * tempBitmap.getHeight()];
tempBitmap.getPixels(pixels, 0, tempBitmap.getWidth(), 0, 0, tempBitmap.getWidth(), tempBitmap.getHeight());
but when i print all pixels they all -1 value !! why?
You're positioning the baseline of the text at (0,0), so you're drawing it just off the top of the bitmap. Move it down a bit. You can use Paint.getTextBounds to measure the text size, and then use the returned height to move your text downwards.
I am creating bitmap, next i am drawing second solid color bitmap on top of it.
And now i want to change first bitmap, so solid color that i drawed on it will be transparent.
Or simply, i want to remove all pixels of one color from bitmap.
I havie tried every colorfilter, and xfermode with no luck, is there any other possibility to remove color other that doing it pixel by pixel?
This works for removing a certain color from a bitmap. The main part is the use of AvoidXfermode. It should also work if trying to change one color to another color.
I should add that this answers the question title of removing a color from a bitmap. The specific question is probably better solved using PorterDuff Xfermode like the OP said.
// start with a Bitmap bmp
// make a mutable copy and a canvas from this mutable bitmap
Bitmap mb = bmp.copy(Bitmap.Config.ARGB_8888, true);
Canvas c = new Canvas(mb);
// get the int for the colour which needs to be removed
Paint p = new Paint();
p.setARGB(255, 255, 0, 0); // ARGB for the color, in this case red
int removeColor = p.getColor(); // store this color's int for later use
// Next, set the alpha of the paint to transparent so the color can be removed.
// This could also be non-transparent and be used to turn one color into another color
p.setAlpha(0);
// then, set the Xfermode of the pain to AvoidXfermode
// removeColor is the color that will be replaced with the pain't color
// 0 is the tolerance (in this case, only the color to be removed is targetted)
// Mode.TARGET means pixels with color the same as removeColor are drawn on
p.setXfermode(new AvoidXfermode(removeColor, 0, AvoidXfermode.Mode.TARGET));
// draw transparent on the "brown" pixels
c.drawPaint(p);
// mb should now have transparent pixels where they were red before
user487252's solution works like a charm up until API level 16 (Jelly Bean), after which AvoidXfermode does not seem to work at all.
In my particular use case, I have rendered a page of a PDF (via APV PDFView) into a pixel array int[] that I am going to pass into Bitmap.createBitmap( int[], int, int, Bitmap.Config ). This page contains line art drawn onto a white background, and I need to remove the background while preserving the anti-aliasing.
I couldn't find a Porter-Duff mode that did exactly what I wanted, so I ended up buckling and iterating through the pixels and transforming them one by one. The result was surprisingly simple and performant:
int [] pixels = ...;
for( int i = 0; i < pixels.length; i++ ) {
// Invert the red channel as an alpha bitmask for the desired color.
pixels[i] = ~( pixels[i] << 8 & 0xFF000000 ) & Color.BLACK;
}
Bitmap bitmap = Bitmap.createBitmap( pixels, width, height, Bitmap.Config.ARGB_8888 );
This is perfect for drawing line art, since any color can be used for the lines without losing the anti-aliasing. I'm using the red channel here, but you can use green by shifting 16 bits instead of 8, or blue by shifting 24.
Pixel by pixel is not a bad option. Just don't call setPixel inside your loop. Fill an array of argb ints with getPixels, modify it in place if you don't need to preserve the original, and then call setPixels at the end. You can do this row-by-row if memory is a concern, or you can just do the whole thing in one shot. You don't need to fill a whole bitmap for your overlay color since you'd just be doing a simple replace (if current pixel is color1, set to color2).