I have a SurfaceView where I'm setting a background color and an image like so:
BitmapDrawable tiledBackground = new BitmapDrawable(BitmapFactory.decodeResource(getResources(), R.drawable.background));
tiledBackground.setTileModeX(Shader.TileMode.REPEAT);
tiledBackground.setColorFilter(0xaacceeff, PorterDuff.Mode.DST_OVER);
this.setBackgroundDrawable(tiledBackground);
I also have an animation thread where I'm drawing an image (successively adjusting its x coordinate so that it appears to move to the left). The background image is a transparent PNG and so some parts of it are transparent. It appears that the image I'm drawing from the thread appears below the background drawable on the SurfaceView. How can I have it appear on top of the background? I'm drawing the image like so:
private void doDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(missile, x, getHeight() - 95, paint);
canvas.restore();
}
missile and paint are initialized in the constructor of the thread to:
missile = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.missile));
paint = new Paint();
Each call to doDraw should draw everything you want to be displayed, including the background.
// Add to initializer
tiledBackground = new BitmapDrawable(BitmapFactory.decodeResource(getResources(), R.drawable.background));
tiledBackground.setTileModeX(Shader.TileMode.REPEAT);
tiledBackground.setColorFilter(0xaacceeff, PorterDuff.Mode.DST_OVER);
private void doDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
// Create a rectangle (just holds top/bottom/left/right info)
Rect drawRect = new Rect();
// Populate the rectangle that we just created with the drawing area of the canvas.
canvas.getClipBounds(drawRect);
// Make the height of the background drawing area equal to the height of the background bitmap
drawRect.bottom = drawRect.top + tiledBackground.getBitmap().getHeight();
// Set the drawing area for the background.
tiledBackground.setBounds(drawRect);
tiledBackground.draw(canvas);
canvas.drawBitmap(missile, x, getHeight() - 95, paint);
canvas.restore();
}
Related
I am trying to programmatically create a gray Rect and black Rect with BlurMaskFilter layer (drop shadow effect) by overriding onDraw in a custom View. I am able to get it to draw on screen without any issues, but when I try to draw the view to a bitmap the results are different. In drawing to bitmap, it appears BlueMaskFilter is applied to the View rather than the specific layer of the black Rect.
What am I missing to make the drawn bitmap same as the output drawn on screen?
CustomView's onDraw override:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw black box (shadow) first
RectF rectShadow = new RectF();
rectShadow.set(10, 10, 120, 120);
Paint shadowPaint = new Paint();
shadowPaint.setMaskFilter(new BlurMaskFilter(5, Blur.NORMAL));
shadowPaint.setStyle(Paint.Style.FILL);
shadowPaint.setColor(Color.DKGRAY);
shadowPaint.setAntiAlias(true);
canvas.drawRect(rectShadow, shadowPaint);
setLayerType(View.LAYER_TYPE_SOFTWARE, shadowPaint);
// draw grey box
RectF rectGray = new RectF();
rectGray.set(0, 0, 100, 100);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.parseColor("#656565"));
paint.setAntiAlias(true);
canvas.drawRoundRect(rectGray, paint);
}
Function to save view to bitmap:
public void saveViewToBitmap( View customView int outWidth, int outHeight ) {
customView.setLayoutParams(new ViewGroup.LayoutParams(outWidth, outHeight));
customView.measure(
ViewGroup.MeasureSpec.makeMeasureSpec(customView.getLayoutParams().width,
ViewGroup.MeasureSpec.EXACTLY),
ViewGroup.MeasureSpec.makeMeasureSpec(customView.getLayoutParams().height,
ViewGroup.MeasureSpec.EXACTLY));
customView.layout(0, 0,
customView.getMeasuredWidth(), customView.getMeasuredHeight());
customView.requestLayout();
Bitmap outputBitmap = Bitmap.createBitmap(outWidth, outHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(outputBitmap);
customView.draw(canvas);
return outputBitmap;
}
Note in the following images the screen image has crisp edges for the gray rect, whereas the drawn bitmap has the top and left edges of the gray rect blurred demonstrating the problem.
Image on screen:http://i.stack.imgur.com/0Hl0t.png
Image saved from bitmap created by saveViewToBitmap: http://i.stack.imgur.com/xNzi5.png
Thanks!
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 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?
I am drawing a blue circle on the canvas at x,y.
mPaint.setColor(0xFF0000FF);
mCanvas.drawCircle(x,y, radius, mPaint);
Now I want to redraw at the same place with red color.
mPaint.setColor(0xFFFF0000);
mCanvas.drawCircle(x,y, radius, mPaint);
This never succeeds. The circle shown is always blue. What can I do to achieve the red circle in the second drawCircle
In an app I worked on awhile back, I have:
ImageView playLayout;
Display display;
Canvas canvas;
Bitmap bitmap;
Paint paint;
I then have:
display = getWindowManager().getDefaultDisplay();
screenHeight = playLayout.getHeight();
screenWidth = playLayout.getWidth();
playLayout = (ImageView)findViewById(R.id.playLayout); //playLayout in xml
bitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
paint = new Paint();
Then in a thread to prevent lag:
paint.setARGB(255, red, green, blue);
canvas.drawCircle(x, y, circleRadius, paint);
playLayout.post(new Runnable(){
#Override
public void run(){
playLayout.setImageBitmap(bitmap);
}
});
This works for me. The circle does not actually draw, until I call playLayout.setImageBitmap.
Let me know if I need me to add more info or anything, and I'll try to help.
I am drawing a grid and I want it to be larger than the screen size so that a user can drag the screen left/right/up/down to get to the rest of the grid.
What is the best way to do that? I've tried drawing a larger bitmap to the canvas, but didn't get anywhere.
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
Bitmap testBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888);
canvas.drawBitmap(testBitmap, 0, 0, paint);
canvas.drawPaint(paint);
//other grid drawing code here
}
I used the View's scrollBy() method in the onTouch method of the Activity. It worked.
You can probably use the canvas.translate(x, y) method. That will adjust the origin for your canvas in relation to the screen. So canvas.translate(10, 10) will make you canvas origin (0, 0) be at the point of (10, 10) on the screen. Use a negative translation to scroll the screen.