What does PorterDuff.Mode mean in android graphics.What does it do?
Here's an excellent article with illustrations by a Google engineer:
http://ssp.impulsetrain.com/porterduff.html
PorterDuff is described as a way of combining images as if they were "irregular shaped pieces of cardboard" overlayed on each other, as well as a scheme for blending the overlapping parts.
The default Android way of composing images is PorterDuff.Mode.SRC_OVER, which equates to drawing the source image/color over the target image. In other words, it does what you would expect and draws the source image (the one you're drawing) on top of the destination image (the canvas) with the destination image showing through to the degree defined by the source image's alpha.
You can use the key below to understand the algebra that the Android docs use to describe the other modes (see the article for a fuller desription with similar terms).
- Sa Source alpha
- Sc Source color
- Da Destination alpha
- Dc Destination color
Where alpha is a value [0..1]
, and color is substituted once per channel (so use the formula once for each of red, green and blue)
The resulting values are specified as a pair in square braces as follows.
[<alpha-value>,<color-value>]
Where alpha-value
and color-value
are formulas for generating the resulting alpha chanel and each color chanel respectively.
What do PorterDuff source and destination refer to when drawing on canvas?
What do PorterDuff source and destination refer to when drawing on canvas?
After some in-depth study, I write a few demo to explain this deeply. To help you understand what is source and destination refer to.
First, look at the following code :
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
//Set the background color
canvas.DrawARGB(255, 139, 197, 186);
int canvasWidth = canvas.Width;
int r = canvasWidth / 3;
//Draw a yellow circle
paint.Color = Color.Yellow;
canvas.DrawCircle(r, r, r, paint);
//Draw a blue rectangle
paint.Color = Color.Blue;
canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint);
}
I override the OnDraw
method, set a green background, then draw a yellow circle and a blue rectangle, effect :
Above is the normal procedure when we draw a Canvas
, I didn't use any PorterDuffXfermode
,let's analyse its process :
First, we call
canvas.DrawARGB(255, 139, 197, 186)
method draw the wholeCanvas
with a single color, every pixels in this canvas has the sameARGB
value :(255, 139, 197, 186)
. Since the alpha value inARGB
is 255 instead of 0, so every pixels is opaque.Second, when we execute
canvas.DrawCircle(r, r, r, paint)
method, Android will draw a yellow circle at the position you have defined. All pixels which ARGB value is(255,139,197,186)
in this circle will be replaced with yellow pixels.
The yellow pixels is source and the pixels which ARGB value is(255,139,197,186)
is destination. I will explain later.Third, after execute the
canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint)
method, Android will draw a blue rectangle, all pixels in this rectangle is blue, and these blue pixels will replac other pixels in the same position. So the blue rectangle can be draw onCanvas
.
Second, I use a mode of Xfermode
, PorterDuff.Mode.Clear
:
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
//Set the background color
canvas.DrawARGB(255, 139, 197, 186);
int canvasWidth = canvas.Width;
int r = canvasWidth / 3;
//Draw a yellow circle
paint.Color = Color.Yellow;
canvas.DrawCircle(r, r, r, paint);
//Use Clear as PorterDuffXfermode to draw a blue rectangle
paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.Clear));
paint.Color = Color.Blue;
canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint);
paint.SetXfermode(null);
this.SetLayerType(LayerType.Software, null);
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//I found that PorterDuff.Mode.Clear doesn't work with hardware acceleration, so you have add this code
}
Effect :
Let's analyse its process :
First, we call
canvas.DrawARGB(255, 139, 197, 186)
method to draw the wholeCanvas
as a single color, every pixels is opaque.Second, we call
canvas.DrawCircle(r, r, r, paint)
method to draw a yellow
circle inCanvas
.Third, execute
paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.Clear))
, set the paintPorterDuff
model toClear
.Forth, call
canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint)
to draw a blue rectangle, and finally it shows a white rectangle.
Why it display a white rectangle? Usually, when we call canvas.DrawXXX()
method we will pass a Paint
parameter, when Android execute draw method it will check whether the paint has a Xfermode
mode. If not, then the graphics will directly covers the pixels that in Canvas
at the same position. Otherwise, it will update the pixels in Canvas
according to the Xfermode
mode.
In my example, when execute canvas.DrawCirlce()
method, Paint
didn't has a Xfermode
model, so the yellow circle directly cover the pixels in Canvas
. But when we call canvas.DrawRect()
to draw a rectangle, Paint
has a Xfermode
value PorterDuff.Mode.Clear
. Then Android will draw a rectangle in memory, the pixels in this rectangle has a name : Source. The rectangle in memory has a corresponding rectangle in Canvas
, the corresponding rectangle is called :
destination .
The value of the ARGB
of the source pixel and the value of the ARGB
of the destination pixel are calculated according to the rules defined by Xfermode
, it will calculate the final ARGB value. Then update the ARGB
value of the target pixel with the final ARGB
value.
In my example, the Xfermode
is PorterDuff.Mode.Clear
, it require destination pixels ARGB
becomes (0,0,0,0)
, that means it is transparent. So we use canvas.DrawRect()
method draw a transparent rectangle in Canvas
, since Activity
itself has a white background color, so it will show an white rectangle.
EDIT :
To implement the feature you post in the picture, I write a demo :
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
paint.SetARGB(255, 255, 0, 0);
RectF oval2 = new RectF(60, 100, 300, 200);
canvas.DrawOval(oval2, paint);
paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.*));
Path path = new Path();
paint.SetStyle(Paint.Style.Fill);
paint.SetARGB(255, 0, 0, 255);
path.MoveTo(180, 50);
path.LineTo(95, 240);
path.LineTo(255, 240);
path.Close();
this.SetLayerType(LayerType.Software, null);
canvas.DrawPath(path, paint);
paint.SetXfermode(null);
}
When use different Xfermode
, their effect :
Xor, SrcOut, Screen, Lighten, Darken, Add.
As you can see, you could use different color and different Xfermode
to achieve your effect.
Drawing on Canvas - PorterDuff.Mode.CLEAR draws black! Why?
PorterDuff.Mode.CLEAR
doesn't work with hardware acceleration. Just set
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Works perfectly for me.
What is the best way to colour a part of the letter?
One approach is using a PorterDuffXfermode
for compositing the blue rectangle over the text. You could extend TextView
and override onDraw()
to draw the rectangle after the text has been drawn, and with the proper mode (I believe XOR
is the one you want) it should achieve the desired effect. Something like this:
public class ProgressTextView extends TextView {
private static final float MAX_PROGRESS = ...;
private Paint mPaint;
public ProgressTextView(Context context) {
super(context);
initPaint();
}
/* other constructor omitted, but do the same pattern in those */
private void initPaint() {
mPaint = new Paint();
mPaint.setColor(...);
mPaint.setXfermode(new PorterDuffXfermode(Mode.XOR));
// note: you may also need the following line if hardware accel is available
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawProgress(canvas);
}
private void drawProgress(Canvas canvas) {
int w = getWidth() - getPaddingLeft() - getPaddingRight();
int h = getHeight() - getPaddingTop() - getPaddingBottom();
float progress = getProgress();
float rectW = w * (progress / MAX_PROGRESS);
int saveCount = canvas.save();
canvas.translate(getPaddingLeft(), getPaddingTop());
canvas.drawRect(0, 0, rectW, h, mPaint);
canvas.restoreToCount(saveCount);
}
private float getProgress() {
// TODO
}
}
More info on Porter/Duff compositing: http://ssp.impulsetrain.com/porterduff.html
Android Paint PorterDuff.Mode.CLEAR
The problem with the fingerpaint code is that what you see is not the same that is compressed into the png. Look at onDraw(). First you draw the screen white. Then you add the Bitmap. Because you used Porter Duff Clear the erased part of the bitmap contains actually transparent black pixels (value 0x00000000). But because you have the white background these black pixels show as white.
To fix this either change your save code to do the same thing as the draw code
try {
fos = new FileOutputStream(file);
Bitmap saveBitmap = Bitmap.createBitmap(mBitmap);
Canvas c = new Canvas(saveBitmap);
c.drawColor(0xFFFFFFFF);
c.drawBitmap(mBitmap,0,0,null);
saveBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
saveBitmap.recycle();
...
or don't use PortDuff.Clear:
case ERASE_MENU_ID:
mPaint.setColor(Color.WHITE);
Related Topics
Read Only File System on Android
Android Dialogfragment VS Dialog
How to Marshall and Unmarshall a Parcelable to a Byte Array with Help of Parcel
Handle Screen Rotation Without Losing Data - Android
Create Blurry Transparent Background Effect
Example Communicating with Handlerthread
Does Alarm Manager Persist Even After Reboot
Android: Force Edittext to Remove Focus
No Need to Cast the Result of Findviewbyid
No Internet on Android Emulator - Why and How to Fix
How to View Androidmanifest.Xml from APK File
Android List View Drag and Drop Sort
How to Fix: "Hax Is Not Working and Emulator Runs in Emulation Mode"
How to Create Edittext Accepts Alphabets Only in Android
How to Change Progressbar's Progress Indicator Color in Android
How to Set Edittext to Only Accept Numeric Values in Android