During the past weeks I was looking for appropriate solution for zooming and scrolling functionality on a custom view.In my custom view I am drawing bitmaps on my canvas in onDraw() and creating new bitmap and canvas in onSizeChanged().
my code snippet here :
onDraw() method.In this method I am calling drawBitmap() this method takes image times from pagesProvider class that is my custom class.
Bitmap backBitmap = pagesProvider.getPageBitmap(tile);
src = new Rect();
dst = new Rect();
#Override
public void onDraw(Canvas canvas) {
drawBitmap(canvas, b, src, dst);
}
Here it is drawBitmap() this method drawBitmap on canvas based on Color mode. This color takes from my custom Options Class.And again I am drawing bitmap on canvas with mBitmap this mBitmap created on onSizeChanged() and drawing path on canvas.
private void drawBitmap(Canvas canvas, Bitmap backBitmap, Rect src, Rect dst) {
if (colorMode != Options.COLOR_MODE_NORMAL) {
paint.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(
Options.getColorModeMatrix(this.colorMode))));
canvas.drawBitmap(backBitmap, src, dst, paint);
} else {
canvas.drawBitmap(b, src, dst, null);
}
canvas.drawBitmap(mTopBitmap, 0, 0, mPaint); // mTopBitmap canvas is creating new bitmap on backBitmap bitmap canvas.
canvas.drawPath(mPath, mPaint);
}
onSizeChanged() :
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
this.width = w;
this.height = h;
mTopBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mTopBitmap);
}
If I remove canvas.drawPath(mPath, mPaint); line from drawBitmap() canvas is not drawing paths on mTopBitmap canvas.And if I remove canvas.drawBitmap(mTopBitmap, 0, 0, mPaint); paths are drawn on backBitmap but those paths resetting when touch up.
In onTouch UP I am calling mCanvas.drawPath(mPath, mPaint); for drawing new path on mTopBitmap.
And My problem was I want to scroll the mTopBitmap canvas content whatever I draw on mCanvas and zoom the mTopBitmap canvas content of mCanvas.
I written zooming functionality and I added scroller according to backBitmap size.It's working fine.But I have confuse with mTopBitmap canvas how I can put scroll and zoom into mTop Canvas.
Related
I try to hide a picture and show interactively its portions when user touches a screen. I tried numerous approaches like having background view that is overlapsed with views that I will make transparent which somehow worked. A final solution shall be a single custom view which will give me more painting flexibility.
Activity:
hiddenPicture.setBackgroundResource(R.drawable.picture);
View:
init()
eraserPaint = new Paint();
eraserPaint.setColor(Color.TRANSPARENT);
// eraserPaint.setAlpha(0);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
veilPaint = new Paint();
veilPaint.setColor(Color.GRAY);
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0, 0, w, h, veilPaint);
canvas.drawOval(new RectF(20, 20, 220, 220), eraserPaint);
}
The problem is that the oval is black. I found tons of similar questions but I am too junior in Android to apply them to my case. What shall I do to erase oval in grey veil and show background picture? Thank you.
Update:
I found nice blog: http://www.41post.com/4794/programming/android-rendering-a-path-with-a-bitmap-fill
I recycled it to my code:
fillBMP = BitmapFactory.decodeResource(context.getResources(), R.drawable.picture);
fillBmpShader = new BitmapShader(fillBMP, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
eraserPaint.setColor(0xFFFFFFFF);
eraserPaint.setStyle(Paint.Style.FILL);
eraserPaint.setShader(fillBmpShader);
It seemed to work but the problem is that it does not scale the image used as brush. Is really the only solution to implement onDraw() this way:
Paint the picture
Paint the veil except uncovered parts
I worry about the performance. I do not want to paint complete screen after each user interaction. I would prefer to repaint only relevant parts. Is this even possible or am I over-optimizing already?
I have to maintain and paint two bitmaps - one for background and second for veil with transparent hollows:
private void setupDrawing() {
eraserPaint = new Paint();
eraserPaint.setColor(Color.TRANSPARENT);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
veilPaint = new Paint();
veilPaint.setColor(Color.GREEN);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
// http://developer.android.com/training/custom-views/custom-drawing.html
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
drawCanvas.drawRect(0, 0, w, h, veilPaint);
}
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(fillBitmap, 0, 0, canvasPaint);
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
}
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
drawCanvas.drawCircle(event.getX(), event.getY(), 150, eraserPaint);
invalidate();
}
return true;
}
I created a function to draw a text on a path:
public void drawText(float x, float y, String text) {
Log.i("DRAWING", "drawText");Typeface.BOLD);
mPath.reset();
mPath.moveTo(x, y);
mPath.lineTo(x+200,y);
Paint textPaint = new Paint();
textPaint.setColor(Color.RED);
textPaint.setTextSize(20f);
textPaint.setAntiAlias(true);
textPaint.setStrokeWidth(5f);
textPaint.setStyle(Paint.Style.FILL);
mCanvas.drawTextOnPath(text, mPath, 0, 0, textPaint);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
paths.add(mPath);
invalidate();
}
I set a Bitmap like this:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas();
mCanvas.setBitmap(mBitmap);
reset();
}
Question:
When I set a bitmap, it works fine and the text appears on the ImageView, but when I don't, just a white line appears and not the text.
Do I have to use a Bitmap to draw text on path with drawTextOnPath ? Because I want to use only paths (all works fine except text, like it needed a Bitmap).
So a Bitmap is mandatory to draw text with drawTextOnPath.
The text won't appear if there is no Bitmap.
For those who, like me, implemented a Undo/Redo functionnality, I guess you have to save both paths and bitmaps to allow user to go back and forward.
I need to produce the effect of drawing a bitmap in a circle shape, this circle will be growing from the center until it reaches the maximum radius, to do that I wrote the following method:
public Bitmap applyDrawingEffect(Bitmap src, int nRadiusprct)
{
// image size
int width = src.getWidth();
int height = src.getHeight();
// create bitmap output
Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
// set canvas for painting
Canvas canvas = new Canvas(result);
canvas.drawARGB(0, 0, 0, 0);
// config paint
final Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.BLACK);
// config rectangle for embedding
final Rect rect = new Rect(0, 0, width, height);
final RectF rectF = new RectF(rect);
// draw rect to canvas
//canvas.drawRoundRect(rectF, round, round, paint);
float fRadius = (width<=height) ? (width/2) : (height/2);
canvas.drawCircle(width/2, height/2, (fRadius * nRadiusprct/100), paint);
// create Xfer mode
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// draw source image to canvas
canvas.drawBitmap(src, rect, rect, paint);
// return final image
return result;
}
As you see I passed the radius percentage as a parameter, my question is How I can optimize the code more and more, why do I need to create a bitmap everytime and assign it to a new canvas instance and repaint again....
Is there any way to create a single bitmap and draw over it many times to produce the same effect.
For the simple way to use a canvas and a bitmap, you can use it along with one of Androids animation methods to do the heavy lifting. Below I wrote a translate animation using the value animator to move my bitmap inside of the canvas. It doesn't require recreate the bitmap over and over again.
public void doCanvas(){
//Create our resources
Bitmap bitmap = Bitmap.createBitmap(mLittleChef.getWidth(), mLittleChef.getHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
final Bitmap chefBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.dish_special);
final Bitmap starBitmap= BitmapFactory.decodeResource(getResources(),R.drawable.star);
//Link the canvas to our ImageView
mLittleChef.setImageBitmap(bitmap);
ValueAnimator animation= ValueAnimator.ofInt(canvas.getWidth(),0,canvas.getWidth());
animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (Integer) animation.getAnimatedValue();
//Clear the canvas
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
canvas.drawBitmap(chefBitmap, 0, 0, null);
canvas.save();
canvas.translate(value,0);
canvas.drawBitmap(starBitmap, 0, 0, null);
canvas.restore();
//Need to manually call invalidate to redraw the view
mLittleChef.invalidate();
}
});
animation.addListener(new AnimatorListenerAdapter(){
#Override
public void onAnimationEnd(Animator animation) {
simpleLock= false;
}
});
animation.setInterpolator(new LinearInterpolator());
animation.setDuration(mShortAnimationDuration);
animation.start();
}
For more about the code and canvas, and the different way to use canvas and bitmap see
here
I have a drawing app that allows the user to draw to a blank canvas. I am attempting to draw a scaled 'thumbnail' of the current bitmap so that when the user has scaled in the View, they can reference the thumbnail to get a sense as to where they are in the overall draw canvas. I have the scaling working, and am displaying the thumbnail in the correct location, but it appears that the thumbnail is not being updated on subsequent onDraws when new lines/shapes are added.
So that I have access to the underlying bitmap for this view (to show the thumbnail, be able to easily save the bitmap to a file, etc) I do the following in onSizeChanged() for the View:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// set the canvas height, panning, etc, based on the now inflated View
mWidth = getWidth();
mHeight = getHeight();
mAspectRatio = mWidth / mHeight;
mPanX = 0;
mPanY = 0;
// create the Bitmap, set it to the canvas
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(mBitmap);
draw(mCanvas);
}
Then, when the user draws and invalidate() is called, I do the following in onDraw() to generate the thumbnail:
#Override
protected void onDraw(Canvas canvas) {
<snipped code that draws paths, shapes to canvas>
if (mScaled) {
Bitmap out = Bitmap.createScaledBitmap(mBitmap, (int) thumbWidth, (int) thumbHeight, false);
canvas.drawBitmap(out, null, thumbnailRectF, thumbCanvasPaint);
}
}
The thumbnail gets displayed in the space defined by thumbnailRectF using the thumbCanvasPaint, but in subsequent onDraw() calls, the scaled bitmap has not changed from it's original state, even though the full-sized active canvas shows all of the drawings, etc. Based on some testing, it seems to me that while I am setting the Bitmap with the initial call to draw(mCanvas);, subsequent onDraws are writing to the underlying Bitmap rather than the one specified in onSizeChanged().
So, I guess I am trying to figure out how I tie the onDraw canvas to a Bitmap that I can readliy access to perform re-sizes, save, etc. Looking at this question, I thought that the draw(mCanvas); call would tie the onDraw to the bitmap specified in the mCanvas (in my case, mBitmap), but in practice, it doesn't seem to be working, in so far as updats to the canvas are concerned.
Thanks,
Paul
canvas.drawBitmap(out, null, thumbnailRectF, thumbCanvasPaint);
should change to
canvas.drawBitmap(out, new Rect(0,0,mBitmap.getWidht, mBitmap.getheight), thumbnailRectF, thumbCanvasPaint);
There is no need for
Bitmap out = Bitmap.createScaledBitmap(mBitmap, (int) thumbWidth, (int)....
Also check that mScaled is true all the time when zoom is greater than 1
Scale bitmap by Bitmap.createScaledBitmap then draw will not work
The solution for scale the canvas bitmap is use this function (from the docs)
void drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
// dst : Rect: The rectangle that the bitmap will be scaled/translated to fit into
so by changing the size of dst, your bitmap size will change
Here is example if I want to draw a bitmap at top-left and scale it to 100px x 120px
Bitmap bitmap = BitmapFactory.decodeResource(...);//bitmap to be drawn
float left = 0;
float top = 0;
RectF dst = new RectF(left, top, left + 100, top + 120); // width=100, height=120
canvas.drawBitmap(bitmap, null, dst, null);
This is how I draw Bitmap on Canvas in my Android app:
canvas.save();
canvas.scale(scale, scale, x, y);
canvas.drawBitmap(bitmap, x, y, null);
canvas.restore();
However the Bitmap is not scaled smoothly, no anti-aliasing is performed. How can I enable anti-aliasing?
Try this:
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setDither(true);
canvas.drawBitmap(bitmap, x, y, paint);
Both Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG); or paint.setFilterBitmap(true); worked for me but be very careful, on my game it cut down the FPS from 30FPS to 17FPS only. So if it is a mission critical drawing like in a game you better scale the image at loading time. Which I did in the following manner:
public Bitmap getImage (int id, int width, int height) {
Bitmap bmp = BitmapFactory.decodeResource( getResources(), id );
Bitmap img = Bitmap.createScaledBitmap( bmp, width, height, true );
bmp.recycle();
return img;
}
Have you tried creating a Paint object, calling setAntiAlias(true) on it and passing it to the drawBitmap method as the 4th parameter? If this does not work I guess you should scale down the drawBitmap call instead of scaling the Canvas, e.g. by using drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint).
You need only one Line of Code:
canvas.drawBitmap(bitmap, x, y, new Paint(Paint.ANTI_ALIAS_FLAG));
Not 5 Lines