Android Bitmap Transformation Math - android

I need to combine 2 images into one. Basically all i need to do is to overlay one of them on top of the other in the center of the image. This needs to work on all major android devices.
I have tried a number of things, but here is my code snippet as of right now (and yes I know it's messed up, we need to figure out delx and dely):
/* Rotate our original photo */
// final float scale = getResources().getDisplayMetrics().density;
canvas.drawBitmap(bmp, 0f, 0f, null);
final float overlay_scale_factor = .5f;
final int overlaywidth = (int)(overlay.getWidth() * overlay_scale_factor);
final int overlayheight = (int)(overlay.getHeight() * overlay_scale_factor);
final int delx = overlaywidth;
final int dely = overlayheight;
Matrix mat = new Matrix();
mat.postRotate(270);
mat.postScale(overlay_scale_factor, overlay_scale_factor);
//mat.postTranslate(-delx, -dely);
canvas.drawBitmap(overlay, mat, null);
/* Bottom image 'composite' is now a composite of the two. */
Any help is appreciated. I know this is just math, but I'm not good at this kind of stuff.
The first image, 'bmp' is 100% the size of the canvas.
The second image, 'overlay' is the overlay that needs to be centered after it's rotated 270 degrees.

Totally untested, but I'd expect something like this to work:
// Set the origin (0,0) in the middle of the view
canvas.translate(width/2, height/2);
// Draw the first bitmap so it is centered at (0,0)
canvas.drawBitmap(bmp, -bmp.getWidth()/2, -bmp.getHeight()/2, null);
// Rotate & scale
canvas.save();
canvas.rotate(270);
canvas.scale(.5f);
// Draw the overlay
canvas.drawBitmap(overlay, -overlay.getWidth()/2, -overlay.getHeight()/2, null);
canvas.restore();

Related

Speeding up bitmap drawing on android

I have been battling with trying to draw a bitmap and then highlighting a region on it with a rectangle. Originally, I was drawing a bitmap with alpha black in paint to make image darker and then on top drawing original bitmap in a region creating effect of highlight. I discovered that largest slowdown was because of alpha in Paint. So I have reworked the code and ended up with following in my draw thread:
private synchronized void drawSquare(int xStart, int yStart, int xEnd, int yEnd) {
Canvas c = holder.lockCanvas();
if(c != null) {
// Draw the background picture on top with some changed alpha channel to blend
Paint paint = new Paint();
paint.setAntiAlias(true);
if(bg != null && cWidth > 0 && cHeight > 0) {
c.clipRect(xStart, yStart, xEnd, yEnd, Region.Op.DIFFERENCE);
c.drawBitmap(bg, gTransform, blackSqr); // Draw derker background
c.clipRect(xStart, yStart, xEnd, yEnd, Region.Op.REPLACE);
c.drawBitmap(bg, gTransform, paint); ///draw original in selection
c.clipRect(0, 0, cWidth, cHeight,Region.Op.REPLACE);
}
Matrix RTcorner = new Matrix();
RTcorner.setRotate(90);
RTcorner.postTranslate(xEnd + 13, yStart - 13);
Matrix RBcorner = new Matrix();
RBcorner.setRotate(180);
RBcorner.postTranslate(xEnd + 13, yEnd + 13);
Matrix LBcorner = new Matrix();
LBcorner.setRotate(270);
LBcorner.postTranslate(xStart - 13, yEnd + 13);
// Draw the fancy bounding box
c.drawRect(xStart, yStart, xEnd, yEnd, linePaintB);
// Draw corners for the fancy box
c.drawBitmap(corner, xStart - 13, yStart - 13, new Paint());
c.drawBitmap(corner, RBcorner, new Paint());
c.drawBitmap(corner, LBcorner, new Paint());
c.drawBitmap(corner, RTcorner, new Paint());
}
holder.unlockCanvasAndPost(c);
}
So this clips out my selection area, I draw with paint that has this code to make it darker.
blackSqr.setColorFilter(new LightingColorFilter(Color.rgb(100,100,100),0));
And in the area inside the clip I draw my original bitmap. It works. But I am not happy with response time. After profiling Bitmap is what takes the longest. I have scaled the bitmap to the size of the screen already so it's drawing 300x800-ish image. The biggest resource hog seems to be the Lighting effect. Because when I turn it off I get decent response time.
So I was wondering if I have missed anything to improve how quickly bitmap is drawn, maybe caching? Or am I just stuck with this because I want darker image and actually should rethink the "highlighting/selection" altogether? Why is is so expensive to draw a bitmap with alpha colour in 2D image?
if I understand what you want, you want a rectangle (with rounded corners) to highlight a part from another image.
if it is that, then I would use an image with the square wit draw9patch and use it as a floating view over the image view
RelativeLaoyut (Image container)
+- ImageView (your actual image)
+- view (it has the square as a background, and you only have to move it to the area you want to highlight)
I'm sorry, I'm not good explaining myself.
For anyone that is interested, perhaps facing similar problem. This solution applies to my particular situation, but I have a separate background bitmap with darkened pixels manually set using:
for(int i = 0; i < cWidth; i++){
for(int j = 0; j < cHeight; j++){
int c = bg2.getPixel(i, j);
float mult = 0.15f;
int r = (int) (Color.red(c) * mult);
int g = (int) (Color.green(c) * mult);
int b = (int) (Color.blue(c) * mult);
bg2.setPixel(i, j, Color.rgb(r, g, b));
}
}
Then use the bg2 to draw main part and the original (not darkened) for the clip rectangle of the selection. There is a bit of overhead for creating and maintaining the second bitmap but the draw speed and response time is quick and smooth in comparison to bitmaps with alpha.

Set zoom ratio of a displayed image in an android canvas

I am trying to write an android app that lets me draw graphics on top of an image then scale and zoom the image with the graphics staying over the same place on the image that have been drawn on top of it while changing the graphics in real time.
However I have been having a lot of issues actually getting it to zoom in while maintaining the center of the image. I have written code where I have a thread that updates the image. Updates are passed in using a class that I created called "PendingUpdate" through an ArrayBlockingQueue. This update contains a desired zoom level which is supposed to be the ratio of the image pixels to the canvas pixels and an image center. However the following code makes it pan while I am zooming which confuses me.
//Scale the image
canvas.scale(pendingUpdate.getZoom(), pendingUpdate.getZoom());
//Translate the image
double updateCx = pendingUpdate.getCenter().getX();
double updateCy = pendingUpdate.getCenter().getY();
double halfCanvasWidthInImagePixels = pendingUpdate.getZoom()*(canvas.getWidth()/2);
double halfCanvasHeightInImagePixels = pendingUpdate.getZoom()*(canvas.getHeight()/2);
double imageTranslateX = updateCx - halfCanvasWidthInImagePixels;
double imageTranslateY = updateCy - halfCanvasHeightInImagePixels;
canvas.translate(-(float)imageTranslateX, -(float)imageTranslateY);
canvas.drawBitmap(pendingUpdate.getImage(), matrix, new Paint());
Thank you for the help!
Edit: here is the full function, I can also post PendingUpdate if this helps, however its just a data class.
private void doDraw(Canvas canvas, PendingUpdate pendingUpdate) {
int iWidth = pendingUpdate.getImage().getWidth();
int iHeight = pendingUpdate.getImage().getHeight();
Matrix matrix = new Matrix();
canvas.drawColor(Color.BLACK);
//TODO: add scrolling functionality to this
if(pendingUpdate.getZoom()>0) {
//Scale the image
canvas.scale(pendingUpdate.getZoom(), pendingUpdate.getZoom());
//Translate the image
double updateCx = pendingUpdate.getCenter().getX();
double updateCy = pendingUpdate.getCenter().getY();
double halfCanvasWidthInImagePixels = pendingUpdate.getZoom()*(canvas.getWidth()/2);
double halfCanvasHeightInImagePixels = pendingUpdate.getZoom()*(canvas.getHeight()/2);
double imageTranslateX = updateCx - halfCanvasWidthInImagePixels;
double imageTranslateY = updateCy - halfCanvasHeightInImagePixels;
canvas.translate(-(float)imageTranslateX, -(float)imageTranslateY);
canvas.drawBitmap(pendingUpdate.getImage(), matrix, new Paint());
}else {
//matrix.postTranslate(canvas.getWidth()-iWidth/2, canvas.getWidth()-iHeight/2);
canvas.drawBitmap(pendingUpdate.getImage(),
(canvas.getWidth()-iWidth)/2,
(canvas.getHeight()-iHeight)/2, null);
}
//TODO: draw other stuff on canvas here such as current location
}
edit 2: This is how I finally got it to work, it was simply a matter of scaling it before translating it.
private void doDraw(Canvas canvas, PendingUpdate pendingUpdate) {
int iWidth = pendingUpdate.getImage().getWidth();
int iHeight = pendingUpdate.getImage().getHeight();
canvas.drawColor(Color.BLACK);
//TODO: add scrolling functionality to this
if(pendingUpdate.getZoom()>0) {
//Scale the image
canvas.save();
double updateCx = pendingUpdate.getCenter().getX();
double updateCy = pendingUpdate.getCenter().getY();
double halfCanvasWidthInImagePixels = (canvas.getWidth()/2);
double halfCanvasHeightInImagePixels = (canvas.getHeight()/2);
double imageTranslateX = updateCx - halfCanvasWidthInImagePixels;
double imageTranslateY = updateCy - halfCanvasHeightInImagePixels;
//canvas.scale(pendingUpdate.getZoom(), pendingUpdate.getZoom(), (float)pendingUpdate.getCenter().getX(), (float)pendingUpdate.getCenter().getY());
canvas.scale(pendingUpdate.getZoom(),
pendingUpdate.getZoom(),
canvas.getWidth()/2,
canvas.getHeight()/2);
canvas.translate(-(float)imageTranslateX,
-(float)imageTranslateY);
canvas.drawBitmap(pendingUpdate.getImage(), 0, 0, null);
canvas.restore();
}else {
//TODO: update this so it displays image scaled to screen and updates current zoom somehow
canvas.drawBitmap(pendingUpdate.getImage(),
(canvas.getWidth()-iWidth)/2,
(canvas.getHeight()-iHeight)/2, null);
}
//TODO: draw other stuff on canvas here such as current location
}
}
If I were you, I'd use the Canvas.scale(float sx, float sy, float px, float py) method which does exactly what you want.
However looking at your code I think you might be messing with too many transformations at once, which is harder to debug.
Always (and I mean always) call Canvas.save() and Canvas.restore() on the initial matrix you're getting in Canvas if you plan to alter it. This is because the Canvas that you get to draw on may be the canvas for e.g. the whole window with just clipping set to the boundaries of the control that is currently drawing itself.
Use matrix transformation method provided by the Canvas method and draw bitmap using the simplest invocation.
Following these two advices look at the whole View I have just made up, that scales the bitmap by a factor of 3 with point (16,16) set as the pivot (unchanged point - center of scaling). Tested - working.
public class DrawingView extends View {
Bitmap bitmap;
public DrawingView(Context context) {
super(context);
bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
}
#Override
protected void onDraw(Canvas canvas) {
float sx = 3;
float sy = 3;
float px = 16;
float py = 16;
canvas.save();
canvas.scale(sx, sy, px, py);
canvas.drawBitmap(bitmap, 0, 0, null);
canvas.restore();
}
}

