Bitmap.recycle() Calling it after drawing - android

I am having a problem. I have a post draw listener where I draw a scaled version of a bitmap. The problem stems from the fact that I tend to do some scaling every time a zoom happens (zoom in scale up, zoom out scale down). The problem is I am not able to recycle the bitmap because when I try to do so after drawing
canvas.draw(scaledbitmap,0,0,null);
scaledBitmap.recycle()
i get the Canvas canot draw recycled bitmap exception
Does anyone know how I would go about recycling a bitmap after I am done with it so that another can be scaled afterwards and I don't get the OutOfMemoryException crash.
Some Code to show you exactly how I am using it:
private SpenDrawListener mPosteDrawListener = new SpenDrawListener() {
#Override
public void onDraw(Canvas canvas, float x, float y, float ratio,
float frameStartX, float frameStartY, RectF updateRect) {
if(mLineDrawingBitmap == null)
mLineDrawingBitmap = loadLineDrawingBitmap(mLineDrawingFileName);
Bitmap bm = Bitmap.createScaledBitmap(mLineDrawingBitmap, (int)(mLineDrawingBitmap.getWidth() * ratio), (int)(mLineDrawingBitmap.getHeight() * ratio), true);
/*
float pointX = (mScreenRect.width() - bm.getWidth()) / 2;
float pointY = mScreenRect.height() / 2 - bm.getHeight();
*/
float pointX = frameStartX - (x * ratio);
float pointY = frameStartY - (y * ratio);
//canvas.drawBitmap(bm, 0, 0,null);
canvas.drawBitmap(bm, pointX, pointY, null);
//bm.recycle();
}
};

After several hours of trial and error I have figured out how to scale nicely AND not crash the app with an OutOfMemoryException: Below is the code for drawing and scaling at runtime with no crash (as long as the image is not too large when decoded). I will use my own PostDrawListener but I believe it can be used anywhere with minor modification
private SpenDrawListener mPosteDrawListener = new SpenDrawListener() {
#Override
public void onDraw(Canvas canvas, float x, float y, float ratio,
float frameStartX, float frameStartY, RectF updateRect) {
if(mLineDrawingBitmap == null)
mLineDrawingBitmap = loadLineDrawingBitmap(mLineDrawingFileName);
float pointX = frameStartX - (x * ratio);
float pointY = frameStartY - (y * ratio);
//Create a new Matrix
Matrix m = new Matrix();
//Use any scaling ratio you want
m.postScale(ratio, ratio);
//Use any translation you want
m.postTranslate(pointX, pointY);
//when using below call you will not be creating a new bitmap, just
//using the original with runtime modifications
canvas.drawBitmap(mLineDrawingBitmap, m, null);
}
};

Related

How to center imageView with matrix scaletype?

