The popular game Words with Friends draws letter tiles at the game board as a single entity -
You can see a yellow linear gradient applied to all letter tiles in the following screenshot and also an emboss effect on the edge:
In my word game I would like to have similar effects:
So I create a game board sized mBitmap, then draw all tiles into it and finally draw the bitmap into my custom view -
Setup:
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
// create yellow linear gradient
mGradStart = new Point(3 * mWidth / 4, mHeight / 3);
mGradEnd = new Point(mWidth / 4, 2 * mHeight / 3);
LinearGradient gradient = new LinearGradient(
mGradStart.x,
mGradStart.y,
mGradEnd.x,
mGradEnd.y,
new int[]{ 0xCCFFCC00, 0xCCFFCC99, 0xCCFFCC00 },
null,
TileMode.CLAMP);
// create the big bitmap holding all tiles
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mPaintGrad = new Paint();
mPaintGrad.setShader(gradient);
mPaintEmboss = new Paint();
mPaintEmboss.setShader(gradient);
EmbossMaskFilter filter = new EmbossMaskFilter(
new float[] { 0f, 1f, 0.5f }, 0.8f, 3f, 3f);
mPaintEmboss.setMaskFilter(filter);
Drawing:
#Override
protected void onDraw(Canvas canvas) {
mGameBoard.draw(canvas);
// draw all tiles as rectangles into big bitmap
// (this code will move to onTouchEvent later)
mBitmap.eraseColor(Color.TRANSPARENT);
for (SmallTile tile: mTiles) {
mCanvas.drawRect(
tile.left,
tile.top,
tile.left + tile.width,
tile.top + tile.height,
mPaintGrad);
tile.draw(mCanvas);
}
canvas.drawBitmap(mBitmap, 0, 0, mPaintEmboss); // emboss NOT displayed
canvas.drawText("TEXT WORKS OK", 400, 400, mPaintEmboss); // ebmoss OK
canvas.drawRect(300, 600, 800, 1200, mPaintEmboss); // emboss OK
}
The EmbossMaskFilter effect works OK with drawText() and drawRect() calls, but it does NOT work for the drawBitmap():
My question: is it possible to use some combinations of PorterDuff.Mode (and extractAlpha?) to draw an emboss around my big bitmap?
UPDATE:
By looking at HolographicOutlineHelper.java I have been able to add an outer shadow:
with the following code in MyView.java -
Setup:
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
mScale = getResources().getDisplayMetrics().density;
mGradStart = new Point(3 * mWidth / 4, mHeight / 3);
mGradEnd = new Point(mWidth / 4, 2 * mHeight / 3);
LinearGradient gradient = new LinearGradient(
mGradStart.x,
mGradStart.y,
mGradEnd.x,
mGradEnd.y,
new int[]{ 0xCCFFCC00, 0xCCFFCC99, 0xCCFFCC00 },
null,
TileMode.CLAMP);
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mPaintGrad = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
mPaintGrad.setShader(gradient);
mPaintBlur = new Paint();
mPaintBlur.setColor(Color.BLACK);
BlurMaskFilter blurFilter = new BlurMaskFilter(mScale * 1, Blur.OUTER);
mPaintBlur.setMaskFilter(blurFilter);
}
Drawing:
private void prepareBitmaps() {
mBitmap.eraseColor(Color.TRANSPARENT);
for (SmallTile tile: mTiles) {
mCanvas.drawRect(
tile.left,
tile.top,
tile.left + tile.width,
tile.top + tile.height,
mPaintGrad);
tile.draw(mCanvas);
}
mAlphaBitmap = mBitmap.extractAlpha(mPaintBlur, mOffset);
}
#Override
protected void onDraw(Canvas canvas) {
mGameBoard.draw(canvas);
canvas.drawBitmap(mAlphaBitmap, mOffset[0], mOffset[1], mPaintBlur);
canvas.drawBitmap(mBitmap, 0, 0, mPaintGrad);
}
but unfortunately the app is acting slow now - and I still don't know how to add an emboss effect around the bitmap.
I'm not sure i got exacly what you need, but if you just want to apply EmbossMaskFilter around some png letter with alpha channel, you can pretty much do this trick with
EmbossMaskFilter filter = new EmbossMaskFilter(new float[]{1, 1, 1}, 0.5f, 0.6f, 2f);
Paint paintEmboss = new Paint();
paintEmboss.setMaskFilter(embossMaskFilter);
Bitmap helperBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas helperCanvas = new Canvas(helperBitmap);
Bitmap alpha = src.extractAlpha();
helperCanvas.drawBitmap(alpha, 0, 0, paintEmboss);
alpha.recycle();
...
canvas.drawBitmap(helperBitmap, 0, 0, anyPaint);
You will never want all of this code in 1 onDraw, because it creates lots of objects in memory. And src.extractAlpha(); creates new Bitmap each time. (Btw i always get out of memory error from your project git . Added mAlphaBitmap.recycle(); and it could at least boot. But it still lagges like hell)
So, i played with your git repository and got some results. Here is demo image and git repo of first commit:
But then i realized, that you don't need EmbossMaskFilter around letters, you need them around rectangles. And it can be done pretty much the same way. Here is how i done this:
Create new helper static Bitmap and Canvas for emboss background, just like mAlphaBitmap
On each prepareBitmaps() paint rects on helper bitmap. Solid color with no alpha.
Extract alpha from created bitmap like this Bitmap alpha = helperCanvas.extractAlpha();
Draw extracted alpha bitmap on helper with paint with emboss filter helperCanvas.drawBitmap(alpha, 0, 0, paintEmboss);
In onDraw print helperBitmap with some alpha before main Bitmap.
Here is screenshot without alpha(because it is much easier to see the shapes this way)
Here is git demo of this version: https://github.com/varren/AndroidEmbossMaskFilterForPng/blob/1d692d576e78bd434252a8a6c6ad2ee9f4c6dbd8/app/src/main/java/de/afarber/mytiles2/MyView.java
And here is essential part of code i changed in your project:
private static final EmbossMaskFilter filter =
new EmbossMaskFilter(new float[]{1, 1, 1}, 0.5f, 0.6f, 2f);
private static Canvas helperCanvas;
private static Paint paintEmboss;
public Canvas getHelperCanvas(int width, int height){
if (mAlphaBitmap == null) {
mAlphaBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
helperCanvas = new Canvas(mAlphaBitmap);
paintEmboss = new Paint();
paintEmboss.setColor(Color.BLACK);
}
return helperCanvas;
}
private void prepareBitmaps() {
mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
helperCanvas = getHelperCanvas(mBitmap.getWidth(),mBitmap.getHeight());
helperCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
paintEmboss.setMaskFilter(null);
paintEmboss.setAlpha(255);
for (SmallTile tile: mTiles) {
if (!tile.visible) continue;
helperCanvas.drawRect(tile.left,tile.top,tile.left + tile.width,
tile.top + tile.height,paintEmboss);
mCanvas.drawRect(tile.left, tile.top,tile.left + tile.width,
tile.top + tile.height, mPaintGrad);
tile.draw(mCanvas);
}
paintEmboss.setMaskFilter(filter);
Bitmap alpha = mAlphaBitmap.extractAlpha();
helperCanvas.drawBitmap(alpha, 0, 0, paintEmboss);
}
protected void onDraw(Canvas canvas) {
// ...
paintEmboss.setAlpha(255); //todo change alpha here
if(mAlphaBitmap!= null)canvas.drawBitmap(mAlphaBitmap, 0,0, paintEmboss);
if(mBitmap!= null)canvas.drawBitmap(mBitmap, 0, 0, mPaintGrad);
// ...
}
And the last 3-d step i made is to move everything from onDraw to prepareBitmaps() and preformance is fine now, but we have text destortion on resize. so here is source code for this step.
And here is kinda fine working final solution. Moving all paints with filters solved preformance issues, but i think there are still better options to implement this. As i said erlier i don't know is it what you need, but this code pretty much creates Emboss around Bitmap
PS: kinda cool effect when splitting and adding cells together
PS2: new EmbossMaskFilter(new float[] { 0f, 1f, 0.5f }, 0.8f, 3f, 3f); this will not look the same on diferent devices with diferent screen resolution
Here's a suggestion using a custom layout.
You'll need your own layout for the scrabble board. Since it's grid, this should be pretty easy to code.
The basic idea is to have a set of PNG shadow images, one for each type of combination of adjacent cells. In your layout onDraw(), draw the shadows first, then draw the tile in onLayout().
In onDraw(), iterate through your array of tiles placeholders. If you have a tile, then for each edge, inspect the adjacent cells. Depending on what's adjacent, choose the correct shadow image and draw it.
You can reduce the number of shadow images substantially by having a shadow image which is exactly the width of a tile and then specializing the corner area: one for 270 degrees, one for straight alignment, one for 90 degrees.
I don't know if using porter-duff can help since you still need to determine all these "edge" cases (no pun intended).
Related
I want to create a rounded graph that will display a range of values from my app. The values can be classified to 3 categories: low, mid, high - that are represented by 3 colors: blue, green and red (respectively).
Above this range, I want to show the actually measured values - in a form of a "thumb" over the relevant range part:
The location of the white thumb over the range arc may change, according to the measured values.
Currently, I'm able to draw the 3-colored range by drawing 3 arcs over the same center, inside the view's onDraw method:
width = (float) getWidth();
height = (float) getHeight();
float radius;
if (width > height) {
radius = height / 3;
} else {
radius = width / 3;
}
paint.setAntiAlias(true);
paint.setStrokeWidth(arcLineWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.STROKE);
center_x = width / 2;
center_y = height / 1.6f;
left = center_x - radius;
float top = center_y - radius;
right = center_x + radius;
float bottom = center_y + radius;
oval.set(left, top, right, bottom);
//blue arc
paint.setColor(colorLow);
canvas.drawArc(oval, 135, 55, false, paint);
//red arc
paint.setColor(colorHigh);
canvas.drawArc(oval, 350, 55, false, paint);
//green arc
paint.setColor(colorNormal);
canvas.drawArc(oval, 190, 160, false, paint);
And this is the result arc:
My question is, how do I:
Create a smooth gradient between those 3 colors (I tried using
SweepGradient but it didn't give me the correct result).
Create the overlay white thumb as shown in the picture, so that I'll be able to control where to display it.
Animate this white thumb over my range arc.
Note: the 3-colored range is static - so another solution can be to just take the drawable and paint the white thumb over it (and animate it), so I'm open to hear such a solution as well :)
I would use masks for your first two problems.
1. Create a smooth gradient
The very first step would be drawing two rectangles with a linear gradient. The first
rectangle contains the colors blue and green while the second rectangle contains green
and red as seen in the following picture. I marked the line where both rectangles touch each other
black to clarify that they are infact two different rectangles.
This can be achieved using the following code (excerpt):
// Both color gradients
private Shader shader1 = new LinearGradient(0, 400, 0, 500, Color.rgb(59, 242, 174), Color.rgb(101, 172, 242), Shader.TileMode.CLAMP);
private Shader shader2 = new LinearGradient(0, 400, 0, 500, Color.rgb(59, 242, 174), Color.rgb(255, 31, 101), Shader.TileMode.CLAMP);
private Paint paint = new Paint();
// ...
#Override
protected void onDraw(Canvas canvas) {
float width = 800;
float height = 800;
float radius = width / 3;
// Arc Image
Bitmap.Config conf = Bitmap.Config.ARGB_8888; // See other config types
Bitmap mImage = Bitmap.createBitmap(800, 800, conf); // This creates a mutable bitmap
Canvas imageCanvas = new Canvas(mImage);
// Draw both rectangles
paint.setShader(shader1);
imageCanvas.drawRect(0, 0, 400, 800, paint);
paint.setShader(shader2);
imageCanvas.drawRect(400, 0, 800, 800, paint);
// /Arc Image
// Draw the rectangle image
canvas.save();
canvas.drawBitmap(mImage, 0, 0, null);
canvas.restore();
}
As your goal is having a colored arc with rounded caps, we next need to define the area of
both rectangles that should be visible to the user. This means that most of both rectangles
will be masked away and thus not visible. Instead the only thing to remain is the arc area.
The result should look like this:
In order to achieve the needed behavior we define a mask that only reveals the arc area within
the rectangles. For this we make heavy use of the setXfermode method of Paint. As argument
we use different instances of a PorterDuffXfermode.
private Paint maskPaint;
private Paint imagePaint;
// ...
// To be called within all constructors
private void init() {
// I encourage you to research what this does in detail for a better understanding
maskPaint = new Paint();
maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
imagePaint = new Paint();
imagePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
}
#Override
protected void onDraw(Canvas canvas) {
// #step1
// Mask
Bitmap mMask = Bitmap.createBitmap(800, 800, conf);
Canvas maskCanvas = new Canvas(mMask);
paint.setColor(Color.WHITE);
paint.setShader(null);
paint.setStrokeWidth(70);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setAntiAlias(true);
final RectF oval = new RectF();
center_x = 400;
center_y = 400;
oval.set(center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius);
maskCanvas.drawArc(oval, 135, 270, false, paint);
// /Mask
canvas.save();
// This is new compared to step 1
canvas.drawBitmap(mMask, 0, 0, maskPaint);
canvas.drawBitmap(mImage, 0, 0, imagePaint); // Notice the imagePaint instead of null
canvas.restore();
}
2. Create the overlay white thumb
This solves your first problem. The second one can be achieved using masks again, though this
time we want to achieve something different. Before, we wanted to show only a specific area (the arc)
of the background image (being the two rectangles). This time we want to do the opposite:
We define a background image (the thumb) and mask away its inner content, so that only
the stroke seems to remain. Applied to the arc image the thumb overlays the colored arc with
a transparent content area.
So the first step would be drawing the thumb. We use an arc for this with the same radius as
the background arc but different angles, resulting in a much smaller arc. But becaus the
thumb should "surround" the background arc, its stroke width has to be bigger than the
background arc.
#Override
protected void onDraw(Canvas canvas) {
// #step1
// #step2
// Thumb Image
mImage = Bitmap.createBitmap(800, 800, conf);
imageCanvas = new Canvas(mImage);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(120);
final RectF oval2 = new RectF();
center_x = 400;
center_y = 400;
oval2.set(center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius);
imageCanvas.drawArc(oval2, 270, 45, false, paint);
// /Thumb Image
canvas.save();
canvas.drawBitmap(RotateBitmap(mImage, 90f), 0, 0, null);
canvas.restore();
}
public static Bitmap RotateBitmap(Bitmap source, float angle)
{
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
}
The result of the code is shown below.
So now that we have a thumb that is overlaying the background arc, we need to define the mask
that removes the inner part of the thumb, so that the background arc becomes visible again.
To achieve this we basically use the same parameters as before to create another arc, but
this time the stroke width has to be identical to the width used for the background arc as
this marks the area we want to remove inside the thumb.
Using the following code, the resulting image is shown in picture 4.
#Override
protected void onDraw(Canvas canvas) {
// #step1
// #step2
// Thumb Image
// ...
// /Thumb Image
// Thumb Mask
mMask = Bitmap.createBitmap(800, 800, conf);
maskCanvas = new Canvas(mMask);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(70);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
final RectF oval3 = new RectF();
center_x = 400;
center_y = 400;
oval3.set(center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius);
maskCanvas.drawBitmap(mImage, 0, 0, null);
maskCanvas.drawArc(oval3, 270, 45, false, paint);
// /Thumb Mask
canvas.save();
canvas.drawBitmap(RotateBitmap(mMask, 90f), 0, 0, null); // Notice mImage changed to mMask
canvas.restore();
}
3. Animate the white thumb
The last part of your question would be animating the movement of the arc. I have no solid
solution for this, but maybe can guide you in a useful direction. I would try the following:
First define the thumb as a ImageView that is part of your whole arc graph. When changing
the selected values of your graph, you rotate the thumb image around the center of the background
arc. Because we want to animate the movement, just setting the rotation of the thumb image would
not be adequate. Instead we use a RotateAnimation kind of like so:
final RotateAnimation animRotate = new RotateAnimation(0.0f, -90.0f, // You have to replace these values with your calculated angles
RotateAnimation.RELATIVE_TO_SELF, // This may be a tricky part. You probably have to change this to RELATIVE_TO_PARENT
0.5f, // x pivot
RotateAnimation.RELATIVE_TO_SELF,
0.5f); // y pivot
animRotate.setDuration(1500);
animRotate.setFillAfter(true);
animSet.addAnimation(animRotate);
thumbView.startAnimation(animSet);
This is far from final I guess, but it very well may aid you in your search for the needed
solution. It is very important that your pivot values have to refer to the center of your
background arc as this is the point your thumb image should rotate around.
I have tested my (full) code with API Level 16 and 22, 23, so I hope that this answer at least
gives you new ideas on how to solve your problems.
Please note that allocation operations within the onDraw method are a bad idea and should
be avoided. For simplicity I failed to follow this advise. Also the code is to be used as
a guide in the right direction and not to be simply copy & pasted, because it makes heavy
use of magic numbers and generally does not follow good coding standards.
I would change a bit of the way you draw your view, by looking on the original design, instead of drawing 3 caps I would draw just 1 line, that way the SweepGradient will work.
This migth be a bit tricky, you have 2 options:
create a Path with 4 arcs
draw 2 arcs- one is the big white (filled with white so you still want to use Paint.Style.STROKE) and another on top of that make it fill transparent, you can achieve it with PorterDuff xfermode, it probably take you couple of tries until you get that without clearing the green circle too.
I imagine you want to animate thumb position, so just use simple Animation that invalidate the view and draw the thumb view position accordingly.
Hopes this helps
Create a gradient than follow a path is not so simple.
So I can suggest you to use some libraries than already did it.
Include the library:
dependencies {
...
compile 'com.github.paroca72:sc-gauges:3.0.7'
}
Create the gauge in XML:
<com.sccomponents.gauges.library.ScArcGauge
android:id="#+id/gauge"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
Your code:
ScArcGauge gauge = this.findViewById(R.id.gauge);
gauge.setAngleSweep(270);
gauge.setAngleStart(135);
gauge.setHighValue(90);
int lineWidth = 50;
ScCopier baseLine = gauge.getBase();
baseLine.setWidths(lineWidth);
baseLine.setColors(Color.parseColor("#dddddd"));
baseLine.getPainter().setStrokeCap(Paint.Cap.ROUND);
ScCopier progressLine = gauge.getProgress();
progressLine.setWidths(lineWidth);
progressLine.setColors(
Color.parseColor("#65AAF2"),
Color.parseColor("#3EF2AD"),
Color.parseColor("#FF2465")
);
progressLine.getPainter().setStrokeCap(Paint.Cap.ROUND);
Your result:
You can find something more complex on this site:
ScComponents
So I'm fundamentally not understanding something about PorterDuff and its various modes. I have two images I am trying to combine, one being an aerial image and the other being an alpha mask meant to be overlayed on the aerial so only certain sections show through. I have a system currently where I can correctly overlay the mask onto the other image where only it comes through, but the problem is that in this case I have to have a background color and I need it to be transparent. (similar to the problem here Efficient Bitmap masking with black and white alpha mask in Android)
Bitmap thumbnail = Bitmap.createBitmap(mThumbnailSize, mThumbnailSize, Config.ARGB_8888);
Canvas canvas = new Canvas(thumbnail);
Paint paint = new Paint();
paint.setFilterBitmap(true);
canvas.drawColor(Color.TRANSPARENT);
canvas.drawBitmap(aerial, transformation, paint);
paint = new Paint();
paint.setFilterBitmap(true);
paint.setColorFilter(new PorterDuffColorFilter(Color.WHITE,PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(overlay, transformation, paint);
I feel like I've tried every combination of ColorFilter, XferMode, even drawing the images in the reverse order but nothing seems to work. I feel like I am just completely misunderstanding how the PorterDuff modes work (basing a lot of my information on http://ssp.impulsetrain.com/porterduff.html).
If anyone has any insight on how to accomplish this, insight on what PorterDuff Mode I should use, whether I should be using an ColorFilter or XferMode, anything at all I would really appreciate it.
Thanks in advance
May be I'm late but the solution is to make the background transparent so you get the alpha mask that you already know how to use. Here is a CSharp helper method to convert B/W image to the alpha mask.
private static Bitmap ConvertToAlphaMask(Bitmap mask)
{
float[] src = {
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0.3f, 0.59f, 0.11f, 0, 0,
};
var colorMatrix = new ColorMatrix(src);
var filter = new ColorMatrixColorFilter(colorMatrix);
var maskPaint = new Paint();
maskPaint.SetColorFilter(filter);
var alphaMask = Bitmap.CreateBitmap(mask.Width, mask.Height, Bitmap.Config.Argb8888);
var canvas = new Canvas(alphaMask);
canvas.DrawBitmap(mask, 0, 0, maskPaint);
return alphaMask;
}
I tried to create a Maze with a moving ball and a hole using the Accelerometer Sensor. With the following code, the ball falls into the hole, but the performance is really bad, I set the Accelerometer Frequency to the fastest, but it's everything other than smooth. I made a second canvas, because so I could make a hole.
public RenderView(Context context, int width, int height) {
super(context);
playGround = new Rect(40, 40, width - 40, height - 40);
holes.addElement(new PointF(500f, 500f));
// Set background
this.setBackgroundResource(R.drawable.bottom);
// Set bitmap
woodGround= wood.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas();
bitmapCanvas.setBitmap(woodGround);
// Set eraser paint properties
eraserPaint.setAlpha(0);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
eraserPaint.setAntiAlias(true);
}
protected void onDraw(Canvas canvas) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG
| Paint.FILTER_BITMAP_FLAG);
paint.setStyle(Style.FILL_AND_STROKE);
paint.setAntiAlias(true);
if (ballInHole)
canvas.drawBitmap(ball, b.x, b.y, paint);
bitmapCanvas.drawBitmap(wall, 0, 0, paint);
bitmapCanvas.drawBitmap(wood, playGround, playGround, paint);
canvas.drawBitmap(bitmap, 0, 0, paint);
for (PointF h : holes) {
bitmapCanvas.drawCircle(h.x + radius, h.y + radius, radius,
eraserPaint);
}
if (!ballInHole)
canvas.drawBitmap(ball, b.x, b.y, paint);
invalidate();
}
It's solved very ugly, because I just draw the ball bellow the other bitmaps when he falls into a hole. Is there another way to do it?
The performance is also really bad, i set the Accelerometer-Sensor-Delay to the fastest, but the ball doesn't run smooth. When I remove the line canvas.drawBitmap(bitmap, 0, 0, paint);, then the ball is smoother, but then the wooden background is away.
The problem here is that you're doing A LOT of drawings all the time and that's take time to draw and the performance gets very low.
here a few tips on how you should approach it.
You probably better have one view with the static stuff (the background image and the holes) and on your layout have a second view on top of it just drawing the ball.
on the background image, do not call invalidate. That way you will draw the background just once.
and the top image (the ball only) you can invalidate, so it can redraw on the new position.
I'm not sure on this last part: but you may need to call invalidate(rect); passing the area where the ball was on the previous time, to make the background only re-draw that small area (instead of the whole screen)
happy coding.
So I tried the code from here: Creating an ImageView with a mask. I'm using the following images as original and mask:
However, the result I get is this:
Note that the window background is not black, but holo light (which on the galaxy nexus looks like a very pale gray, not completely white). The second image is the result I get when an item is selected on a list view.
If instead I create a new Bitmap using the same algorithm and then pass it to the image view instead of overriding onDraw(), it draws correctly:
Canvas canvas = new Canvas();
Bitmap mainImage = //get original image
Bitmap maskImage = //get mask image
Bitmap result = Bitmap.createBitmap(mainImage.getWidth(), mainImage.getHeight(), Bitmap.Config.ARGB_8888);
canvas.setBitmap(result);
Paint paint = new Paint();
paint.setFilterBitmap(false);
canvas.drawBitmap(mainImage, 0, 0, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(maskImage, 0, 0, paint);
paint.setXfermode(null);
imageView.setImageBitmap(result);
I get the expected result:
Note the fade is correctly applied. This is more evident when a selection is made.
So what's going on on ImageView's onDraw method to create this black backdrop instead of letting the window background show through? What's interesting is that if the original image itself has some transparency, that transparency is respected, for example:
I can't figure it out by myself. I'd rather be able to do it on onDraw instead of pre-creating the bitmap because it only works for bitmaps as source and mask. I want to be able to do it with other drawables like gradients and solid colours but on those cases the width and height are not set.
I have found the perfect combination for creating masking without black border after researching through all the stackoverflow posts. It suits my purpose quite well.
Currently I'm creating a draggable view using one normal image and a masking image (a png with transparency), so I'll need to override the onDraw function.
private Bitmap mImage = ...;
private Bitmap mMask = ...; // png mask with transparency
private int mPosX = 0;
private int mPosY = 0;
private final Paint maskPaint;
private final Paint imagePaint;
public CustomView (final Context context) {
maskPaint = new Paint();
maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
imagePaint = new Paint();
imagePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
}
/* TODO
if you have more constructors, make sure you initialize maskPaint and imagePaint
Declaring these as final means that all your constructors have to initialize them.
Failure to do so = your code won't compile.
*/
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.drawBitmap(mMask, 0, 0, maskPaint);
canvas.drawBitmap(mImage, mPosX, mPosY, imagePaint);
canvas.restore();
}
Answering my own question. The Xfermode was working as intended. The paint was making the resulting are of the canvas transparent (which was the canvas used by the window activity). Since the canvas itself was being set transparent, the window was showing what was behind it: the black background.
To do it properly, indeed a new Bitmap has to be created to hold the result of the alpha mask. I updated the code to take into account drawables of all types.
In this Code Apply:
mask_over = BitmapFactory.decodeResource(
getResources(), mask_over1[0]);
icon = Bitmap.createScaledBitmap(icon, screenwidth, screenwidth, false);
mask_over = Bitmap.createScaledBitmap(mask_over, screenwidth, screenwidth, false);
back_img=createBitmap_ScriptIntrinsicBlur(Bitmap.createScaledBitmap(cropview.croppedImage, screenwidth, screenwidth, false),25.0f);
LinearLayout.LayoutParams layoutParams111 = new LinearLayout.LayoutParams(screenwidth, screenwidth);
I am trying to create an Oval Bitmap and I need to get a feather effect around the margins,
Does anyone have any idea How I can achieve this ?
Thanks.
You could consider the 'feather effect' as a gradial gradient, with the alpha fading from 100% to 0%.
Android offers the RadialGradient class for this purpose. You'll want to use the constructor where you can specify the control points for the radient, as you'll want the fading to start near the edge, not in the middle.
The one problem with Android's RadialGradient class is that it only supports perfect circles, not for ovals. To compensate for this, we'll just draw a perfect circle and scale afterwards.
Example code:
private Bitmap makeFeatheredOval(int width, int height) {
// Determine largest dimension, use for rectangle.
int size = Math.max( width, height);
RadialGradient gradient = new RadialGradient(size / 2, size / 2, size / 2,
new int[] {0xFFFFFFFF, 0xFFFFFFFF, 0x00FFFFFF},
new float[] {0.0f, 0.8f, 1.0f},
android.graphics.Shader.TileMode.CLAMP);
Paint paint = new Paint();
paint.setShader(gradient);
Bitmap bitmap = Bitmap.createBitmap(size, size, Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawCircle(size / 2, size / 2, size / 2, paint);
// Scale the bitmap, creating an oval
bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
return bitmap;
}
Example image (it's the oval "moon" in the upper left corner):
Bonus points for everyone who recognizes that backdrop image.