Perspective correction of imageview

I'm working on an app that needs to apply perspective distortion correction to a photo taken with the phone's camera.
Once the photo is taken, the idea is to show it on an imageview and let the user mark the four corners of the document (a card, a sheet of paper, etc.) and then apply the correction based on those points.
This is an example of what im trying to achieve:
http://1.bp.blogspot.com/-ro9hniPj52E/TkoM0kTlEnI/AAAAAAAAAbQ/c2R5VrgmC_w/s640/s4.jpg
Any ideas on how to do this on android?
You don't have to use a library for this.
You can just as well use one of the drawBitmap functions of the Canvas class with a matrix that's initialized using the setPolyToPoly function of the Matrix class.
public static Bitmap cornerPin(Bitmap b, float[] srcPoints, float[] dstPoints) {
int w = b.getWidth(), h = b.getHeight();
Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
Canvas c = new Canvas(result);
Matrix m = new Matrix();
m.setPolyToPoly(srcPoints, 0, dstPoints, 0, 4);
c.drawBitmap(b, m, p);
return result;
}
(The Paint object is only needed to enable anti-aliasing.)
Usage:
int w = bitmap.getWidth(), h = bitmap.getHeight();
float[] src = {
0, 0, // Coordinate of top left point
0, h, // Coordinate of bottom left point
w, h, // Coordinate of bottom right point
w, 0 // Coordinate of top right point
};
float[] dst = {
0, 0, // Desired coordinate of top left point
0, h, // Desired coordinate of bottom left point
w, 0.8f * h, // Desired coordinate of bottom right point
w, 0.2f * h // Desired coordinate of top right point
};
Bitmap transformed = cornerPin(bitmap, src, dst);
Where src are the coordinates of the source points, dst are the coordinates of the destination points. Result:
What you want to do goes under various names of art, "corner-pin" being the one commonly used in the visual effects industry. You need to proceed in two steps:
Compute the mapping from the the desired, rectified image, to the original, distorted, image
Actually warp the original image according to the mapping computed in (1).
The 4 (non-collinear, perspective-distorted) corners of the original image, and the 4 corners of the target (undistorted) image, define the mapping. This mapping is called a "homography" - read the pointed wikipedia page for details. Once the mapping is known, the warping at step (2) can be computed by interpolation: for every pixel in the target image, find the corresponding pixel in the original image. As this will typically not be at integer coordinates, you interpolate its color from the neighbors. Various interpolation schemes are used, the common ones being nearest-neighbor, bilinear and bicubic (in increasing order of smoothness in the results).
For Android, I'd recommend installing the OpenCV SDK , and then use the geometry transformation routines (getPerspectiveTransform and warpPerspective for the two steps above).