I'm using Android Studio to display an imageView. I'm using pinch zoom to interact with my ImageView.
Code:
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
#Override
public boolean onScale(ScaleGestureDetector detector) {
scale = scale * detector.getScaleFactor();
scale = Math.max(0.1f, Math.min(scale, 5f));
matrix.setScale(scale, scale);
imageView.setImageMatrix(matrix);
return true;
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
scaleGestureDetector.onTouchEvent(event);
return true;
}
Zoom is fine, but problem is the position of imageview, it's stuck in left upper corner. Tried layout changes but nothing to do. After some reseaches, I've seen these links in the forum ImageView Center in position with ScaleType Matrix and Center the image in ImageView after zoom-pinch, but these solutions aren't working, I've also checked the links given inside.
Help would be appreciated,
Thanks !
EDIT: Added the piece of code on how I get my ImageView
Picasso.with(this).load(url).resize(350, 330).centerInside().into(imageView);
onCreate Code
ScaleListner Class and Gesture Code
Picasso.with(this).load(url).into(imageView, new Callback.EmptyCallback() {
#Override
public void onSuccess() {
Drawable d = imageView.getDrawable();
// TODO: check that d isn't null
RectF imageRectF = new RectF(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
RectF viewRectF = new RectF(0, 0, imageView.getWidth(), imageView.getHeight());
matrix.setRectToRect(imageRectF, viewRectF, ScaleToFit.CENTER);
imageView.setImageMatrix(matrix);
}
});
Edit 2:
How to center the image like CENTER_INSIDE but using matrix:
First we need a rectangle with the image dimensions:
Drawable d = imageView.getDrawable();
// TODO: check that d isn't null
RectF imageRectF = new RectF(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
Next you need a rectangle with the view dimensions:
RectF viewRectF = new RectF(0, 0, imageView.getWidth(), imageView.getHeight());
Now we run a method on matrix that will center the image in the view:
matrix.setRectToRect(imageRectF, viewRectF, ScaleToFit.CENTER);
What this does is set up the matrix so that the first rectangle will transform to the second rectangle. ScaleToFit.CENTER will preserve the aspect ratio and have the same effect as scale type CENTER_INSIDE.
So if you call
imageView.setImageMatrix(matrix);
at this point, you will have a centered image.
Edit: I think you are almost there.
Your matrix will undo the image centering that Picasso did, so you need to put in a translation to center the image before you scale it.
#Override
public boolean onScale(ScaleGestureDetector detector) {
Drawable d = imageView.getDrawable();
// if d is null, then Picasso hasn't loaded the image yet
float offsetX = (imageView.getWidth() - d.getIntrinsicWidth()) / 2F;
float offsetY = (imageView.getHeight() - d.getIntrinsicHeight()) / 2F;
float centerX = imageView.getWidth() / 2F;
float centerY = imageView.getHeight() / 2F;
// note that these offset and center values don't change with the scaling,
// so you can calculate them somewhere else and then use them here.
scale *= detector.getScaleFactor();
scale = Math.max(0.1f, Math.min(scale, 5f));
matrix.setScale(scale, scale, centerX, centerY);
matrix.preTranslate(offsetX, offsetY);
imageView.setImageMatrix(matrix);
}
You are using setScale(float sx, float sy). There is another version, setScale(float sx, float sy, float px, float py) where px,py is a pivot point.
So if you want to center your image, determine the center of your view and use the x,y value as the pivot point.
You will also want to center the image inside the view, so you will need to move the image first before you scale.
float offsetX = (imageView.getWidth() - bitmap.getIntrinsicWidth()) / 2F;
float offsetY = (imageView.getHeight() - bitmap.getIntrinsicHeight()) / 2F;
float centerX = imageView.getWidth() / 2F;
float centerY = imageView.getHeight() / 2F;
matrix.setScale(scale, scale, centerX, centerY);
matrix.preTranslate(offsetX, offsetY);

Scale bitmap to specific size, keeping aspect ratio and filling the rest with 0 alpha pixels

I've been searching for a solution to this problem for quite some time and haven't found anything to match my needs yet.
I want to scale a bitmap to a specific size while maintaining aspect ratio.
Think of it as scaling a bitmap using fitCenter in an ImageView, only in a new bitmap.
The source bitmap has to fit inside the destination bitmap which has a specific size, and the rest of the pixels have to be transparent.
I have tried using Glide like so:
Glide.with(context).load(url)
.asBitmap()
.override(1280, 720)
.fitCenter()
.into(1280, 720)
.get();
But this method returns a bitmap that fits only width (or hight) and wraps the size.
I've heard that using Canvas is a possible solution but haven't found any way of achieving my goal using it.
Any help or insight would be greatly appreciated. I will post any needed clarifications if requested.
I managed to solve it using this function:
Bitmap resizeBitmap(Bitmap image, int destWidth, int destHeight) {
Bitmap background = Bitmap.createBitmap(destWidth, destHeight, Bitmap.Config.ARGB_8888);
float originalWidth = image.getWidth();
float originalHeight = image.getHeight();
Canvas canvas = new Canvas(background);
float scaleX = (float) 1280 / originalWidth;
float scaleY = (float) 720 / originalHeight;
float xTranslation = 0.0f;
float yTranslation = 0.0f;
float scale = 1;
if (scaleX < scaleY) { // Scale on X, translate on Y
scale = scaleX;
yTranslation = (destHeight - originalHeight * scale) / 2.0f;
} else { // Scale on Y, translate on X
scale = scaleY;
xTranslation = (destWidth - originalWidth * scale) / 2.0f;
}
Matrix transformation = new Matrix();
transformation.postTranslate(xTranslation, yTranslation);
transformation.preScale(scale, scale);
Paint paint = new Paint();
paint.setFilterBitmap(true);
canvas.drawBitmap(image, transformation, paint);
return background;
}

How to draw circle on canvas that will not be affected by the scaling

Does anyone know how to make the radius of the circle be fixed although other stuff in the same canvas be scaled. I don't really know how to get the scale factor of the circle dynamically. Thanks
Canvas c = new Canvas(image);
c.drawColor(Color.TRANSPARENT, Mode.CLEAR);
c.drawBitmap(bm, 0, 0, null);
c.drawCircle(cx, cy, radius, mPaint);
Canvas c = new Canvas(image); // This image has a matrix that every time I scaled on the image the cursor will also change. I want the cursor size to stay as 13.0f.
Code:
public void draw(Canvas canvas, Paint paint) { //This is access from the main onDraw();
paint.setColorFilter(colorFilter);
// scaled bitmap base on the scaling of the bitmap
canvas.drawBitmap(bitmap, matrix, paint);
if(activateCursor == true){
if(isTouched == true && ActivityMainEditor.IS_ERASING == true){
RectF r = new RectF();
matrix.mapRect(r);
// sol1
float scaledX = (lastX - r.left) + 48;
float scaledY = (lastY - r.top) - 137;
float[] values = new float[9];
matrix.getValues(values);
// mScalingFactor shall contain the scale/zoom factor
float scalex = values[Matrix.MSCALE_X];
float skewy = values[Matrix.MSKEW_Y];
float scale = (float) Math.sqrt(scalex * scalex + skewy * skewy);
scaledX /= scale;
scaledY /= scale;
// cursor adjustment
I have used this lines to make the circle radius will always be the same although scaling the bitmap, but it's not accurate when not scaled yet is okay but when scaled bigger the cursor will got bigger also.
float scaleCursor = (float) Math.sqrt((scalex - 5) * (scalex - 5) + (skewy - 2) * (skewy - 2));
float cursorSize = (13.0f / scaleCursor); // 13.0f fixed circle radius
drawACircle(canvas, bitmap, scaledX, scaledY, cursorSize);
}
}
}
private Bitmap drawACircle(Canvas c, Bitmap bm, float cx, float cy, float radius)
{
Bitmap bmOverlay = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888);
Canvas cd = new Canvas(bmOverlay);
cd.drawColor(Color.TRANSPARENT, Mode.CLEAR);
cd.drawBitmap(bm, 0, 0, null);
// the line in which the circle will also get scaled :(
cd.drawCircle(cx, cy, radius, mPaint);
// update the main bitmap
bitmap = bmOverlay;
if(saveNow == true){
imageHistory.add(bitmap);
saveNow = false;
}
return bmOverlay;
}
It's simple:
c.scale(x,y); // scales the whole canvas (preconcat the matrix with the scale factor)
c.save(); // saves this matrix
// Do all your drawing here except the `drawCircle`.
c.restore(); // restores the original matrix
c.drawCircle(cx, cy, radius, mPaint);
Once you call restore(), the matrix is returned to the original one. You can now draw the circle in the normal fashion.
UPDATE:
Make x and y as fields in the CustomView, that way you can modify them outside the onDraw method. So in the onTouch or onTouchEvent, you can modify the x and y and call invalidate(). This will call onDraw and here it will scale. This will get you what you are looking for. In case of global scaling, always go with the canvas.scale() rather than scaling individual draw elements. This will keep things simple.

Android How to get original coordinates back after transformation of bitmap with drawMatrix

i have following problem. I have a bitmap and on this bitmap certain draw operations can be done. Like drawing a line, arrwow, etc.
This works pretty well, but the bitmap can be zoomed, dragged and rotated. For this a drawMatrix is used. Everything this works pretty good but, for drawing i need the original coordinates so that the drawings come in the right size, etc.
I can get this original coordinates with the following function. But it doesn't work with rotations. Does anybody have an idea, how to solve this?
/**
* When we have moved or zoomed we need the original image coordinates calculated from
* the screen coordinates. This function gives that back
*/
private Point translateScreenCoordsToOriginalImageCoords(MotionEvent e) {
float[] m = new float[9];
drawMatrix.getValues(m);
float transX = m[Matrix.MTRANS_X] * -1;
float transY = m[Matrix.MTRANS_Y] * -1;
float scaleX = m[Matrix.MSCALE_X];
float scaleY = m[Matrix.MSCALE_Y];
// TODO function does not work when rotated by 90 degree
if (scaleX == 0.0) {
scaleX = -1.0f;
scaleY = -1.0f;
}
int lastTouchX = (int) ((e.getX() + transX) / scaleX);
int lastTouchY = (int) ((e.getY() + transY) / scaleY);
lastTouchX = Math.abs(lastTouchX);
lastTouchY = Math.abs(lastTouchY);
return new Point(lastTouchX, lastTouchY);
}
This is how scaling is performed
/**
* Scale the canvas by
*
* #param scaleFactor e.g 2 is double
* #param focusX the center x point
* #param focusY the center y point
*
* #return true if scaling is performed
*/
private boolean doScale(float scaleFactor, int focusX, int focusY) {
if (this.scaleBoundaries(scaleFactor))
return false;
Matrix transformationMatrix = new Matrix();
// Zoom focus is where the fingers are centered
transformationMatrix.postTranslate(-focusX, -focusY);
transformationMatrix.postScale(scaleFactor, scaleFactor);
transformationMatrix.postTranslate(focusX, focusY);
drawMatrix.postConcat(transformationMatrix);
invalidate();
return true;
}
e.g. This is the draw function of a Line
#Override
public void draw(Canvas c, Matrix drawMatrix, boolean translateCoordiantes) {
float pts[] = new float[] {this.start.x, this.start.y, this.end.x, this.end.y};
if (translateCoordiantes)
drawMatrix.mapPoints(pts);
c.drawLine(pts[0], pts[1], pts[2], pts[3], this.paint);
}
Wow pskink thanks a lot! That was the answer! To get the orignal coordinates back, use:
private Point translateCoordinatesBack(MotionEvent event) {
Matrix inverse = new Matrix();
drawMatrix.invert(inverse);
float[] pts = {event.getX(), event.getY()};
inverse.mapPoints(pts);
return new Point((int) pts[0], (int) pts[1]);
}

Find new coordinates of overlay object on the canvas after modifying on matrix in Android

I'm working on Android project and I'm using View class to drawing image and overlays.
I'm using this code to drawing image and circle (it is working fine):
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if (!isInitialized) {
w = getWidth();
h = getHeight();
position.set(w / 2, h / 2);
// Scaling image to be fit screen
if(width<=height) { scale=((float)w)/width; } else { scale=((float)h)/height; }
isInitialized = true;
}
Paint paint = new Paint();
matrix.reset();
matrix.postTranslate(-width / 2.0f, -height / 2.0f);
matrix.postRotate((float)(angle*180/Math.PI));
matrix.postScale(scale, scale);
matrix.postTranslate(position.getX(), position.getY());
canvas.drawBitmap(bitmap, matrix, paint);
canvas.concat(matrix);
canvas.drawCircle(testPoint.x, testPoint.y, 20, paint );
}
Now I want to show PopupWindow windows as a tip on the image and on the same position of circle (testPoint). but the PopupWindows is not drawable object, so how can I find the new coordinates of testPoint after scaling, rotation and translating to put the coordinates of the PopupWindow same.
I'm trying to write code and it working fine, but for rotation and translating only without scaling, this is the code I wrote:
int offsetX=(int) (-(width / 2.0f)+position.getX())+rc.left;
int offsetY=(int) (-(height / 2.0f)+position.getY())+rc.top;
Point RotatedPoint = RotatePoint(testPoint, centerPoint, angle);
RotatedPoint.x +=offsetX;
RotatedPoint.y +=offsetY;
popup.update(RotatedPoint.x, RotatedPoint.y, -1, -1);
Where rc is the offset difference between bitmap view coordinates and the screen coordinates.
And I'm tested RotatePoint function and it working correctly.
Note: when the scale is equal 1 (disable scaling) the position of popup window is updating correctly if I rotate or move the image.
How can I merged the scale (if not equal 1 only) with equations?
Or there is another way to find new coordinates of overlay object on the canvas after modifying on matrix?
Please help me to solved this problem.
I will be grateful for the help.
Thank you.
try to use matrix.mapPoints()
u must have the absolute coordinates of the image
how to absolute coord?
void calculaCoordenadasImagen(MotionEvent e){
float []m = new float[9];
matrix.getValues(m);
float transX = m[Matrix.MTRANS_X] * -1;
float transY = m[Matrix.MTRANS_Y] * -1;
float scaleX = m[Matrix.MSCALE_X];
float scaleY = m[Matrix.MSCALE_Y];
lastTouchX = (int) ((e.getX() + transX) / scaleX);
lastTouchY = (int) ((e.getY() + transY) / scaleY);
lastTouchX = Math.abs(lastTouchX);
lastTouchY = Math.abs(lastTouchY);
}
then use 2 different arrays to save the points
int [] absolute = new int[2];
absolute[0]=lastTouchX;
absolute[1]=lastTouchY;
int [] points = new int[2];
points = matrix.mapPoints(absolute)
in absolute u have the absolute coordinates and in points u have the points u want know
i wish it help!

Categories

Resources