I need some advice regarding efficient use of canvas and matrices
I have a source bitmap "B0" loaded in memory, which is WxH
I have a bitmap B1, onto which I draw with a canvas.
There is a rotated (with an arbitrary angle) rectangular portion
(w_p*h_p) of B0. I need to get this portion and draw it, once unrotated, onto B1
I would like to do it with normal Views, canvas and matrices. Not surfaceviews, not opengl
An "already working" approach is:
Rotate the bitmap B0 to compensate for the selected zone rotation -->
we get B0_r
Calculate the translated rectangle, which will now be unrotated. We
have SrcRect_u
With a canvas, draw the selected rectangle of B0_r (srcRect_u) onto
B1
However, if B0 is large enough, the rotation operation is very expensive since it applies to the whole bitmap. Also, it means to create an intermediate bitmap each time.
I need to repeat this step in a gameloop, where this rectangle (srcRect) can be rotated and translated, so it must be a "cheap" operation to achieve this
My question: is there a better approach in terms of efficiency , using canvas, matrices and "normal" Views?
EDIT
To better illustrate what I mean, I have added some pics.
B0, with the rotated selection zone
B0 rotated. Now the selection zone is unrotated
unrotated selection zone drawn onto a part of B1
Yes. Rotate the canvas you're going to be drawing to via canvas.rotate. Then draw the subset of the bitmap you wish via drawCanvas. This will have the effect of drawing a rotated bitmap, without rotating the bitmap or creating an intermediate. If you want to draw some things rotated and some unrotated, save the matrix (via canvas.save), rotate it, draw the bitmap, then restore (via canvas.restore)
Related
I have a set of small images. If I draw these images individually on canvas, the draw quality is significantly low, compared to the case where I draw them on a screen size large bitmap and draw that bitmap on the canvas. Specially the lines get distorted. See the below (right side).
From the code below, the canvas also supports zooming (scaling). This issue occurs on small scale factors.
Question is how to improve the draw quantity of multiple small images to the standard of large image.
This is a code of multiple bitmaps drawn on canvas
canvas.scale(game.mScaleFactor, game.mScaleFactor);
canvas.translate(game.mPosX, game.mPosY);
for (int i = 0; i < game.clusters.size(); i++) {
Cluster cluster = game.clusters.get(i);
canvas.drawBitmap(cluster.Picture, cluster.left,
cluster.top, canvasPaint);
}
This is the code for single bitmap, game.board is a screen size image which has all the small bitmaps drawn on.
canvas.scale(game.mScaleFactor, game.mScaleFactor);
canvas.translate(game.mPosX, game.mPosY);
canvas.drawBitmap(game.board, matrix, canvasPaint)
The paint brush has following properties set.` All bitmaps are Bitmap.Config.ARGB_8888.
canvasPaint.setAntiAlias(true);
canvasPaint.setFilterBitmap(true);
canvasPaint.setDither(true);`
I can think of a couple, depending on you you are drawing the borders of the puzzle pieces.
The problem you are having is that when the single image is scaled, the lines are filtered with the rest of the image and it looks smooth (the blending is correct). When the puzzle is draw per-piece, the filtering reads adjacent pixels on the puzzle piece and blends them with the piece.
Approach 1
The first approach (one that is easy to do) is to render to FBO (RTT) at the logical size of the game and then scale the whole texture to the canvas with a fullscreen quad. This will get you the same result as single because the pixel blending involves neighboring pieces.
Approach B
Use bleeding to solve the issue. When you cut your puzzle piece, include the overlapping section of the adjacent pieces. Instead of setting the discarded pixels to zero, only set the alpha to zero. This will cause your blending function to pickup the same values as if it were placed on a single image. Also, double the lines for the border, but set the outside border alpha to zero.
Approach the Final
This last one is the most complicated, but will be smooth (AF) for any scaling.
Turn the alpha channel of your puzzle piece into a Signed Distance Field and render using a specialized shader that will smooth the output at any distance. Also, SDF allows you to draw the outline with a shader during rendering, and the outline will be smooth.
In fact, your SDF can be a separate texture and you can load it into the second texture stage. Bind the source image as tex unit 0, the sdf puzzle piece cutout(s) on tex unit 1 and use the SDF shader to determine the alpha from the SDF and the color from tex0, then mix in the outline as calculated from the SDF.
http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf
https://github.com/Chlumsky/msdfgen
http://catlikecoding.com/sdf-toolkit/docs/texture-generator/
SDF is generated from a Boolean map. Your puzzle piece cutouts will need to start as monochrome cutout and then turned into SDF (offline) using a tool or similar as listed above. Valve and LibGDX have example SDF shaders, as well as the tools listed above.
Why does drawing a shape to a bitmap cause the shape to be blurry versus drawing the shape straight to the canvas?
Bitmap is a rasterized computer image, consisting of points (pixels).
The Canvas class holds the "draw" calls.
A shape is an abstract definition of shape (vector-like).
drawing the shape straight to the canvas
That's not accurate, maybe you mean straight to the screen of device, which is Bitmap too.
To use Canvas for drawing, you need the canvas to host the calls, some bitmap as target, and some drawing primitive (shape is one), and Paint (contains information how to paint the drawing primitive).
Once you draw some shape into the target bitmap, it will be aliased to the pixels of target bitmap. Ie. an circle will turn into some approximation created by rectangular pixels of target bitmap.
What you probably see in your particular case is, that your Bitmap has lower resolution than screen, and when you draw that low-res bitmap to the target screen bitmap, it is upscaled by some filter which makes the upscaled picture a bit blurry to avoid big rectangular pixelation (or it may be also other way, downscaling hi-res bitmap, which contains too sharp/thin contours, which will be aliased second time when downscaled, and even with anti-aliasing filtering it will get blurred a bit). Or maybe you use some paint with settings causing blur (unlikely, can't think of creating one by accident, you would knew).
If you will use for both targets Bitmap of identical density (resolution of single pixel), and the same paint method, then the result will be same, and also drawing the shape from bitmap to bitmap as long as you will use whole pixel coordinates/size, and no filtering, again the result will be same as drawing directly to screen bitmap.
So start first by checking the size of your bitmap vs screen bitmap, and then check paint settings and additional canvas arguments to draw the bitmap, whether you don't upscale/downscale it with some kind of filtering.
im working on an app, that displays large(around 2000x2000px) bitmap in imageview. This image has to be that large since user can pinch to zoom it in order to see some details. App has to be able to draw circles on that image, and also to display image alone, without circles on it. I was using 2 layers but the problem is memory since 2k x 2k px is around 16mb of memory, and creating another bitmap(another 16mb), just to draw a few circles, is pointless in my opinion. Is there any way, that you can draw simple primitives on image, and also be able to display it without primitives(circles in my case)?
Maybe somehow to store only modified pixels or sth?
Thanks!
You don't need to make another 2000x2000 Bitmap to draw those circles on. Just 'prerender' a circle, and then choose where you draw it.
I'm working under the assumption that you're drawing your 'big' image on a Canvas, since you have zooming features etc.
If you're not, you'll need to override your SurfaceView's onDraw(Canvas canvas) method so that you can access the SurfaceView Canvas. I won't go into depth about that part since again I'm assuming you have it, but if not the implementation of that function would look like this:
//Overriding SurfaceView onDraw(Canvas canvas)
#Override
protected void onDraw(Canvas surfaceCanvas) {
if(canvas == null) return; //No Canvas? No point in drawing then.
surfaceCanvas.drawColor(Color.BLACK);
//Draw your 'big' image on the SurfaceView Canvas
insertYourBigImageDrawingFunctionHere(surfaceCanvas);
//Now draw your circles at their correct positions...
insertCircleDrawingFunctionHere(surfaceCanvas);
}
Now that you have access to the SurfaceView Canvas, you can choose precisely how things are drawn on it. Like circles for example...
I want to draw your attention to the multiple Canvas' being used below (surfaceCanvas vs. circleCanvas). I once thought that Canvas was a kind-of 'one Canvas for the whole app/activity' implementation, but it isn't. You are free to create Canvas' as you please. It is merely an instance of a tool to draw onto Bitmaps. This was a HUGE revelation for me, and gave me much more robust control over how Bitmaps are composed.
public void myCircleDrawingFunction(Canvas surfaceCanvas){
//Make a new Bitmap for your circle
Bitmap.Config conf = Bitmap.Config.ARGB_4444;
tinyCircleBMP = Bitmap.createBitmap(10,10, conf);
//Make a new canvas using that Bitmap as the source...
Canvas circleCanvas = new Canvas(cacheBmp);
//Now, perform your drawing on the `Canvas`...
Paint p = new Paint();
circleCanvas.drawCircle(5, 5, 5, p);
//Now the `Bitmap` has a circle on it, draw the `Bitmap` on the `SufaceView Canvas`
surfaceCanvas.drawBitmap(tinyCircleBMP, 10, 10, p);
//Replace the '10's in the above function with relevant coordinates.
}
Now obviously, your circles will zoom/pan differently to your 'big' image, since they are no longer being drawn at the same size/position of the 'big' image. You will need to consider how to translate the positions of each circle taking into account the current scale and position of the 'big' image.
For example, if your image is zoomed in to 200%, and a circle is supposed to appear 100px from the left of the big image, then you should multiply the pixel values to take into account the zoom, like this
(PsuedoCode):
drawCircleAtX = Bitmap.left * BitmapZoomFactor
If you are using the canvas API (if not I would suggest to)? if so you are just draw your image on the canvas and then the primitive shapes on top of the same canvas before display. This way you just keep a reference of the circles position in some basic data types and scale them as the user moves around and zooms, so you know where to draw them each frame.
Can't get android.graphics.picture objects to rotate, but translates are OK. No camera involved in the following code.
For making dynamic sprites, recording drawing primitives into an android.graphics.picture object for later use seems consistently more efficient than doing individual primitive draws during onDraw. And a Picture object is very similar to a bitmap, (if not the same?) and can be saved and restored to / from a bitmap.
However, just to be able to rotate these Pictures, I'd like to avoid having to save them as Bitmaps after recording() them. That is, just use the Picture objects like bitmaps, which the canvas api seems to imply is possible. Perhaps the Picture API is less sophisticated, but more likely I'm just doing this wrong.
The canvas API for Bitmaps versus Pictures are not parallel, e.g. there is a:
canvas.drawBitmap(bitmap_Sprite, matrix, null); // Draw sprite with matrix, no Paint
And there is a:
canvas.drawPicture(picture_Sprite, destination_rectangle); Draw sprite into rect.
But there isn't a:
canvas.drawPicture(picture_Sprite, matrix ...)
Details:
Imagine both bitmap_Sprite and picture_Sprite are a "compass arrow" indicating where it's pointing during rotation.
In the onDraw() call, using the bitmap version, this works:
matrix = new matrix();
matrix.setRotate(angle); // angle is ++ during onDraw(canvas)
// Draw arrow, matrix rotates it as expected:
canvas.drawBitmap(bitmap_Sprite, matrix, null); // null = no Paint obj
But attempting the same thing using the picture object, the only way I could figure this out was to rotate the the destination_rectangle (containing the arrow) like this:
//destination_rectangle sized correctly to wrap previously recorded picture obj.
matrix2 = new matrix();
matrix2.setRotate(angle); // angle is ++ during onDraw(canvas)
matrix2.mapRect(destination_rectangle); // apply rotation
canvas.drawPicture(picture_Sprite, destination_rectangle);
But all this does is pivot the rectangle around a location, it doesn't rotate the rectangle (and the arrow) itself.
Questions:
Is using the destination rect. the wrong way to do this?
Is rotation even possible using a Picture object?
Certainly, if I can't get Picture objects to rotate, then I could just use previously created bitmaps / mipmaps directly. So then
Generally, assuming it is possible to use Picture objects the same way as Bitmaps, am I really saving overhead using Pictures which are dynamically created once versus dynamically creating them, saving them as bitmaps, and reloading the bitmaps to do the matrix stuff?(!)
just use Canvas.concat, you will probably want save/restore as well
My question may be a bit unclear, but I have extended the View class and generated a number of shapes on the canvas around (0,0). I want to put this point in the middle, so I have to tell the View that it has to draw horizontally, for example, from -640 to 640 on the x-axis and vertically, for example, from -360 to 360 on the y-axis.
Is there a way to tell the view that it has to draw these pixels without changing the coordinates of the drawn shapes. I just want to tell the view that it has to draw certain coordinates.
I want to be able to change dynamically which area is drawn.
I'm not 100% what you are trying to achieve, but if you want to move and scale your shapes, you can use the canvas translate or scale methods, to move the canvas to under your shapes. Remember that it is the canvas you translate, and not the shape, so the transformations will have to be done in reverse. You should also use the canvas save and restore methods to restore your canvas position between transformations.
If you instead want to limit any drawing to an area, you can use the canvas clip-methods, for example:
canvas.clipRect(-640, -360, 640, 360);
Would case any drawing outside that rectangle to be discarded.