Android Fresco: drawing different kind of image shapes - android

Fresco has built-in support for circular images and rounded corner, but what about other shapes such as diamond or parallelogram, etc?
It's simple to do with the standard ImageView via custom drawable that uses BitmapShader. For example, the following custom Drawable receives the image Bitmap and a slope height to make a an ImageView look like this picture:
public class MaskDrawable extends Drawable {
private Paint mPaint;
private Path mPath;
private int mSlopeHeight;
public MaskDrawable(Bitmap bitmap, int slopeHeight) {
BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setShader(shader);
mSlopeHeight = slopeHeight;
mPath = new Path();
}
#Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
mPath.moveTo(0, 0);
mPath.lineTo(0, bounds.bottom);
mPath.lineTo(bounds.right, bounds.bottom - mSlopeHeight);
mPath.lineTo(bounds.right, 0);
canvas.drawPath(mPath, mPaint);
}
To do that with Fresco, I need the Bitmap of the image, but I'm not sure how to do that. I read that I can get the Bitmap directly from the ImagePipeline, but that are many gotchas that comes with it. In one case, the returned Bitmap is short lived and shouldn't be used to draw on the screen where in the other case I get a CloseableReference which I need to release at some point which isn't clear to me. What I have seen on the net so far is code similar to this for getting the Bitmap:
ImagePipeline imagePipeline = Fresco.getImagePipeline();
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(uri)
.setRequestPriority(Priority.HIGH)
.setLowestPermittedRequestLevel(ImageRequest.RequestLevel.FULL_FETCH)
.build();
DataSource<CloseableReference<CloseableBitmap>> dataSource = imagePipeline.fetchDecodedImage(imageRequest, getContext());
DataSubscriber<CloseableReference<CloseableBitmap>> dataSubscriber =
new BaseDataSubscriber<CloseableReference<CloseableBitmap>>() {
#Override
protected void onNewResultImpl(DataSource<CloseableReference<CloseableBitmap>> dataSource) {
mBitmapRef = dataSource.getResult();
// Get the bitmap here and use it in my custom drawable?
}
#Override
protected void onFailureImpl(DataSource<CloseableReference<CloseableBitmap>> dataSource) {
}
};
dataSource.subscribe(dataSubscriber, UiThreadImmediateExecutorService.getInstance());
I haven't tried that yet and was wondering if somebody can provide a working solution instead of the bits and bytes I've gathered so far from different places. It has to be done right or else I can easily leak memory, which beats the whole idea of using Fresco from the first place.

You don't need to and also not recommend to use imagepipeline as you're dealing with view.
One way is to manage those bitmap in postprocessor. You need to override the process method, use the same BitmapShader, paint, canvas implementation, use PlatformBitmapFactory createBitmap to create purgeable bitmap CloseableReference, and finally close the reference when you're done with the bitmap.
See more in
http://frescolib.org/docs/modifying-image.html
EDIT
Below is the final implementation I came up with after getting help from Jie Wang. The following code snippet places the image in the shape I presented in the question.
mSimpleDraweeView = (SimpleDraweeView) findViewById(R.id.shaped_picture);
final int slopeHeight = 100;
Postprocessor maskProcessor = new BasePostprocessor() {
#Override
public CloseableReference<Bitmap> process(Bitmap sourceBitmap, PlatformBitmapFactory bitmapFactory) {
// Get the size of the downloaded bitmap
final int width = sourceBitmap.getWidth();
final int height = sourceBitmap.getHeight();
// Create a new bitmap and use it to draw the shape that we want.
CloseableReference<Bitmap> bitmapRef = bitmapFactory.createBitmap(width, height);
try {
Bitmap destBitmap = bitmapRef.get();
// Create canvas using the new bitmap we created earlier
Canvas canvas = new Canvas(destBitmap);
// Set up the Paint we will use for filling in the shape
// BitmapShader will fill the shape with the downloaded bitmap
BitmapShader shader = new BitmapShader(sourceBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(shader);
// Set up the actual shape. Modify this part with any shape you want to have.
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(0, height);
path.lineTo(width, height - slopeHeight);
path.lineTo(width, 0);
// Draw the shape and fill it with the paint
canvas.drawPath(path, paint);
return CloseableReference.cloneOrNull(bitmapRef);
}
finally {
CloseableReference.closeSafely(bitmapRef);
}
}
};
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setPostprocessor(maskProcessor)
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);