Compass with bitmap is going everywhere in the screen

i want my compass to spin like this
but my result is that:
the compass is going everywhere in my screen...
where is my problem please?this is my compass.java code:
#Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.GRAY);
int w = canvas.getWidth();
int h = canvas.getHeight();
int cw = w / 2;
int ch = h / 2;
Bitmap myBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.compass);
canvas.translate(cw, ch);
if (mValues != null) {
canvas.rotate(-mValues[0]);
}
int cx = (mWidth - myBitmap.getWidth()) / 2;
int cy = (mHeight - myBitmap.getHeight()) / 2;
canvas.drawBitmap(myBitmap, cx, cy, null);
}
p.s.: i m sorry for the bad pictures but i really dont know how to explain my problem in english!Thanks
Since you have already translated to the center of the canvas, you may only need to offset the compass with its half width/height to center it. Try:
int cx = -myBitmap.getWidth() / 2;
int cy = -myBitmap.getHeight() / 2;
canvas.drawBitmap(myBitmap, cx, cy, null);
Also to get a good hang of transformations (translate, rotate), read The OpenGL Red book chapter 3, specifically the part Thinking about Transformations. While this is about OpenGL, you can use the knowledge for non-OpenGL transforms too.
EDIT:
Think in turtle logic. Your first translation takes your pencil to the center of your canvas. The rotation rotates your pencil. So now you could draw the compass exactly where the pencil is (no offsets), except that drawing the compass image is done starting from its top-left corner instead of its center. Therefore you need a last translation of (-compassWidth/2, -compassHeight/2). Note that this translation already occurs on the rotated x & y axes. Also note that you may pass 0/0 for cx/cy in drawBitmap if you manually apply that translation to the canvas itself.
So the full sequence is: translate to canvas center, rotate, translate negated to image center.
Don't decode the Bitmap in onDraw - do it when the view is created and reuse the Bitmap.
Make a Matrix and matrix.postRotate(mValues[0], half_width_of_bitmap, half_height_of_bitmap); and matrix.postTranslate(cw, ch);
Draw the bitmap with canvas.drawBitmap(bitmap, matrix, null);

