Pulling my hear out over this one. I have a background bitmap that I want to overlay another bitmap on top of that has transparent cutouts. I have no problem doing that if the cutout is a basic shape, but I need the cutout to be the intersection of two circles (sort of a leaf shape). I tried making a third bitmap to produce the a cutout template but I can't even get a clean representation of the cutout let alone get it to work as a cutout template.
Anyone know how to do something like this? Here is my (simplified) attempt:
#Override
public void draw(Canvas canvas) {
float w = canvas.getWidth();
float h = canvas.getHeight();
// just used to set some proportions
float off = 300f;
Paint paint = new Paint();
// make a background bitmap with a yellow to green gradient
Bitmap bitmapBkg = Bitmap.createBitmap((int) w, (int) h, Bitmap.Config.ARGB_8888);
Canvas canvasBkg = new Canvas(bitmapBkg);
paint.reset();
paint.setShader(new LinearGradient(0, h/2 - off, 0, h/2 + off, Color.YELLOW, Color.GREEN, Shader.TileMode.CLAMP));
canvasBkg.drawRect(new RectF(0, 0, w, h), paint);
// make an overlay bitmap with a red to magenta gradient which will have the cutouts
Bitmap bitmapOver = Bitmap.createBitmap((int) w, (int) h, Bitmap.Config.ARGB_8888);
Canvas canvasOver = new Canvas(bitmapOver);
paint.reset();
paint.setShader(new LinearGradient(0, h/2 - off, 0, h/2 + off, Color.RED, Color.MAGENTA, Shader.TileMode.CLAMP));
canvasOver.drawRect(new RectF(0, 0, w, h), paint);
// make a bitmap of intersecting circles to be used as the cutout shape
Bitmap bitmapCut = Bitmap.createBitmap((int) w, (int) h, Bitmap.Config.ARGB_8888);
Canvas canvasCut = new Canvas(bitmapCut);
paint.reset();
paint.setColor(Color.BLACK);
//paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
canvasCut.drawCircle(w / 2 - (off / 2 ), h / 2, off, paint);
paint.reset();
paint.setColor(Color.BLACK);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
canvasCut.drawCircle(w / 2 + (off / 2), h / 2, off, paint);
// apply cutout to overlay
paint.reset();
paint.setColor(Color.BLACK);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
canvasOver.drawBitmap(bitmapCut, 0, 0, paint);
// draw background and overlay onto main canvas
paint.reset();
paint.setColor(Color.BLACK);
canvas.drawBitmap(bitmapBkg, 0, 0, paint);
canvas.drawBitmap(bitmapOver, 0, 0, paint);
}
Here is an image of what I am getting:
What I am trying to get would have the outside portion also red-magenta; only the eye-shape in the middle should be yellow-green.
Turns out the trick was adding yet another layer.
// make a secondary overlay that cuts out the whole circles
Bitmap bitmapOver2 = Bitmap.createBitmap((int) w, (int) h, Bitmap.Config.ARGB_8888);
Canvas canvasOver2 = new Canvas(bitmapOver2);
paint.reset();
paint.setShader(new LinearGradient(0, h / 2 - off, 0, h / 2 + off, Color.RED, Color.MAGENTA, Shader.TileMode.CLAMP));
canvasOver2.drawRect(new RectF(0, 0, w, h), paint);
paint.reset();
paint.setColor(Color.BLACK);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvasOver2.drawCircle(w / 2 - (off / 2), h / 2, off, paint);
canvasOver2.drawCircle(w / 2 + (off / 2), h / 2, off, paint);
Applying it like so:
// draw background and overlay onto main canvas
paint.reset();
paint.setColor(Color.BLACK);
canvas.drawBitmap(bitmapBkg, 0, 0, paint);
canvas.drawBitmap(bitmapOver2, 0, 0, paint);
canvas.drawBitmap(bitmapOver, 0, 0, paint);
Basically it is a bit of a trick. It draws the mid-tier backdrop twice, once with a full circle cutouts and the other with the eye-shape cutout. The two fit together just right to pull off the desired effect.
Of course #Rotwang, you are probably right. Using Path and arcTo() would be a much better solution. The only reason I avoided that approach is b/c arcTo() is an api 21+ feature. So far I've manged to keep the api to 17+. But if anyone else would like to provide an arcTo() solution for completeness that would be cool to compare.
Related
I have a bitmap like in the picture. I want to add a border for it using Canvas. Can anyone help me?
You can draw a border using Paint.STYLE.STROKE. You need to do two separate calls to draw.
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.parseColor("#BAB399")); // set fill color
canvas.drawCircle(sbmp.getWidth() / 2+0.7f, sbmp.getHeight() / 2+0.7f,
sbmp.getWidth() / 2+0.1f, paint);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10); // set stroke width
paint.setColor(Color.parseColor("#ffffff")); // set stroke color
canvas.drawCircle(sbmp.getWidth() / 2+0.7f, sbmp.getHeight() / 2+0.7f,
sbmp.getWidth() / 2+0.1f, paint);
If the image has a transparent background (png file) and the stroke size is not too large, you can just draw the bitmap with a color overlay applied to it 4 times -- left, top, right and bottom.
final float stroke = 20F; //stroke size in pixels.
//Load target image as bitmap.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shap, options);
//A new, clear bitmap with same size.
Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newBitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
//Overlay target bitmap with black color.
PorterDuffColorFilter filter = new PorterDuffColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN);
paint.setColorFilter(filter);
//Draw target bitmap over new bitmap.
canvas.drawBitmap(bitmap, -stroke, 0F, paint); //left
canvas.drawBitmap(bitmap, 0F, -stroke, paint); //top
canvas.drawBitmap(bitmap, stroke, 0F, paint); //right
canvas.drawBitmap(bitmap, 0F, stroke, paint); //bottom
//Remove overlay.
paint.setColorFilter(null);
//Draw target bitmap.
canvas.drawBitmap(bitmap, 0F, 0F, paint);
//Load new bitmap into ImageView.
ImageView iv = findViewById(R.id.iv);
iv.setImageBitmap(newBitmap);
The feature of a bitmap image is that it has a transparent background and usually has a rectangular shape, so when we try to add a border to the image, instead of its content inside there will be a border, its background will have a border. For example, I have a bitmap image of a dog and I want it to have a border but instead, a rectangular background will have a border. I'm using Canvas to draw a bitmap and am having this problem. Can anyone help me?
Block code to create photo
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10); // set stroke width
mPaint.setColor(getResources().getColor(R.color.black)); // set stroke color
mPaint.setAntiAlias(true);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icons8_bus_36_2);
tempBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas tempCanvas = new Canvas(tempBitmap);
Rect rect = new Rect(
10 / 2,
10 / 2,
tempCanvas.getWidth() - 10 / 2,
tempCanvas.getHeight() - 10 / 2);
tempCanvas.drawRect(rect,mPaint);
tempCanvas.drawBitmap(bitmap, 0, 0, mPaint);
On this part:
Rect rect = new Rect(
10 / 2,
10 / 2,
tempCanvas.getWidth() - 10 / 2,
tempCanvas.getHeight() - 10 / 2);
change to
Rect rect = new Rect(
10 / 2,
10 / 2,
(tempCanvas.getWidth() - 10) / 2,
(tempCanvas.getHeight() - 10) / 2);
Thats because 10 is divided by two first
Also, draw bitmap first before you draw rect
OR
you could draw with this:
Path clipPath = new Path();
RectF rect = new RectF(0, 0, this.getWidth(), this.getHeight());
clipPath.addRoundRect(rect, radius, radius, Path.Direction.CW);
canvas.clipPath(clipPath);
So, basically, if a bitmap exists, draw it first, stretched accross whole view/canvas. Then draw some text and a rectangle over top of that after, and it should work great. Right?
I was under the assumption that this would draw a "background" (Being currentPicture) and then draw some stuff below it, the % and the rectangle.
Wondering why it's not behaving like such? It was working before, but something changed and now it doesn't.
Further, the:
canvas.drawRect(0, 0, width, 200, p);
does not draw.
Similar story with:
canvas.drawRect(0, 100, width, 200, p);
both of which are in the if statement. I believe the first one shouldn't be drawn, but the second one should. The bitmap is drawn correctly.
Any questions, please ask!
Code:
#Override
public void onDraw(Canvas canvas) {
int myColor = 0;
p.setColor(Color.TRANSPARENT);
canvas.drawRect(0, 0, width, height, p);
if (currentPicture != null) {
p.setColor(Color.RED);
canvas.drawRect(0, 0, width, 200, p);
canvas.drawBitmap(currentPicture, new Rect(0, 0, width, height), new Rect(0, 0, width, height), p);
canvas.translate(translateX, translateY);
canvas.scale(scaleX, scaleY);
pictureCanvas = canvas;
p.setColor(Color.GREEN);
canvas.drawRect(0, 100, width, 200, p);
}
if (ci >= 66) {
myColor = (Color.RED);
} else if (ci >= 33) {
myColor = (Color.YELLOW);
} else {
myColor = (Color.GREEN);
}
p.setColor(myColor);
// Progress bar stuff.
canvas.drawRect(0, height - 100, (progress * width / 20), height, p);
// backdrop for textview.
p.setColor(Color.BLACK);
canvas.drawText(ci + "%", -1, height - 19, p);
canvas.drawText(ci + "%", +1, height - 21, p);
// Draw CI.
p.setColor(myColor);
p.setTextSize(100);
canvas.drawText(ci + "%", 0, height - 20, p);
}
p.setColor(Color.TRANSPARENT);
canvas.drawRect(0, 0, width, height, p);
are pretty nonsense especially when you draw a complete background after that.
Have you tried to remove the translate/scale to see if this might be the issue?
Oh, and why are you doing that: pictureCanvas = canvas;?
i am able to draw arc using Paint and passing some gradient color to it, my problem is that i need draw the arc using a gradient image.Is it possible to draw arc using an image?
If so how to do it?
this is my current code:
Paint nPaint = new Paint();
nPaint.setAntiAlias(true);
nPaint.setDither(true);
nPaint.setStrokeJoin(Paint.Join.ROUND);
nPaint.setStrokeCap(Paint.Cap.ROUND);
int gradientStart = getResources().getInteger(R.integer.gradient_start);
int gradientend = getResources().getInteger(R.integer.gradient_end);
nPaint.setShader(new RadialGradient(getWidth() / 2, getHeight() / 2, getWidth() / 2,
gradientStart, gradientend, Shader.TileMode.CLAMP));
Try this code i hope this will help you
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.CYAN);
Paint p = new Paint();
// smooths
p.setAntiAlias(true);
p.setColor(Color.RED);
p.setStyle(Paint.Style.STROKE);
p.setStrokeWidth(5);
// opacity
//p.setAlpha(0x80); //
RectF rectF = new RectF(50, 20, 100, 80);
canvas.drawOval(rectF, p);
p.setColor(Color.BLACK);
canvas.drawArc (rectF, 90, 45, true, p);
}
I am trying to cut a circle from a square bitmap using following code
Canvas canvas=new Canvas(bitmapimg );
int circleXCoord = bitmapimg .getWidth() / 2;
int circleYCoord = bitmapimg .getHeight() / 2;
int circleRadius = bitmapimg .getWidth() / 2;
Rect rect = new Rect(circleXCoord - circleRadius, circleYCoord - circleRadius, circleXCoord + circleRadius, circleYCoord + circleRadius);
int width = rect.width();
int height = rect.height();
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.BLUE);
canvas.drawRect(rect, paint);
canvas.drawBitmap(bitmapimg , rect, rect, paint);
Path p = new Path();
p.addCircle(circleXCoord, circleYCoord, width / 2F, Path.Direction.CW);
canvas.clipPath(p, Region.Op.DIFFERENCE);
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
The idea is to attach a square (rectangular) bitmap to canvas and then clip a circular path. Clear out the difference between the rectangle and circle (make it transparent).
The code works fine for Android 4, but on Android 2.3.3 device, the difference area is appearing black rather that transparent.
Am I missing something here or PorterDuff.Mode.CLEAR is not supported in gingerbread? Is there a better way to cut a circle from a square in Android?
Seems like PorterDuff.Mode.Clear did not work for gingerbread
Solved the problem (cut circle from square using this code)
public Bitmap BitmapCircularCroper(Bitmap bitmapimg){
Bitmap output = Bitmap.createBitmap(bitmapimg.getWidth(),
bitmapimg.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final int color = 0xff424242;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmapimg.getWidth(),
bitmapimg.getHeight());
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawCircle(bitmapimg.getWidth() / 2,
bitmapimg.getHeight() / 2, bitmapimg.getWidth() / 2, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmapimg, rect, rect, paint);
return output;
}
import android.graphics.PorterDuff.Mode;
import android.graphics.Bitmap.Config;
public static Bitmap getCircularBitmap(Bitmap bitmap)
{
Bitmap output;
if (bitmap.getWidth() > bitmap.getHeight()) {
output = Bitmap.createBitmap(bitmap.getHeight(), bitmap.getHeight(), Config.ARGB_8888);
} else {
output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getWidth(), Config.ARGB_8888);
}
Canvas canvas = new Canvas(output);
final int color = 0xff424242;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
float r = 0;
if (bitmap.getWidth() > bitmap.getHeight()) {
r = bitmap.getHeight() / 2;
} else {
r = bitmap.getWidth() / 2;
}
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawCircle(r, r, r, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}
For anyone that's still looking at this, this answer will cause a few issues.
1.) The canvas that you are creating in this instance will not have hardware acceleration. Even though your Paint object has anti-aliasing, the canvas will not. This will cause artifacting when you decide to paint this back to your original canvas in your onDraw() call.
2.) This takes a lot more resources. You have to create a second Bitmap (which can cause OOM), and a secondary Canvas as well as all of the different alterations you have to do.
Please check out Romain Guy's answer. You create a BitmapShader and then create a RoundRect that gives you a Circle. You just need to know the dimensions of your RectF so that it can determine the circle properly.
This means that if you know the center point (x, y) and radius, you can easily determine the RectF.
left = x - radius;
top = y - radius;
right = x + radius;
bottom = y + radius;
This also means that with this solution posted below you only have to draw to the screen once, everything else is done in the off-screen buffer.
http://www.curious-creature.com/2012/12/11/android-recipe-1-image-with-rounded-corners/
The best solution is found here:
BitmapShader shader;
shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(shader);
RectF rect = new RectF(0.0f, 0.0f, width, height);
// rect contains the bounds of the shape
// radius is the radius in pixels of the rounded corners
// paint contains the shader that will texture the shape
canvas.drawRoundRect(rect, radius, radius, paint);