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).
Related
I take a screenshot of a view (which contains a black line drawn on a white background):
myView.setDrawingCacheEnabled(true);
Bitmap drawing = Bitmap.createBitmap(myView.getDrawingCache());
If I rotate this bitmap like so:
Matrix matrix = new Matrix();
matrix.postRotate(45);
drawing = Bitmap.createBitmap(drawing, 0, 0, drawing.getWidth(), drawing.getHeight(), matrix, true);
I will get an image with a transparent background.
What do I have to do in order to replace the transparent color with white and not leave any artifacts? Is this even possible?
Since my image consists of just black lines over a black background, I was able to work around this issue in a very inefficient manner: iterate over all the pixels in the image and replace their color with white if they are not black. It seems that there is a separation zone between Color.TRANSPARENT and Color.WHITE a few pixels wide, which will make life really hard for someone trying to achieve the same result for a more colorful image.
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.
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!
I am creating different custom views with two different colors. According to my app features user will drag those objects on the screen, when dragging objects will overlap each other. I want to differentiated the overlapped area, how to set the combined color for overlapped Area.
Look at the below image. Here I am using canvas for creating those custom views two circles are two different views.
EDIT: If I use opacity 128 I am able to see the background color, but I want the combination color of overlapped objects colors.
The Color mixing you are looking for is sometimes called Intuitive Color Mixing, or the RYB Color System:
RYB:
CC license
A quote from this paper by Nathan Gossett and Baoquan Chen on algorithms for intuitive color mixing summarizes how the intuitive color system works:
"In this model, Red, Yellow and Blue are used as pure primary colors. Red and Yellow mix to form Orange, Yellow and Blue mix to form Green, and Blue and Red mix to form Purple [...]. These are the colors an untrained viewer would expect to obtain using children's paint [...]. In addition, many people do not think of White as the mixture of all colors, but instead as the absence of color (a blank canvas). A more common assumption would be that mixing many colors together would result in a muddy dark brown color."
RYB is not implemented in Android's blend modes and can't really be simulated by mixing alpha/different blend modes.
Most computer graphics applications make use of the RGB or CMYK color spaces:
CMYK:
CC license
CMY is based on subtractive color. Subtractive color mixing means that, starting with white, as we add color, the result gets darker. CMYK is used for mixing colors in images intended for printing in e.g. Photoshop and Illustrator.
RGB:
CC license
RGB is based on Additive Color. The colors on a computer screen are created with light using the additive color method. Additive color mixing begins with black and as more color is added, the result gets lighter and ends in white.
This site discusses CMYK and RGB in more detail.
In neither RGB nor CMYK does mixing blue and yellow produce green, or generally speaking, intuitive color mixes. To implement a RYB color system on Android would be quite involved. The paper by Nathan Gossett and Baoquan Chen quoted above proposes a solution with an algorithm implemented in C at the very end of the paper. This algorithm could be implemented in a custom blend on Android. Drawable.setColorFilter() uses PorterDuffColorfilter which extends ColorFilter. Subclassing ColorFilter as discussed in this SO question would have to be done in native code using the NDK.
CMYK Color Mixing Workaround
In case you are interested in using a CMYK color mixing as a workaround, I've included a basic example of how it can be done below. In this example, A cyan-color circle and a yellow-color circle will be blended to produce a green intersection.
Starting with these .png image files created in Adobe Illustrator:
with hex color value 0x00ffff (cyan) and 0xffff00 (yellow).
Add them to your drawable folder with names cyancircle.png and yellowcircle.png.
Configure your main.xml layout as follows:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#ffffff"
android:padding="30dp">
<ImageView
android:id="#+id/bluecircle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/cyancircle">
</ImageView>
<ImageView
android:id="#+id/yellowcircle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/yellowcircle"
android:layout_marginTop="30dp">
</ImageView>
</RelativeLayout>
Create your Activity:
import android.app.Activity;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.widget.ImageView;
public class PorterDuffTestActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView yellowCircle = (ImageView)findViewById(R.id.yellowcircle);
yellowCircle.getDrawable().setColorFilter(0x88ffff00, PorterDuff.Mode.MULTIPLY);
}
}
Output:
The limitation to this method is that the top shape's alpha has to be set to 50% (the "88" in "0x88ffff00"). For yellow this works reasonable well but for other colors the alpha effect may not be acceptable (the color may appear to be another color, e.g. red becomes pink with low alpha values on white background). Which blend mode is ultimately acceptable for you depends on the set of colors you are going to use and will take some experimentation. Also note the background color may affect the circles' colors in blend mode. In this example the background is set to white.
I have done another example for 6 objects.
Key Points:
onDraw method will not be override for Object Views, and background also will set to transparence
setBackgroundColor(Color.TRANSPARENT);
But, the onDraw method will be renamed as onDrawEx will be called from Overlay View.
public void onDrawEx(Canvas canvas) {
Overlay view will pass a custom canvas to draw into. before pass to object view it will do necessary translate.
mOverlayView = new View(this){
#Override
protected void onDraw(Canvas canvas) {
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);
Canvas canvasBitmap = new Canvas(bitmap);
ViewGroup viewGroup = (ViewGroup)getParent();
for(int i = 0 ; i < viewGroup.getChildCount()-1;i++){
ObjectView objectView = (ObjectView) viewGroup.getChildAt(i);
canvasBitmap.save();
canvasBitmap.translate(objectView.getTranslationX(), objectView.getTranslationY());
objectView.onDrawEx(canvasBitmap);
canvasBitmap.restore();
}
canvas.drawBitmap(bitmap, 0, 0, new Paint());
}
};
use the mPaint.setXfermode(new PorterDuffXfermode(Mode.ADD)); to add colors. But all objects should use colors like 0xFF000030,0xFF0000C0,0xFF003000,0xFF00C000,0xFF300000,0xC00000 then only for all posible overlapping we can get different colors. this is depends on you max number of object.
int k = 0 ;
for(int i = 0 ; i < 2;i++,k++){
int color = 0xFF000000|(0x000030<<i*2);
frameLayout.addView(new ObjectView(this,color,k*50,k*50,k), new FrameLayout.LayoutParams(50, 50));
}
for(int i = 0 ; i < 2;i++,k++){
int color = 0xFF000000|(0x003000<<i*2);
frameLayout.addView(new ObjectView(this,color,k*50,k*50,k), new FrameLayout.LayoutParams(50, 50));
}
for(int i = 0 ; i < 2;i++,k++){
int color = 0xFF000000|(0x300000<<i*2);
frameLayout.addView(new ObjectView(this,color,k*50,k*50,k), new FrameLayout.LayoutParams(50, 50));
}
Here I have modified to support version 8
use
mPaint.setXfermode(new PixelXorXfermode(0x00000000));
for
mPaint.setXfermode(new PorterDuffXfermode(Mode.ADD));
I used layout parameter for translation.
The simplest solution I can think would be to simply use the alpha channel by setting each to 0.5 opacity, either in code or xml. Then the colours should fade into each other when they overlap. This does the mean the colours in the non-overlapping sections will be a little faded, though, and depending on what background you have behind the shapes it may not look good to have them be at all transparent.
I have created a sample Activity with two views
DemoDrawShapeActivity where in the view2 i use canvas.clipPath and canvas.translate
to work this i set minimum sdk version 4
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.RED);
path2.addCircle(getWidth()/2, getHeight()/2, getWidth()/2, Direction.CCW);
canvas.drawPath(path2, paint);
canvas.clipPath(path2);
canvas.save();
canvas.translate(-getTranslationX()+view1.getTranslationX(), -getTranslationY()+view1.getTranslationY());
paint.setColor(Color.BLUE|Color.RED);
canvas.drawPath(path1, paint);
canvas.restore();
}
you can edit paint.setColor(Color.BLUE|Color.RED); to get necessary color according to your logic.
i am using the setTranslationX setTranslationY to move the views.
public boolean onTouchEvent(MotionEvent event) {
switch(event.getActionMasked()){
case MotionEvent.ACTION_UP:
touched[0]=touched[1]=false;
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
if(touched[1]){
view2.setTranslationX(event.getX()-view2.getWidth()/2);
view2.setTranslationY(event.getY()-view2.getHeight()/2);
}else if(touched[0]){
view1.setTranslationX(event.getX()-view1.getWidth()/2);
view1.setTranslationY(event.getY()-view1.getHeight()/2);
view2.invalidate();
}
}
return true;
}
I think you may be looking to draw in a blend mode.
Android will let you do this, just look at this first link.
Composite operations in Android Canvas
Here are all the compositing options
http://developer.android.com/reference/android/graphics/PorterDuffXfermode.html
And their explanations from Mozilla
https://developer.mozilla.org/en/Canvas_tutorial/Compositing
You may take the help from this -
http://www.learnopengles.com/android-lesson-five-an-introduction-to-blending/
and
www.pushing-pixels.org/category/android/page/6
I'm trying to create a 'glow' effect using the Android Path class. However, the gradient is not being warped to fit around the path. Instead, it is simply being display 'above' it and clipped to the path's stroke. Using a square path, the image below shows what I mean:
Instead, that should look more like this:
In other words, the gradient follows the path, and in particular wraps around the corners according to the radius set in the CornerPathEffect.
Here is the relevant part of the code:
paint = new Paint();
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(20);
paint.setAntiAlias(true);
LinearGradient gradient = new LinearGradient(30, 0, 50, 0,
new int[] {0x00000000, 0xFF0000FF, 0x00000000}, null, Shader.TileMode.MIRROR);
paint.setShader(gradient);
PathEffect cornerEffect = new CornerPathEffect(10);
paint.setPathEffect(cornerEffect);
canvas.drawPath(boxPath, paint);
Any ideas?
Another alternative is to get a 'soft-edged brush' effect when defining the stroke width. I've experimented with BlurMaskFilters, but those give a uniform blur rather than a transition from opaque to transparent. Does anyone know if that's possible?
How about drawing with a soft brush bitmap? Make a soft circular brush with opacity decreasing radially outward using image editing software like Photoshop. Save as drawable, load it in a bitmap and draw it evenly spaced along your path. Make the bitmap with white coloured brush. This way you can simply multiply the given colour(Here blue) to your bitmap using PorterDuffColorFilter.
brush1=BitmapFactory.decodeResource(getResources(), R.drawable.brush_custom_one);
//This contains radially decreasing opacity brush
porter_paint.setColorFilter(new PorterDuffColorFilter(paint.getColor(), Mode.MULTIPLY));
for (int i=1;i<matrix.size();i++) {
//matrix contains evenly spaced points along path
Point point = matrix.get(matrix.get(i));
canvas.drawBitmap(brush1, point.x,point.y, porter_paint);}
The brush used is (It's there):
The final result is:
Turns out there was a stupidly obvious way of doing this. Simply re-use the same path, and adjust the stroke width and alpha on each drawing pass. Example code:
float numberOfPasses = 20;
float maxWidth = 15;
for (float i = 0; i <= numberOfPasses; i++){
int alpha = (int) (i / numberOfPasses * 255f);
float width = maxWidth * (1 - i / numberOfPasses);
paint.setARGB(alpha, 0, 0, 255);
paint.setStrokeWidth(width);
canvas.drawPath(path, paint);
}
See below for an example of the result. The left path was drawn using this method, the right path, for comparison, is drawn in a single stroke with maxWidth and 255 alpha.
This mainly works. There are two problems:
The gradient isn't as smooth as it could be. This is because each pass being drawn over the previous one results in the alpha building up too quickly, reaching 255 before the final strokes. Experimenting a bit with the line int alpha = (int) (i / numberOfPasses * 125f); (note the change to 125f rather than 255f) helps.
The path looks like it has been 'cut' on the insides of the corners. Probably some result of the CornerPathEffect applied.
What you're wanting to do, if I understand it right, is to have the gradient effectively form a "brush" for the stroke.
This is exactly what I also was trying to achieve recently, but as far as I can tell the API doesn't provide any straightforward means to do it. I have recently created an SVG to Android Canvas converter class and so I am working a lot in Inkscape lately, too. So, when I was looking into it, I wondered if it's even possible to do it in Inkscape. However, even in Inkscape it's a very non-trivial thing to do. After some searching I eventually came across this image of a gradient being applied along the course of a path, together with a download link for a tutorial beneath:
http://www.flickr.com/photos/35772571#N03/3312087295/
What I was personally trying to do at the time was to create some semi-circles where the path is a kind of neon glow as opposed to a flat colour. Talking in terms of both the Android API and the SVG standard, it seems that the only way to to do this is to create a radial gradient that's centred perfectly on the circle, and position a series of color stops in exactly the right places. Pretty tricky to do, and I certainly don't know how you'd do it to a shape like a square.
Sorry that this is a bit of a 'I couldn't do it either' rather than a useful answer! I'll follow this with interest as I'm eager to know a solution for a kind of 'soft brush' effect too.
Can be very complicated to draw a gradient than follow a path.
So I suggest you to use some library already done than make it for you.
One can be Sc-Gauges.
Have some usefully classe than you can use for your goal.
For first include the library:
dependencies {
...
compile 'com.github.paroca72:sc-gauges:3.0.7'
}
After create an image or what you want with a canvas where draw:
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Now the code:
// Dimensions
int padding = 24;
Rect drawArea = new Rect(padding, padding, 700 - padding, 500 - padding);
// Get the main layout
ImageView imageContainer = (ImageView) this.findViewById(R.id.image);
assert imageContainer != null;
// Create a bitmap and link a canvas
Bitmap bitmap = Bitmap.createBitmap(
drawArea.width() + padding * 2, drawArea.height() + padding * 2,
Bitmap.Config.ARGB_8888
);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.parseColor("#f5f5f5"));
// Create the path building a bezier curve from the left-top to the right-bottom angles of
// the drawing area.
Path path = new Path();
path.moveTo(drawArea.left, drawArea.top);
path.quadTo(drawArea.centerX(), drawArea.top, drawArea.centerX(), drawArea.centerY());
path.quadTo(drawArea.centerX(), drawArea.bottom, drawArea.right, drawArea.bottom);
// Feature
ScCopier copier = new ScCopier();
copier.setPath(path);
copier.setColors(Color.RED, Color.GREEN, Color.BLUE);
copier.setWidths(20);
copier.draw(canvas);
// Add the bitmap to the container
imageContainer.setImageBitmap(bitmap);
And this the result:
The first part of the code is just for create a bitmap where draw.
What you interest is the second part where use ScCopier.
Just give the path, the color and the with.
Note than is you are inside a view you can use onDraw for draw directly on the view canvas.
This library can used to create gauge of every kind.
If you want take a look to this site ScComponents have some free and not gauges components.