Scaling issue on Android - why is picture so clumsy?
Asked Answered
S

2

13

I have no 2D graphics and gaming experience. I taught myself by hundreds of mistakes and tens of lost hours. I started with simple Dress Up game. I used Nexus 5x for development where a screen looked OK. When I finished one milestone I tried game on big Lenovo tablet and tiny Samsung Mini phone. It looked horribly.

enter image description here

An original vector PSD file looks perfect, PNG export does not have any issues either. But when I scale the picture it is bumpy. I know it is bitmap. But there is another picture I scale in other place and it looks fine (both pictures are 32 bit):

enter image description here

When I play some game from Google Play they never have scaling issues. How are they implemented? Do they use vectors? Is there some trick?

I put everything into assets folder and though it took 1 MB. I decompiled some apk and they do not have set of variants for each resolution. Though they scale pictures nicely.

Some source code snippets:

protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    this.w = w;
    this.h = h;
    canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    drawCanvas = new Canvas(canvasBitmap);
}

protected void onDraw(Canvas canvas) {
    if (baseBitmap != null) {
        double scale = Math.min((double) w / (double) figure.getW(), (double) h / (double) figure.getH());
        figure.setScaleRatio(scale);
        int sw = (int) (scale * figure.getW());
        int x = ((w - sw) >> 1);
        figure.setX(x);

        paintDressPart(canvas, figure, figure.getMain());
        if (displayedParts != null) {
            for (DressPart dressPart : figure.getParts()) {
                if (displayedParts.contains(dressPart.getId())) {
                    paintDressPart(canvas, figure, dressPart);
                }
            }
        }
    }
}

private void paintDressPart(Canvas canvas, Figure figure, DressPart part) {
    double scale = figure.getScaleRatio();
    int sh = (int) (scale * part.getH());
    int sw = (int) (scale * part.getW());
    int sdx = (int) (scale * part.getDestX());
    int sdy = (int) (scale * part.getDestY());
    scaledRect.set(figure.getX() + sdx, sdy, figure.getX() + sw + sdx, sh + sdy);
    Rect partRect = part.getRect();
    canvas.drawBitmap(baseBitmap, partRect, scaledRect, canvasPaint);
}

And if you want to see it yourself I preparred complete project on GitHub so you can download it and run it yourself:

https://github.com/literakl/DressUp

Update

Downscaling sucks as well. But thanks to Paavnet's comment I realized that Paint matters. See difference on a picture:

enter image description here

I ran demo app on 3,2" AVD image. The picture 278x786 is scaled to 107x303.

October Update

I rewrote the app to use resource folder instead of assets. Android scales pictures and then I rescale it again. Though it looks better than when I do not use Android resources scaling. Resources scaled picture looks usually better than unscaled nodpi / assets picture.

I found that mdpi works best. I even had xxhdpi pictures and it looked worse than mdpi. I think that even on xxhdpi device! But it may be a trouble of the picture that it was not painted well. Android resource scaling may smooth edges on lines.

Squiggle answered 4/9, 2016 at 13:27 Comment(8)
I tried to scale the picture to full screen in portrait mode and it looked nice. Could it be a problem that downscaling looses details?Squiggle
Lots of image downsizing algorithms are buggy if you are reducing the size by more than half. I would try your resize in multiple steps and see if that helps.Phage
I have done a lot of tests with upscaling so far. Basically 150x150 and 250x250 are useless for both small and big screens, while there is minimal visual difference between 320x586, 640x960 or 1242x2208 on any screen.Squiggle
are you using proper bitmap configurations ?Tetany
you need to decide scaling factor based on device screen density. scaling factor should not be fixed or pre defined.Tetany
I do not know what bitmap configuration is. Is it part of the bmp file? Scaling factor - do you mean double scale in question source code? I just divide picture size and view size - is it wrong? It works fine when I scale picture to full screen in portrait mode, unless source is too small (200px).Squiggle
here's how to get your device metrics (e.g. density) WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); final DisplayMetrics displayMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(displayMetrics);Plebe
do you have these values set with pasin paint.setAntiAlias(true); paint.setFilterBitmap(true); paint.setDither(true); if not then try plus this where Bitmap.Config.ARGB_8888 is the important part #4232317Abby
C
1

I faced same issue .. that scaling not that smooth .. best standard method

Bitmap resized = Bitmap.createScaledBitmap(yourBitmap, newWidth, newHeight, true);

You can find more optimized size-convertor in web .. but actually best solution I ended with use :

