Is it possible to do a drop shadow on the content of an ImageView?
Not a square, but an object drop shadow that acts on the non-transparent content of the ImageView.
Like this
This is taken from Romain Guy's presentation at Devoxx, pdf found here
As you already converted your image as a bitmap try like below
Paint mShadow = new Paint();
// radius=10, y-offset=2, color=black
mShadow.setShadowLayer(10.0f, 0.0f, 2.0f, 0xFF000000);
// in onDraw(Canvas)
canvas.drawBitmap(bitmap, 0.0f, 0.0f, mShadow);
Hope this helps.
For more information follow this answer
NOTES
Don't forget for Honeycomb and above you need to invoke
setLayerType(LAYER_TYPE_SOFTWARE, mShadow), otherwise you will not
see your shadow!
SetShadowLayer does not work with hardware acceleration
unfortunately so it greatly reduces performances [1] [2]
I faced this same problem, and had to figure something out quick. This may not be the BEST solution, but my fix was to add
android:adjustViewBounds="true"
on the image view, and then set the SAME image as the source as the background. That way the background is the same size and shape as the source image, and so the shadow looks natural, since shadows in android are based on the ui elements background.
Use this class to draw shadow on bitmap
public class ShadowGenerator {
// Percent of actual icon size
private static final float HALF_DISTANCE = 0.5f;
public static final float BLUR_FACTOR = 0.5f/48;
// Percent of actual icon size
private static final float KEY_SHADOW_DISTANCE = 1f/48;
public static final int KEY_SHADOW_ALPHA = 61;
public static final int AMBIENT_SHADOW_ALPHA = 30;
private static final Object LOCK = new Object();
// Singleton object guarded by {#link #LOCK}
private static ShadowGenerator sShadowGenerator;
private int mIconSize;
private final Canvas mCanvas;
private final Paint mBlurPaint;
private final Paint mDrawPaint;
private final Context mContext;
private ShadowGenerator(Context context) {
mContext = context;
mCanvas = new Canvas();
mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
mBlurPaint.setMaskFilter(new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL));
mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
}
public synchronized Bitmap recreateIcon(Bitmap icon) {
mIconSize = Utils.convertDpToPixel(mContext,3)+icon.getWidth();
int[] offset = new int[2];
Bitmap shadow = icon.extractAlpha(mBlurPaint, offset);
Bitmap result = Bitmap.createBitmap(mIconSize, mIconSize, Config.ARGB_8888);
mCanvas.setBitmap(result);
// Draw ambient shadow
mDrawPaint.setAlpha(AMBIENT_SHADOW_ALPHA);
mCanvas.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);
// Draw key shadow
mDrawPaint.setAlpha(KEY_SHADOW_ALPHA);
mCanvas.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint);
// Draw the icon
mDrawPaint.setAlpha(255);
mCanvas.drawBitmap(icon, 0, 0, mDrawPaint);
mCanvas.setBitmap(null);
return result;
}
public static ShadowGenerator getInstance(Context context) {
synchronized (LOCK) {
if (sShadowGenerator == null) {
sShadowGenerator = new ShadowGenerator(context);
}
}
return sShadowGenerator;
}
}
Related
Good day. I am pretty much frustrated as what I want to achieve is the simplest thing in world but nowhere I could get anything. I just want to draw an simple HORIZONTAL line in the centre of the GL surface view (the idea is to make audio wave animation based on the amplitude of the recording) so I did achieve that simply and easy with canvas, but with GL surface view there are not even one simple example or one simple question on how to exactly draw that line on the surface of gl.
Anyway here is what I got and have no clue where I should write the draw code and what should I write to achieve above desired function.
GL Render class
public class MyGLRenderer implements GLSurfaceView.Renderer {
GL10 unused;
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
public void onDrawFrame(GL10 unused) {
// Redraw background color
// GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
this.unused = unused;
}
public void onSurfaceChanged(GL10 unused, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
}
GL View class (this class is copy pasted from another one which extends SurfaceView so do not take a notice of the canvas and etc.They will be deleted)
public class GLView extends GLSurfaceView {
// The number of buffer frames to keep around (for a nice fade-out visualization).
private static final int HISTORY_SIZE = 1;
private MyGLRenderer mRenderer;
Handler handler = new Handler();
private boolean isFinished=true;
private Runnable runnable = new Runnable() {
#Override
public void run() {
int colorDelta = 255 / (HISTORY_SIZE + 1);
int brightness = colorDelta;
final Random rnd = new Random();
mPaint.setColor(Color.argb(brightness, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256)));
isFinished = true;
brightness += colorDelta;
}
};
// To make quieter sounds still show up well on the display, we use +/- 8192 as the amplitude
// that reaches the top/bottom of the view instead of +/- 32767. Any samples that have
// magnitude higher than this limit will simply be clipped during drawing.
private static final float MAX_AMPLITUDE_TO_DRAW = 10000.0f;
// The queue that will hold historical audio data.
private final LinkedList<short[]> mAudioData;
private final Paint mPaint;
public GLView(Context context) {
super(context);
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2);
mAudioData = new LinkedList<short[]>();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(5);
mPaint.setAntiAlias(true);
mRenderer = new MyGLRenderer();
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(mRenderer);
}
public GLView(Context context, AttributeSet attrs) {
super(context, attrs);
mAudioData = new LinkedList<short[]>();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(5);
mPaint.setAntiAlias(true);
mRenderer = new MyGLRenderer();
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(mRenderer);
}
/**
* Updates the waveform view with a new "frame" of samples and renders it. The new frame gets
* added to the front of the rendering queue, pushing the previous frames back, causing them to
* be faded out visually.
*
* #param buffer the most recent buffer of audio samples
*/
public synchronized void updateAudioData(short[] buffer) {
short[] newBuffer;
// We want to keep a small amount of history in the view to provide a nice fading effect.
// We use a linked list that we treat as a queue for this.
if (mAudioData.size() == HISTORY_SIZE) {
newBuffer = mAudioData.removeFirst();
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
} else {
newBuffer = buffer.clone();
}
mAudioData.addLast(newBuffer);
final Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
drawWaveform(canvas);
getHolder().unlockCanvasAndPost(canvas);
}
}
/**
* Repaints the view's surface.
*
* #param canvas the {#link Canvas} object on which to draw
*/
private void drawWaveform(Canvas canvas) {
// Clear the screen each time because SurfaceView won't do this for us.
canvas.drawColor(Color.BLACK);
float width = getWidth();
float height = getHeight();
float centerY = height / 2;
// We draw the history from oldest to newest so that the older audio data is further back
// and darker than the most recent data.
int colorDelta = 255 / (HISTORY_SIZE + 1);
int brightness = colorDelta;
final Random rnd = new Random();
if(isFinished){
isFinished = false;
handler.postDelayed(runnable, 500);
}
for (short[] buffer : mAudioData) {
// mPaint.setColor(Color.argb(brightness, 128, 255, 192));
float lastX = -1;
float lastY = -1;
// For efficiency, we don't draw all of the samples in the buffer, but only the ones
// that align with pixel boundaries.
for (int x = 0; x < width; x++) {
int index = (int) ((x / width) * buffer.length);
short sample = buffer[index];
float y = (sample / MAX_AMPLITUDE_TO_DRAW) * centerY + centerY;
if (lastX != -1) {
canvas.drawLine(lastX, lastY, x, y, mPaint);
}
lastX = x;
lastY = y;
}
brightness += colorDelta;
}
}
}
Please...can you help me and tell me how to draw horizontal centered line on this gl surface view?
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);
This question already has answers here:
How to make an ImageView with rounded corners?
(58 answers)
Closed 9 years ago.
I am new to android dev, and I have been trying for a few hours now to add nice and smooth rounded corners to an ImageView, without success.
First thing I tried is simply to round corners of my images directly, but this implies changing the bitmap, and since I need to keep the raw ones, and those are pretty big, this is not really memory friendly. This would also cause other difficulties since my ImageView is fluid.
Second thing I tried to use is the clipPath method after subclassing my view. This works, but corners are aliased. I then tried adding a PaintFlagsDrawFilter to implement the aliasing, but this didn't worked. I'm using monodroid, and I was wondering this was supposed to work in Java.
Here is my code (C#):
public class MyImageView : ImageView
{
private float[] roundedCorner;
/**
* Contains the rounded corners for the view.
* You can define one, four or height values.
* This behaves as the css border-radius property
*
* #see http://developer.android.com/reference/android/graphics/Path.html#addRoundRect(android.graphics.RectF, float[], android.graphics.Path.Direction)
*/
public float[] RoundedCorners{
get{
return roundedCorner;
}
set{
float[] finalValue = new float[8];
int i=0;
if(value.Length == 1){
for(i=0; i<8;i++){
finalValue[i] = value[0];
}
}else if(value.Length == 4){
for(i=0; i<4;i++){
finalValue[2*i] = value[i];
finalValue[2*i+1] = value[i];
}
}
roundedCorner = finalValue;
}
}
public SquareImageView (Context context) :
base (context)
{
Initialize ();
}
public SquareImageView (Context context, IAttributeSet attrs) :
base (context, attrs)
{
Initialize ();
}
private void Initialize ()
{
RoundedCorners = new float[]{0,0,0,0};
}
public override void Draw (Android.Graphics.Canvas canvas)
{
Path path = new Path();
path.AddRoundRect(new RectF(0,0, Width,Height),RoundedCorners, Path.Direction.Cw);
canvas.ClipPath(path);
base.Draw (canvas);
}
/**
* try to add antialiasing.
*/
protected override void DispatchDraw (Canvas canvas)
{
canvas.DrawFilter = new PaintFlagsDrawFilter((PaintFlags)1, PaintFlags.AntiAlias);
base.DispatchDraw (canvas);
}
}
Thanks for your help!
I've created a RoundedImageView based off Romain Guy's example code that wraps this logic into an ImageView that you should be able to just use. It supports borders and antialiasing out of the box.
It's more efficient than other rounded corner examples because it doesn't create another copy of the bitmap, nor does it use clipPath which draws twice to the canvas.
use below code
public Bitmap getRoundedCornerBitmap(Bitmap bitmap, int pixels)
{
Bitmap output = null;
if(bitmap != null)
{
output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), 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());
final RectF rectF = new RectF(rect);
final float roundPx = pixels;
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
}
return output;
}
and call this method like
imageView.setImageBitmap(getRoundedCornerBitmap(bitmap, 10));
I am trying to programmatically draw a parking icon to place as the drawable for an itemized overlay on a map.
The icon consists of a blue square with a white 'P' in its centre of which I would like to programmatically change the colour of the square to denote different parking types.
I've tried creating it via the canvas using drawRect & drawText but I cannot find a simple way of centering the text in the square and I cannot find a way to center the canvas on the coordinates - it keeps wanting to anchor from the top left hand corner.
I've alternatively tried creating an XML layout to convert to a drawable but cannot achieve this either.
Is there an elegant solution for what I am trying to achieve?
public class TextDrawable extends Drawable {
private final static int TEXT_PADDING = 3;
private final static int ROUNDED_RECT_RADIUS = 5;
private final String text;
private final Paint textPaint;
private final Rect textBounds;
private final Paint bgPaint;
private final RectF bgBounds;
public TextDrawable(String text, String backgroundColor, int textHeight) {
this.text = text;
// Text
this.textPaint = new Paint();
this.textBounds = new Rect();
textPaint.setColor(Color.WHITE);
textPaint.setARGB(255, 255, 255, 255);
textPaint.setAntiAlias(true);
textPaint.setSubpixelText(true);
textPaint.setTextAlign(Paint.Align.CENTER); // Important to centre horizontally in the background RectF
textPaint.setTextSize(textHeight);
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
// Map textPaint to a Rect in order to get its true height
// ... a bit long-winded I know but unfortunately getTextSize does not seem to give a true height!
textPaint.getTextBounds(text, 0, text.length(), textBounds);
// Background
this.bgPaint = new Paint();
bgPaint.setAntiAlias(true);
bgPaint.setColor(Color.parseColor(backgroundColor));
float rectHeight = TEXT_PADDING * 2 + textHeight;
float rectWidth = TEXT_PADDING * 2 + textPaint.measureText(text);
//float rectWidth = TEXT_PADDING * 2 + textHeight; // Square (alternative)
// Create the background - use negative start x/y coordinates to centre align the icon
this.bgBounds = new RectF(rectWidth / -2, rectHeight / -2, rectWidth / 2, rectHeight / 2);
}
#Override
public void draw(Canvas canvas) {
canvas.drawRoundRect(bgBounds, ROUNDED_RECT_RADIUS, ROUNDED_RECT_RADIUS, bgPaint);
// Position the text in the horizontal/vertical centre of the background RectF
canvas.drawText(text, 0, (textBounds.bottom - textBounds.top)/2, textPaint);
}
#Override
public void setAlpha(int alpha) {
bgPaint.setAlpha(alpha);
textPaint.setAlpha(alpha);
}
#Override
public void setColorFilter(ColorFilter cf) {
bgPaint.setColorFilter(cf);
textPaint.setColorFilter(cf);
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Make several png images & put them in res\drawable, if you have more than like 5 colors then think about using less. It's confusing for the user.
I am trying to move a rectangle that i created to the center of the screen as I am using an accelerometer to allow it to move, is there a way to move the shape to the center of the screen without using android xml?
public class CustomDrawableView extends View
{
static final int width = 150;
static final int height = 250;
public CustomDrawableView(Context context)
{
super(context);
mDrawable = new ShapeDrawable(new RectShape());
mDrawable.getPaint().setColor(0xff74AC23);
mDrawable.setBounds(x, y, x + width, y + height);
}
protected void onDraw(Canvas canvas)
{
RectF rect = new RectF(AccelActivity.x, AccelActivity.y, AccelActivity.x + width, AccelActivity.y
+ height); // set bounds of rectangle
Paint p = new Paint(); // set some paint options
p.setColor(Color.BLUE);
canvas.drawRect(rect, p);
invalidate();
}
}
}
Yes, check this example, no use of xml there: How can I use the animation framework inside the canvas?
Use on sizeChanged to find the actual size of your canvas...