3

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

image

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);`
pats
  • 1,273
  • 2
  • 20
  • 43
  • Rendering a hi res image onto a fixed size canvas means you have more source pixels per destination pixel than with a smaller sized source image, the result is a better quality down sampling for the larger image. To get the same result for the smaller images, create them from the same hi res source at the resolution they need to be rendered at (ie the scale should be 1) – Blindman67 May 19 '17 at 20:53
  • Thanks for your comment. The Large bitmap is 2x of canvas size (drawn to max zoom level). Small images are segments of large bitmap, small in size but their scale is 2. The Scale factor range is therefore .5 to 1 (means zoom level is 2). The issue occurs when scale size is 0.5 (i.e normal canvas size but half of image). Form the right hand side image lines are impacted significantly at 0.5 level. Do you think I have to create multiple images based on zoom level? or all small images should be the size of big image? Both approaches are problematic given In some cases I have 400 small images. – pats May 22 '17 at 04:25
  • 1
    I have looked carefully at the image and the lower quality matches the quality loss that bilinear down sampling produces. You have bilinear filtering on `canvasPaint.setFilterBitmap(true);` so there is not anything programmatically that you can do to improve the quality. As the loss of aliasing is only noticeable on the high contrast lines the best option would be to render the jigsaw outlines as a path over the bitmap on the fly, removing the lines from the original images This will give you consistent line quality. – Blindman67 May 22 '17 at 13:07
  • Great idea. I tried it out however cannot be used because performance hit is significant. Just drawing 400 images takes 30ms (frame time), but image + outline takes 150ms. – pats May 24 '17 at 10:12
  • The image must be scaled all at once. But if you scale each part separately and then put them together again the border between them will not look good. The separation in parts should be made after the scaling of the whole. – from56 May 25 '17 at 17:23
  • I am using surfaceview canvas, and its scale() method to scale. Frame rates drops if I draw parts on a image and draw that on canvas. Is there a way to draw parts and scale all together later to improve border issues. – pats May 26 '17 at 00:31

1 Answers1

2

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.

James Poag
  • 2,320
  • 1
  • 13
  • 20
  • You were correct. Border is the main issue. Can you please explain Approach 1. Is this same as single image approach explained in my question. Draw small images on a big image and draw the big image on final canvas? – pats May 24 '17 at 10:19
  • I tried out second approach. That reduced the issue significantly. Do you know how to set alpha to zero selectively on pixels. I am currently using porterDuffPaint.setXfermode(new PorterDuffXfermode( PorterDuff.Mode.SRC)); to cut the images of the pieces. What technique can I use to implement the second approach. – pats May 24 '17 at 10:58
  • Approach #1 is slow, but easy to perform. Instead of drawing directly to the screen you draw to FBO (Render-To-Texture). Then draw the rendered texture to the screen. There are several Nehe tutorials on this. – James Poag May 24 '17 at 12:10
  • As for PorterDuff, I spent a little while looking over the equations that PorterDuff offers, as well as ComposeShader and I have to say that it would be easier to hand copy the pixels. What you want is the source image color and the puzzle piece alpha. None of the PorterDuff modes have this equation, and I can't figure out how to write a custom ComposeShader that satisfies this problem. In OpenGLES, you can separate the alpha and color blending functions. Once you figure out how RenderTargets work, composing the pieces will be much faster. You can even write a shader to do this. – James Poag May 24 '17 at 12:16
  • It looks like Render-To-Texture is OpenGL. I am not using OpenGL. Performance of OpenGL was lower than Multi image on canvas approach. I ll try the second approach and report back results. BTW is there a way to implement zoom without using canvas.scale. If I draw bitmaps using https://stackoverflow.com/questions/35678175/how-to-scale-bitmap-drawn-on-the-canvas-while-saving-rotation-and-translation-sp, the draw quality is ok. So can I use that to implement zooming and panning some other way, maintaining canvas scale at 1. – pats May 24 '17 at 23:21
  • Without access to OpenGL, and with PortDuff missing the separation of the alpha/color channels with the composition operations, it looks like you would need to get/set pixels on the bitmaps. This is much slower and I would recommend using a Runnable to cut your pieces. – James Poag May 25 '17 at 00:47
  • Matrices can be multiplied together to compound their effects. I'd have to look, but you might be able to save() a matrix into Canvas that scales/pans all future drawing. https://developer.android.com/reference/android/graphics/Canvas.html#save() – James Poag May 25 '17 at 00:51