Android Scaling Canvas Bitmap - android

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

Related

Drawing bitmap to the size of the canvas

I have bitmap that need to draw to canvas. The image is of a fixed size, but the canvas will change according to the user's screen size and density (bitmap coule be larger/smaller than the canvas).
I need to draw the bitmap to canvas scaling all the way into the canvas size (without distorting the image), I have done the code as below but the bitmap still filling only a portion of the screen.
Rect dest = new Rect(0, 0, drawCanvas.getWidth(), drawCanvas.getHeight());
Paint paint = new Paint();
paint.setFilterBitmap(true);
drawCanvas.drawBitmap(canvasBitmap, null, dest, paint);
May I know if anybody can shed light on a good solution? Thanks.
This example is in javascript but it should still help you out scale an image
jsFiddle : https://jsfiddle.net/CanvasCode/7oghuwe2/3/
javascript
var canvas1 = document.getElementById('canvas1');
var context1 = canvas1.getContext('2d')
var canvas2 = document.getElementById('canvas2');
var context2 = canvas2.getContext('2d');
var image1 = new Image();
image1.src = "http://media.giphy.com/media/iNk83OBPzlA8o/giphy.gif";
image1.onload = function () {
context1.fillStyle = "#F00";
context1.fillRect(0, 0, canvas1.width, canvas1.height);
context2.fillStyle = "#00F";
context2.fillRect(0, 0, canvas2.width, canvas2.height);
ratio(context1, canvas1, image1);
ratio(context2, canvas2, image1);
}
function ratio(context1, canvas1, image1) {
var imageRatio = image1.width / image1.height;
var newHeight = canvas1.width / imageRatio;
var newWidth = canvas1.height * imageRatio;
var heightDiff = newHeight - canvas1.height;
var widthDiff = newWidth - canvas1.width;
if (widthDiff >= heightDiff) {
context1.drawImage(image1, 0, 0, canvas1.width, canvas1.width / imageRatio);
} else {
context1.drawImage(image1, 0, 0, canvas1.height * imageRatio, canvas1.height);
}
}
Basically you need to calculate what the width would be if you scaled the image by the canvas height and what the height would be if you scale the image by the canvas width, and which ever is smaller, then you scale by that dimension.
The reason why it might not work for you might be because the function drawBitmap() ignores the density of the bitmap. The following is from the documentation.
public void drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint
paint)
This function ignores the density associated with the bitmap. This is
because the source and destination rectangle coordinate spaces are in
their respective densities, so must already have the appropriate
scaling factor applied.
What you could do is use public void drawBitmap (Bitmap bitmap, Matrix matrix, Paint paint) instead. First you need to map the source Matrix with the desination Matrix. You do this via Matrix.setRectToRect() or Matrix.setPolyToPoly(). This will give you an accurate mapping. Just make sure you map them correctly, otherwise things will be distorted.
For more info refer here: What code should I use with 'drawMatrix.setPolyToPoly' to manipulate and draw just a rectangular part of a bitmap, rather than the entire bitmap?

Android Bitmap Pan / Zoom / Crop

