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);
Related
I am trying to create an animated wallpaper.
Basically my wallpaper has some background picture, and several foreground pictures.
I want to swap between the foreground pictures with different backgrounds.
I have a Draw method that looks like this:
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
canvas.drawColor(0);
canvas.drawBitmap(background,0,0,null);
canvas.drawBitmap(foreground,x,y,null);
canvas.save();
}
My foreground picture is a png that has lots of transparent space.
I loaded in my foreground picture like this:
foreground=decodeSampledBitmapFromResource(getResources(), R.drawable.c1, 440,320);
Where the decodeSampledBitmapFromResource method was taken from the tutorial here: https://developer.android.com/training/displaying-bitmaps/load-bitmap.html
My problem is that the foreground will display white color where it should display transparent color and thus covering the background with an ugly white rectangle.
I'm wondering if anyone have any advice for me to make this transparent.
I've tried setting options.inPreferredConfig = Bitmap.Config.ARGB_8888; for the BitmapFactory, but it didn't help.
Paint paint = new Paint();
paint.setAlpha(70); // you can change number to change the transparency level
Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.someDrawable);
canvas.drawBitmap(image, x, y, paint);
You need to use Paint and set the mix mode to SRC_OVER
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
canvas.drawColor(0);
canvas.drawBitmap(background,srcRect,dstRect,paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
canvas.drawBitmap(foreground,srcRect2,dstRect2,paint);
canvas.save();
}
See PorterDuff.Mode for the various mixing options (there are many...)
Use the SRC_ATOP this will put your transparent image over a background image.
fun overlay(bmp1: Bitmap, bmp2: Bitmap): Bitmap {
val bmOverlay = Bitmap.createBitmap(bmp1.width, bmp1.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bmOverlay)
val paint = Paint()
canvas.drawBitmap(bmp1, 0f, 0f, paint)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)
canvas.drawBitmap(bmp2, 0f, 0f, paint)
return bmOverlay
}
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).
I need to overlay two images in live wallpaper. The overlay images is the jpg which needs to be set to "additive" overlay. it adds the pixel value rather than calculating the transparency. how can i achieve this in android ?
You can make use of Android's Bitmap and Drawable classes mixed with Canvas, and try something like in this snippet:
public static Drawable mergeImage(Drawable orig, Drawable over, int left, int top) {
Bitmap original = ((BitmapDrawable)orig).getBitmap();
Bitmap overlay = ((BitmapDrawable)over).getBitmap();
Bitmap result = Bitmap.createBitmap(original.getWidth(), original.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setAntiAlias(true);
canvas.drawBitmap(original, 0, 0, paint);
canvas.drawBitmap(overlay, left, top, paint);
return new BitmapDrawable(result);
}
I've coded a photo image gridview overlayered with "online status" using the above lines. Hope that it works for you too.
A more general approach may be to create a PorterDuffXfermode with your wanted PorterDuffMode and then set it on the Paint object that you use with your canvas, as referenced in mthama's answer but substituting some lines. This allows you to use other Porter-Duff modes as wanted/needed.
Paint paint = new Paint();
paint.setAntiAlias(true);
canvas.drawBitmap(original, 0, 0, paint);
paint.setXferMode(new PorterDuffXferMode(PorterDuff.Mode.OVERLAY));
canvas.drawBitmap(overlay, left, top, paint);
Mind you, I haven't tried this, so go with mthama's answer. :)
My code is below. It is exactly the same code as found in the solution for this question: Make certain area of bitmap transparent on touch
And as many others I am having the same problem with this code: the circle comes out black.
I am using a PNG file as my overlay, and this file does not have any transparent areas. But as soon as I add an arbitrary transparent area to the PNG in the Photoshop, the code starts working and the circle is displayed as transparent.
Apparently, there is something with the image and how its transparency is set, but I do not know what. I need to use a PNG without any transparent areas as my overlay.
any advice?
EDIT: good code must not depend on whether the overlay image has transparency or not, I am looking for a way to handle any kind of image as my overlay, be it JPG, PNG, or something else.
EDIT 2: if I use Config.ARGB_4444 when copying my bitmap, alpha channel gets created but this format reduces the image quality. There is API Bitmap.setHasAlpha() for API level 11 and above but I am using level 10 so far.
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new TouchView(this));
}
class TouchView extends View {
Bitmap bgr;
Bitmap overlayDefault;
Bitmap overlay;
Paint pTouch;
int X = 100;
int Y = 100;
Canvas c2;
public TouchView(Context context) {
super(context);
bgr = BitmapFactory.decodeResource(
getResources(),
R.drawable.background);
overlay = BitmapFactory.decodeResource(
getResources(),
R.drawable.foreground)
.copy(Config.ARGB_8888, true);
c2 = new Canvas(overlay);
pTouch = new Paint(Paint.ANTI_ALIAS_FLAG);
pTouch.setXfermode(
new PorterDuffXfermode(Mode.SRC_OUT));
pTouch.setColor(Color.TRANSPARENT);
}
#Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
//draw background
canvas.drawBitmap(bgr, 0, 0, null);
//copy the default overlay
// into temporary overlay and punch a hole in it
//c2.drawBitmap(overlayDefault, 0, 0, null);
c2.drawCircle(X, Y, 80, pTouch);
//draw the overlay over the background
canvas.drawBitmap(overlay, 0, 0, null);
}
}
}
From http://blog.uncommons.org/2011/01/12/adjusting-the-opacity-of-an-android-bitmap/
**
* #param bitmap The source bitmap.
* #param opacity a value between 0 (completely transparent) and 255 (completely opaque).
* #return The opacity-adjusted bitmap. If the source bitmap is mutable it will be
* adjusted and returned, otherwise a new bitmap is created.
*/
private Bitmap adjustOpacity(Bitmap bitmap, int opacity)
{
Bitmap mutableBitmap = bitmap.isMutable()
? bitmap
: bitmap.copy(Bitmap.Config.ARGB_8888, true);
Canvas canvas = new Canvas(mutableBitmap);
int colour = (opacity & 0xFF) << 24;
canvas.drawColor(colour, PorterDuff.Mode.DST_IN);
return mutableBitmap;
}
Note that the Bitmap.Config documentation for ARGB_4444 says:
This field was deprecated in API level 13. Because of the poor quality
of this configuration, it is advised to use ARGB_8888 instead.
I want to set a background of a View with a tiled bitmap, but the tiling needs to be anchored to the bottom-left, instead of the top-left corner (the default). For example, if the tiles are the smiley faces below, I want it to be tiled like:
Using xml drawables I could achieve either tiling (using tileMode="repeat") or bottom positioning (using gravity="bottom"), but combining both is not possible, even the documentation says so:
android:tileMode
Keyword. Defines the tile mode. When the tile mode is
enabled, the bitmap is repeated. Gravity is ignored when the tile mode
is enabled.
Although it's not internally supported, is there any way to achieve this, perhaps using custom views?
Another way would be to extend BitmapDrawable and override the paint() method:
In this method we avoid creating a new bitmap having the size of the view.
class MyBitmapDrawable extends BitmapDrawable {
private Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
private boolean mRebuildShader = true;
private Matrix mMatrix = new Matrix();
#Override
public void draw(Canvas canvas) {
Bitmap bitmap = getBitmap();
if (bitmap == null) {
return;
}
if (mRebuildShader) {
mPaint.setShader(new BitmapShader(bitmap, TileMode.REPEAT, TileMode.REPEAT));
mRebuildShader = false;
}
// Translate down by the remainder
mMatrix.setTranslate(0, getBounds().bottom % getIntrinsicHeight());
canvas.save();
canvas.setMatrix(mMatrix);
canvas.drawRect(getBounds(), mPaint);
canvas.restore();
}
}
It can be set to the view like this:
view.setBackgroundDrawable(new MyBitmapDrawable(getResources().getDrawable(R.drawable.smiley).getBitmap()));
Just a thought, and it's pretty roundabout, but could you flip your image vertically, and then apply a transform to your background to flip that vertically as well?
Using a custom view might involve handling all the drawing yourself, not just the background image.
Instead, I propose to set the view's background programmatically as shown:
// This drawable refers to an image directly and NOT an XML
BitmapDrawable smiley = (BitmapDrawable) getResources().getDrawable(R.drawable.smiley);
// Create a new bitmap with the size of the view
Bitmap bgBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bgBitmap);
// Translate down by the remainder
Matrix matrix = new Matrix();
matrix.setTranslate(0, view.getHeight() % smiley.getIntrinsicHeight());
canvas.setMatrix(matrix);
// Tile the smileys
Paint paint = new Paint();
paint.setShader(new BitmapShader(smiley.getBitmap(), TileMode.REPEAT, TileMode.REPEAT));
canvas.drawPaint(paint);
view.setBackgroundDrawable(new BitmapDrawable(bgBitmap));
Points to consider:
I'm not sure if view.getWidth() & view.getHeight() are the correct
methods to get the dimensions.
What if smiley size is bigger than the view?