Sprite Rotation in Android using Canvas.DrawBitmap. I am close, what am I doing wrong?

I have this sprite rotating algorithm (its poorly named and just used for testing). It is so close, sprites drawn with it do rotate. Everyframe I can add +5 degrees to it and see my nice little sprite rotate around. The problem is, the other stuff drawn to the canvas now flickers. If I don't do the rotation the regular drawn sprites work great. I think I am close but I just don't know what piece I am missing. Below is my two "Draw_Sprite" methods, one just draws the previously resource loaded bitmap to the canvas passed in. The other one, does some rotation the best I know how to rotate the sprite by so x many degrees..and then draw it. If I have a nice game loop that draws several objects, one type is the rotated kind. Then the non-rotated sprites flicker and yet the rotated sprite never does. Though if I draw the non-rotated sprites first, all is well, but then the Z-Ordering could be messed up (sprites on top of UI elements etc)... The method definitions:
/*************************************************
* rotated sprite, ignore the whatever, its for ease of use and testing to have this argument list
* #param c canvas to draw on.
* #param whatever ignore
* #param rot degrees to rotate
* #return
*/
public int Draw_Sprite(Canvas c, int whatever, int rot) {
//rotating sprite
Rect src = new Rect(0, 0, width, height);
Rect dst = new Rect(x, y, x + width, y + height);
Matrix orig = c.getMatrix();
mMatrix = orig;
orig.setTranslate(0, 0);
orig.postRotate(rot, x+width/2, y+height/2);
c.setMatrix(orig);
c.drawBitmap(images[curr_frame], src, dst, null);
c.setMatrix(mMatrix); //set it back so all things afterwards are displayed correctly.
isScaled=false;
return 1;
}
/********************************************************
* draw a regular sprite to canvas c
* #param c
* #return
*/
public int Draw_Sprite(Canvas c) {
Rect src = new Rect(0, 0, width, height);
Rect dst = new Rect(x, y, x + width, y + height);
c.drawBitmap(images[curr_frame], src, dst, null);
isScaled=false;
return 1;
}
And now the usage:
void onDraw(Canvas c)
{
canvas.drawRect( bgRect, bgPaint); //draw the background
//draw all game objects
// draw the normal items
for (GameEntity graphic : _graphics) {
graphic.toScreenCoords((int)player_x, (int)player_y);
if(graphic.getType().equals("planet")) //draw planets
graphic.Draw_Sprite(canvas); //before the rotation call draws fine
else
{
//rotate all space ships every frame so i see them spinning
//test rotation
mRot +=5;
if(mRot>=360)
mRot=0;
graphic.Draw_Sprite(canvas, 0, mRot); //yes function name will be better in future. this rotates spins draws fine
}
}
thePlayer.Draw_Sprite(canvas); //FLICKERS
drawUI(canvas);//all things here flickr
}
So it does do it, things after a call to a rotational draw are drawn correctly. But the problem is it flickrs. Now One could say I should just do all my non rotational stuff and save that last, but the zordering would be off.... suggestions as to how to tackle this issue of zordering or the flickering?
Just for the next guy who may read this. You can do this with only a few lines of code:
canvas.save();
canvas.rotate(rotation_angle, x + (widthofimage / 2), y + (heightofimage / 2));
canvas.drawBitmap(bitmap, x, y, null);
canvas.restore();
Try using canvas.save() before the rotation and canvas.restore() after manipulation is complete.
When performing manipulations on the canvas in order to change the way an object is drawn you have to remember the manipulations set how the canvas handles origins etc... So if you translate or rotate the canvas, that will be set for the lifetime of that canvas. In order to avoid this you first call save, which saves a snapshot of the canvas matrix before you manipulate it, then you run all your changes, then call restore which will restore the canvas back to the last saved point. Otherwise all your changes build up and you get unintended results.

Categories

Resources