1st. Use Vector SVG resource .. this is 100% working with perfect quality. just be sure that SVG elements are compatible with android ( see : https://developer.android.com/studio/write/vector-asset-studio.html )

pros:

  • best quality
  • efficient size
  • editable easily

cons:

  • not all elements are supported.

Or

2nd. provide high resolution image (one is enough in xxxhdpi )

pros:

  • good quality

cons:

  • might lead to performance issue

I hope that may help,'.

Caliph answered 10/9, 2016 at 6:22 Comment(2)
Thanks, I will try that method. SVG in Android Studio will generate set of PNG files for each dimension (hdpi, xx). This will make my app bloated. :-(Squiggle
Thanks for hint it looks little better.Squiggle
A
1

Do they use vectors?

SVG/Vector is a handy solution but vector art requires extremely high precision, making it unsuitable for many art styles.It's easy to use vector art with basic shapes but it can be hard to add a simple detail with SVG symbolic styles although it can added easily with paint with png,jpeg. You can watch performance pattern videos on SVG where googlers suggest SVG for icon designs.Most games don't use it for rich and complex images.

So i won't go for SVG specially where small detailing matter a lot in games.

How are they implemented? Is there some trick?

  1. One way is just publish the app with a single regular assets support (MDPI,HDPI) and if the screen density required high resolution then show a dialog to the user, asking him if he wants to download the high-resolution assets.

  2. You can have a higher density images and scale it down at run-time based on phone density requirement using DisplayMetrics.Ideally you should keep the same aspect ratio.Fitting a game made for a 4:3 display looks hideous on a 16:9, but fitting a 16:9 on to a 16:10, nobody might notice.read for more.

You can use this way to easily get the height and width of screen(considering you are using SurfaceView as best practice )

DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int screenHeight = metrics.heightPixels;
int screenWidth = metrics.widthPixels;

To get new width-height on rotation :

surfaceChanged(SurfaceHolder holder, int format, int width, int height)

That method gives you the width/height of your surface, so you could now use:

Bitmap.createScaledBitmap (Bitmap src, int dstWidth, int dstHeight, boolean filter)

From the Bitmap class to re-size your images in proportion to the size of the surface you're drawing onto.You can look into some technique to find appropriate scaling factors.

  1. OpenGL,kotlin can give you advantage for creating rich graphic app.With these you can control the memory and GPU performance which basically are the back-bone of most games plus you can take advantage of controlling the rendering more efficiently with GPU and native memory space plus there is lot more you can think of, it will take some time if you not familiar with these but once you get it they can do many magical tricks.

  2. Webp will help you to reduce size of large images to a considerable difference.Should try webp. (suggestion , webp has a little issue for alpha channel of image on some devices but too good for image size compression)

Conclusion : Implements best practices using OpenGL, SurfaceView,NDK plus loading images efficiently using scaling factors docs link and should try this too.

you can also take a look into 9patch images where you can put constraints to just scale some portion of the image instead of whole.it's quite useful.

Update : As suggested , if you are going to use higher resolution image then also look into this and this to disable scaling or manually calculation scaling factor.Good Luck

Note: Guys feel free to edit this answer with valid info if you like.

Abby answered 12/9, 2016 at 10:21 Comment(12)
Thank you for detailed information. I do not use SurfaceView or OpenGL at this moment. Complete source code is above / at github. I keep aspect ratio when scaling.Squiggle
What do you mean with higher density pictures? I can see 72x72 dpi for this image: github.com/literakl/DressUp/blob/master/app/src/main/assets/…. Shall I ask for higher DPI variant? Or just change this property in editor and save? Thanks.Squiggle
you can keep image let's 512x512 and can scale it down as new bitmap with 72x72 dpi but if you scale-up a 72x72 image for higher display density (big screens) you'll get a blurry effect like you mentioned as a issue, as i suggest try to use OpenGL, SurfaceView because SurfaceView can show frames fast twice as normal view plus OpenGL let you talk to the GPU and memory almost directly so hence save time and more power though opengl is sort of like c,c++ so you can dig in to learnAbby
size can go max around 2560X1600 and min 800X400 so you can select a dpi between range,little above average so do some scaling at run time , you can try 480 dpi which is close to the standard one or take a look into 9patch or webp images too on android.dev siteAbby
Would you be so kind and try to submit code snippet for SurfaceView with scaling bitmap? I heard rumors how ugly implementation in OpenGL is. But it may be not true. Thank you.Squiggle
first you should try your app with single collection of higher resolution images with run-time scaling,will probably solve your problem and yeah openGL can be a nightmare because code need to handle all the memory in arrays , float, bytebuffers, plus texture ,releasing memory etc etc, little long process. plus you don't have to start it from scratch . try find examples on google there will be similar code to suite your requirement. Give it a tryAbby
don't worry about bounty ,you can restart it ,it's late now 12 o clock in morning , office tomorrow , i caught-up by some other question. i am gonna again emphasize on using large images with bitmap.createScaledBitmap with couple of height , width checks will solved your problem to greater extent ,try itAbby
Downsizing does not work either. I ran demo app on 3,2" AVD image. The picture is scaled to 107x303 from 278x786. And it is very ugly as well.Squiggle
go higher , around 500Abby
The source picture is 786, which is higher than 500. And it is scaled to view 303.Squiggle
Downscaling orig Rect(0, 0 - 1196, 1698) to scaled Rect(0, 0 - 419, 596) and the result is ugly. I used smaller picture orig Rect(0, 0 - 598, 849) to scaled Rect(0, 0 - 419, 596) and it sucked as well.Squiggle
i guess you have tried ARGB_8888 while scaling , i used this code setImageBitmap(BitmapFactory.decodeResource(cxt.getResources(),bean.getImg_id(),options) with options.inSampleSize=4; and it worked fine for me to scale down the 512 x 542 image to 60x60 , you can try otherwise i guess last option is generating image of all sizes with webp format to reduce the size will help youAbby

© 2022 - 2024 — McMap. All rights reserved.