I need a bit of help. I have an ImageView with a touch listener, and I am able to capture all touch inputs into a matrix, and apply that matrix to the ImageView and VOILA! the image pans and zooms appropriately.
Here is the trouble: I'd now like to CROP the image in such a way that it ALWAYS ends up the same size; eg a 300x300 image.
In other words, suppose I have a 300x300 square in the middle of my screen, a user pans and zooms an image until an item of interest fits into that square, and hits "next". I would like to have a resulting image that has cropped the photo to only be the 300x300 portion that was contained in the box.
Make sense?? Please help! Thanks comrades! See a bit of code below for what I have tried thus far.
float[] matrixVals = new float[9];
ImageTouchListener.persistedMatrix.getValues(matrixVals);
model.setCurrentBitmap(Bitmap.createBitmap(model.getOriginalBitmap(), 0, 0, model.getTargetWidth(), model.getTargetHeight(), ImageTouchListener.persistedMatrix, true));
model.setCurrentBitmap(Bitmap.createBitmap(model.getCurrentBitmap(), Math.round(matrixVals[Matrix.MTRANS_X]), Math.round(matrixVals[Matrix.MTRANS_Y]), model.getTargetWidth(), model.getTargetHeight(), null, false));
Finally, I would also like to be able to SHRINK the image into the box, where the edges may actually need to be filled in with black or white or some kind of border... So far, everything I do other than no pan or zoom at all crashes when I hit next.
Thanks again!
see this custom ImageView, the most important part is onTouchEvent where cropped Bitmap is created and saved to /sdcard for verification:
class IV extends ImageView {
Paint paint = new Paint();
Rect crop = new Rect();
public IV(Context context) {
super(context);
paint.setColor(0x660000ff);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
crop.set(w / 2, h / 2, w / 2, h / 2);
crop.inset(-75, -75);
}
#Override
public void setImageResource(int resId) {
super.setImageResource(resId);
setScaleType(ScaleType.MATRIX);
Matrix m = getImageMatrix();
m.postScale(2, 2);
m.postTranslate(40, 30);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
Bitmap croppedBitmap = Bitmap.createBitmap(crop.width(), crop.height(), Config.ARGB_8888);
Canvas c = new Canvas(croppedBitmap);
c.translate(-crop.left, -crop.top);
c.concat(getImageMatrix());
getDrawable().draw(c);
// just save it for test verification
try {
OutputStream stream = new FileOutputStream("/sdcard/test.png");
croppedBitmap.compress(CompressFormat.PNG, 100, stream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return false;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(crop, paint);
}
}
It's not really clear for me what your problem is, a calculation or a drawing problem ... if it is a drawing problem I might have the solution ...
Create a new bitmap, get the canvas and draw the correct rect of the big image into the new smaller one ....
Bitmap bigPicture; //your big picture
Bitmap bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
Rect source = ...
Rect dest = ...
c.drawBitmap(bigPicture, source, dest, paint);
Now if your problem is a calculation problem I might have a solution too ...

Scale and keep ratio after rotating bitmap by using matrix

I got a picture of a sun, 860x860 pixels.
I want to rotate the sun with the anchor-point being center of the screen.
This is what I got so far:
class GraphicalMenu extends View{
int screenH;
int screenW;
int angle;
Bitmap sun, monster;
public GraphicalMenu(Context context){
super(context);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
sun = BitmapFactory.decodeResource(getResources(),R.drawable.sun,options);
monster = BitmapFactory.decodeResource(getResources(),R.drawable.monster,options);
}
#Override
public void onSizeChanged (int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
screenH = h;
screenW = w;
sun = Bitmap.createScaledBitmap(sun, w, h, true);
monster = Bitmap.createScaledBitmap(monster, w, h, true);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Increase rotating angle.
if (angle++ >360)
angle =0;
Matrix matrix = new Matrix();
matrix.setRotate(angle , getWidth()/2, getHeight()/2);
canvas.drawBitmap(sun, matrix, new Paint());
//Call the next frame.
canvas.drawBitmap(monster,0 , 0, null);
invalidate();
}
}
I've tried to change this line:
sun = Bitmap.createScaledBitmap(sun, w, h, true);
to:
sun = Bitmap.createScaledBitmap(sun, h, h, true);
but then the sun leaves the center of the screen and rotates far out to the right.
How can I fit the sun to the screen?
And how can I make it keep its ratio?
edit
Screenshot from running it on my N5 and the picture of the sun.
If I understand correctly, you have several problems with your code:
If you're making multiple calls to onSizeChanged() then you'll want to keep the original bitmap (pre-scaled) otherwise when you upscale the bitmaps again, they will look pixelated because every time you downscale them, you're losing information.
When you provide a matrix through which to transform your bitmap for drawing onto the canvas, that matrix will apply to the bitmap itself, not the screen as a whole. So when you make the drawBitmap() call, what you are actually requesting is for the bitmap to be rotated around the local point (getWidth()/2, getHeight()/2), not the screen point.
You're not actually rotating around the screen centre, you're rotating around the View's centre. Unless the View takes up the entire screen, you won't get the intended effect.
I can't pinpoint exactly what your problem is, but uploading some images would likely clarify what you're trying to achieve and what your current situation is.

Rotating Bitmap Not Rotating

I have tried several hours to rotate a bitmap with no success. I have read numerous articles about this subject on this web site and it seems the prefered solution involves creating a temporary canvas. Well I did this and I still do not see a roated bitmap.
My bitmap is a 40x40 blue square and I am trying to rotate it 45 degrees. Thats not asking for much is it? When the code runs, the bitmap that appears on the screen is the non-rotated original. ( I have also tried a translate with no success as well)
Here is my code:
// Load the bitmap resource
fillBMP2 = BitmapFactory.decodeResource(context.getResources(), R.drawable.test1);
// Copy to a mutable bitmap
mb = fillBMP2.copy(Bitmap.Config.ARGB_8888, true);
// Create canvas to draw to
Canvas offscreenCanvas = new Canvas (mb);
// Create Matrix so I can rotate it
Matrix matrix = new Matrix();
matrix.setRotate (45);
offscreenCanvas.setMatrix (matrix);
// Send the contents of the canvas into a bitmap
offscreenCanvas.setBitmap(mb);
Later in an OnDraw I do the following:
canvas.drawBitmap(mb, 200, 200, null);
Any ideas what I am doing wrong? Seems like it should work.
Thanks
Try using this
Matrix matrix = new Matrix();
matrix.setRotate(15);
canvas.drawBitmap(bmp, matrix, paint);
setRotation method takes in a float representing
the degrees of rotation.
Try this...
Matrix matrix = new Matrix();
float px = 200;
float py = 200;
matrix.postTranslate(-bitmap.getWidth()/2, -bitmap.getWidth()/2);
matrix.postRotate(45);
matrix.postTranslate(px, py);
canvas.drawBitmap(bitmap, matrix, paint);
You definitely want to use transformations: check out this link.
Basically it's this:
// save the canvas
ctx.save();
// move origin to center
ctx.translate(x,y);
// rotate
ctx.rotate(angle * (Math.PI / 180));
// draw image
ctx.drawImage(image, x, y, w, h, .w/2, h/2, w, h);
// restore
ctx.restore();

Android drawBitmap 5x performance difference

I've been fighting with android performance all night and possibly solved the issue I've been dealing with, however I'm still very confused and could use some help. Consider the timing differences between these two samples.
The first sample loads in a drawable bitmap and creates a mutable copy of it
Bitmap cacheBitmap;
Canvas cacheCanvas;
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (cacheBitmap != null) {
cacheBitmap.recycle();
}
Resources res = getContext().getResources();
Bitmap blankImage = BitmapFactory.decodeResource(res, R.drawable.blank);
/* copy existing bitmap */
cacheBitmap = Bitmap.createScaledBitmap(blankImage, w, h, false);
/* copy existing bitmap */
cacheCanvas = new Canvas();
cacheCanvas.setBitmap(cacheBitmap);
cacheCanvas.drawRGB(255, 255, 255);
}
public void onDraw(Canvas canvas) {
canvas.drawBitmap(cacheBitmap, 0, 0, null); // draws in 7-8 ms
}
The second sample creates a new bitmap without copying the original blank image.
Bitmap cacheBitmap;
Canvas cacheCanvas;
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (cacheBitmap != null) {
cacheBitmap.recycle();
}
Resources res = getContext().getResources();
Bitmap blankImage = BitmapFactory.decodeResource(res, R.drawable.blank);
/* create fresh bitmap */
cacheBitmap = Bitmap.createBitmap(w, h, blankImage.getConfig());
/* create fresh bitmap */
cacheCanvas = new Canvas();
cacheCanvas.setBitmap(cacheBitmap);
cacheCanvas.drawRGB(255, 255, 255);
}
public void onDraw(Canvas canvas) {
canvas.drawBitmap(cacheBitmap, 0, 0, null); // draws in 40 ms
}
The first sample draws 5-6 times faster then the second sample, why is this? I'd like to be able to write this code in some way that doesn't even rely on the blank image, but no matter what I do I end up with a slow bitmap draw without having it available to copy initially.
Check the format of the bitmap. In older versions of Android, there was a bug (feature?) that would always use 565 for bitmaps without alpha and 8888 for bitmaps with alpha when creating the bitmap using certain functions.
I'm tempted to say that somehow one version uses 8888 while the other one uses 565, giving you the speed gain.
Use getConfig to investigate both bitmaps.
Couldn't it be that the createScaledBitmap() actually creates a new bitmap with exactly the proportions needed for the screen, giving a 1:1 pixel draw internally and probably allowing for a faster drawing routine, where the second just creates a new bitmap which contains ALL of the information for the original resource (probably a lot of extra pixels) and each call to draw the bitmap results in an internal scaling between the pixels in the internal bitmap and the canvas that is being drawn to?

Categories

Resources