Related

How to invert mask?

I am trying to invert image mask bitmap using below code
static final PorterDuffXfermode eraseMode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
public void invertSelection() {
Bitmap inverted = Bitmap.createBitmap(imageBitmap.getWidth(), imageBitmap.getHeight(), Bitmap.Config.ARGB_8888);
if (!annotationBitmap.sameAs(inverted)) {
Canvas canvas = new Canvas(inverted);
paint.setColor(Color.RED);
canvas.drawPaint(paint);
paint.setXfermode(eraseMode);
canvas.drawBitmap(annotationBitmap, 0,0,paint);
annotationBitmap = inverted;
undoStack.push(annotationBitmap.copy(annotationBitmap.getConfig(), true));
invalidate();
}
}
after calling this function I am no longer able to draw on annotationBitmap.
What am I doing wrong here???
I think you should read this beautiful article at medium
and i am not sure but i think if you changePorterDuff.Mode.CLEAR to PorterDuff.Mode.DST_OUT
your problem will solve

Android: Change size of canvas / drawable in custom drawable

I'm trying to implement a custom drawable which should have the shape of a speechbubble. Therefore I use two paths, one draws the rect and the other should draw the triangle for the bubble.
My class looks like the following:
public class SpeechBubbleView extends Drawable {
private Paint mBubblePaint;
private Paint mBubblePaint2;
private Path mRectPath;
private Path mBubblePath;
public SpeechBubbleView() { }
public void initPaint() {
mBubblePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBubblePaint.setStyle(Paint.Style.FILL);
mBubblePaint.setColor(Color.GREEN);
mBubblePaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
mBubblePaint2.setStyle(Paint.Style.FILL);
mBubblePaint2.setColor(Color.RED);
int width = getBounds().width();
int height = getBounds().height();
mRectPath = new Path();
mRectPath.addRoundRect(new RectF(0, 0, width, height), 8, 8, Path.Direction.CW);
mRectPath.close();
mBubblePath = new Path();
mBubblePath.moveTo(50, height);
mBubblePath.lineTo(100, height + 50);
mBubblePath.lineTo(150, height);
mBubblePath.close();
}
#Override
public void draw(Canvas canvas) {
if(mRectPath == null && mPathValues == null) {
initPaint();
}
canvas.drawPath(mRectPath, mBubblePaint);
canvas.drawPath(mBubblePath, mBubblePaint2);
}
#Override
public void onBoundsChange(Rect bounds) {
Rect customBound = new Rect(0, 0, bounds.width(), bounds.height() + 50);
super.onBoundsChange(customBound);
}
The problem now is, that I take the width and height from the drawable to draw the rect of the speechbubble. The full space of the canvas is taken and there is no more room for the triangle to display below the rect.
My question now is: Is it possible to change the size of canvas or the drawable, so that I am able to display the small triangle below the rect?
I already tried the method onBoundsChange, but it takes no effect. In the draw-method the size is still the same.
If possible, it would be nice to change the size directly in the custom drawable class, shown above, because I do not have the size of the view, when I call it. Also I cannot make the size of the rect smaller, because in the drawable there is content and if the rect is smaller, some of the content will be outside of the drawable. I use a drawable, so that I can simple call setBackgroundDrawable of my layout or TextView and it matches always the content size.
If anyone of you got an idea on how to do the size change, this would be very great. Thank you :D

Android Mask bitmap on canvas gen a black space

I have a mask bitmap with a half is red color and ones is transparent like this
https://www.dropbox.com/s/931ixef6myzusi0/s_2.png
I want to use mask bitmap to draw content on canvas only visible in red area, code like this:
Paint paint = new Paint();
public void draw(Canvas canvas) {
// draw content here
...
//and mask bitmap here
paint.setXfermode(new PorterDuffXfermode(android.graphics.PorterDuff.Mode.DST_IN));
canvas.drawBitmap(maskBitmap, 0, 0, paint);
}
The result as my expecting (content only visible in red area, BUT THE TRANSPARENT AREA BECOME BLACK IS PROBLEM!)
this image result :https://www.dropbox.com/s/mqj48992wllfkiq/s_2%20copy.png
Anyone help me???
Here is a solution which helped me to implement masking:
public void draw(Canvas canvas) {
Bitmap original = BitmapFactory.decodeResource(getContext().getResources(),R.drawable.original_image);
Bitmap mask = BitmapFactory.decodeResource(getContext().getResources(),R.drawable.mask_image);
//You can change original image here and draw anything you want to be masked on it.
Bitmap result = Bitmap.createBitmap(mask.getWidth(), mask.getHeight(), Config.ARGB_8888);
Canvas tempCanvas = new Canvas(result);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
tempCanvas.drawBitmap(original, 0, 0, null);
tempCanvas.drawBitmap(mask, 0, 0, paint);
paint.setXfermode(null);
//Draw result after performing masking
canvas.drawBitmap(result, 0, 0, new Paint());
}
The mask should be a white image with transparency.
It will work like this:
+ =
I encountered the same problem in my custom view and instead of decoding the bitmap from a resource, I had created the original bitmap and the masking bitmap from the scratch via canvas.draw*() methods (since both the original and mask are basic shapes). I was getting the blank opaque space instead of a transparent one. I fixed it by setting a hardware layer to my view.
View.setLayerType(LAYER_TYPE_HARDWARE, paint);
More info on why this is to be done here: https://stackoverflow.com/a/33483016/4747587
Same answer as #Sergey Pekar give but I have updated it in Kotlin.
fun ImageView.getMaskBitmap(imageUrl: String? = null, mContent: Int, mMaskedImage : Int) {
runOnBackground {
// if you have https image url then use below line
//val original: Bitmap = BitmapFactory.decodeStream(URL(imageUrl).openConnection().getInputStream())
// if you have png or jpg image then use below line
val original: Bitmap = BitmapFactory.decodeResource(resources, mContent)
val mask = BitmapFactory.decodeResource(resources, mMaskedImage) // mMaskedImage Your masking image
val result: Bitmap = Bitmap.createBitmap(mask.width, mask.height, Bitmap.Config.ARGB_8888, true)
val tempCanvas = Canvas(result)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
tempCanvas.apply {
drawBitmap(original, 0f, 0f, null)
drawBitmap(mask, 0f, 0f, paint)
}
paint.xfermode = null
//Draw result after performing masking
runOnBackground(onMainThread = {
this.apply {
setImageBitmap(result)
scaleType = ImageView.ScaleType.FIT_CENTER
}
})
}
}
Github Demo
Bitmap finalMasking = stackMaskingProcess(imageBitmap, bitmapMasking);
private Bitmap stackMaskingProcess(Bitmap _originalBitmap, Bitmap _maskingBitmap) {
try {
if (_originalBitmap != null)
{
int intWidth = _originalBitmap.getWidth();
int intHeight = _originalBitmap.getHeight();
resultMaskBitmap = Bitmap.createBitmap(intWidth, intHeight, Bitmap.Config.ARGB_8888);
getMaskBitmap = Bitmap.createScaledBitmap(_maskingBitmap, intWidth, intHeight, true);
Canvas mCanvas = new Canvas(resultMaskBitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mCanvas.drawBitmap(_originalBitmap, 0, 0, null);
mCanvas.drawBitmap(getMaskBitmap, 0, 0, paint);
paint.setXfermode(null);
paint.setStyle(Paint.Style.STROKE);
}
} catch (OutOfMemoryError o) {
o.printStackTrace();
}
return resultMaskBitmap;
}
I like the approach from Er. Praful Parmar's answer but for me it did not quite work as expected. I had problems, because some scaling was going on without intention.
My Bitmaps had a different density than my device and this messed things up.
Also I wanted to reduce the creation of Objects, so I moved the Paint object to a constant for reuse.
So here is my utils method:
public static final//
Bitmap createWithMask(final Bitmap img, final Bitmap mask) {
final Bitmap result = Bitmap.createBitmap(img.getWidth(), img.getHeight(),
Bitmap.Config.ARGB_8888);
result.setDensity(originalBitmap.getDensity()); // to avoid scaling if density of 'img' is different form the default on your device
final Canvas canvas = new Canvas(result);
canvas.drawBitmap(img, 0, 0, null);
canvas.drawBitmap(mask, 0, 0, PAINT_FOR_MASK);
return result;
}//end-method
private static final Paint PAINT_FOR_MASK = createPaintForMask();
private static final//
Paint createPaintForMask() {
final Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
return paint;
}//end-method

Android how to apply mask on ImageView?

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);

android - adding a String over a Drawable image?

I'm trying to add a String to a Drawable image. I'm currently not using a Panel to draw and I'd like to keep it that way. Any ideas or do I need to invoke an onDraw() method?
My image is showing up with this code:
Drawable image = getResources().getDrawable(tile_types[tileType]);
setImageDrawable(image);
I'd like to add a String over this image.
Thanks.
Sam's answer was my starting point, but the image didn't show up, only the text (I use it on a Google Map). Finally I got it working with a LayerDrawable. Here is my solution:
private Drawable createMarkerIcon(Drawable backgroundImage, String text,
int width, int height) {
Bitmap canvasBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
// Create a canvas, that will draw on to canvasBitmap.
Canvas imageCanvas = new Canvas(canvasBitmap);
// Set up the paint for use with our Canvas
Paint imagePaint = new Paint();
imagePaint.setTextAlign(Align.CENTER);
imagePaint.setTextSize(16f);
// Draw the image to our canvas
backgroundImage.draw(imageCanvas);
// Draw the text on top of our image
imageCanvas.drawText(text, width / 2, height / 2, imagePaint);
// Combine background and text to a LayerDrawable
LayerDrawable layerDrawable = new LayerDrawable(
new Drawable[]{backgroundImage, new BitmapDrawable(canvasBitmap)});
return layerDrawable;
}
Drawable image = getResources().getDrawable(tile_types[tileType]);
// Store our image size as a constant
final int IMAGE_WIDTH = image.getIntrinsicWidth();
final int IMAGE_HEIGHT = image.getIntrinsicHeight();
// You can also use Config.ARGB_4444 to conserve memory or ARGB_565 if
// you don't have any transparency.
Bitmap canvasBitmap = Bitmap.createBitmap(IMAGE_WIDTH,
IMAGE_HEIGHT,
Bitmap.Config.ARGB_8888);
// Create a canvas, that will draw on to canvasBitmap. canvasBitmap is
// currently blank.
Canvas imageCanvas = new Canvas(canvasBitmap);
// Set up the paint for use with our Canvas
Paint imagePaint = new Paint();
imagePaint.setTextAlign(Align.CENTER);
imagePaint.setTextSize(16f);
// Draw the image to our canvas
image.draw(imageCanvas);
// Draw the text on top of our image
imageCanvas.drawText("Sample Text",
IMAGE_WIDTH / 2,
IMAGE_HEIGHT / 2,
imagePaint);
// This is the final image that you can use
BitmapDrawable finalImage = new BitmapDrawable(canvasBitmap);
If your resulted text looks "angular" due to resizing, it's better to use TextPaint instead of plain Paint with these parameters:
TextPaint textPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG | TextPaint.LINEAR_TEXT_FLAG);

Categories

Resources