8

I have this:

paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setDither(true);

sm = new Matrix();
sm.setScale(scale, scale);

private Bitmap getImage(String n) {
    File dir = context.getDir("theme", Context.MODE_PRIVATE);
    File file = new File(dir, n + ".png");
    if (file.exists()) {
       return BitmapFactory.decodeFile(file.getAbsolutePath());
    } else {
        return BitmapFactory.decodeResource(getResources(), getResources().getIdentifier(n, "drawable", getPackageName()));
    }
}

private Bitmap resizeImage(Bitmap b) {
    return Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), sm, true);
}

public void onTouchEvent(MotionEvent event) {
    #scrollable bitmaps, parallax effect
    updatePosition();
}

private void draw() {
    current_time = System.currentTimeMillis();
    if (current_time - last_update_time >= 25) {
        SurfaceHolder holder = getSurfaceHolder();
        Canvas c = null;
        try {
            c = holder.lockCanvas();
            if (c != null) {
                c.drawBitmap(bitmap1, bitmap1_x, bitmap1_y, paint);
                c.drawBitmap(bitmap2, bitmap2_x, bitmap2_y, paint);
                ...
                c.drawBitmap(bitmap20, bitmap20_x, bitmap20_y, paint);
            }
        } finally {
            if (c != null)
                holder.unlockCanvasAndPost(c);
        }
        last_update_time = current_time;
    }
}

I'm resizing image to a bigger size, not smaller.

  1. Without resize, works very good, perfomance 100%

    bitmap1 = getImage("bitmap1"); ... bitmap20 = getImage("bitmap20");

  2. With resize, performance 80%

    bitmap1 = getImage("bitmap1"); ... bitmap20 = getImage("bitmap20");

    called once, when screen width and height are known bitmap1 = resizeImage(bitmap1); ... bitmap20 = resizeImage(bitmap20);

  3. Without resize, canvas scale, performance 40%

    bitmap1 = getImage("bitmap1"); ... bitmap20 = getImage("bitmap20");

    set canvas.scale(scale, scale) inside draw() method

I know there are some frameworks like libgdx resizing images without loosing performance, but I'm using native canvas.

Question: How do I draw resized images with 100% performance?

UPDATE Tried to make min sample.

mWallpaperService.java

import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.os.BatteryManager;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.service.wallpaper.WallpaperService;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.Scroller;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;

public class mWallpaperService extends WallpaperService {

    @Override
    public Engine onCreateEngine() {
        return new mEngine();
    }

    private class mEngine extends Engine {
        private final Handler handler = new Handler();
        private final Runnable drawRunner = new Runnable() {
            @Override
            public void run() {
                draw();
            }

        };

        private Context context;
        private Paint paint;
        private boolean visible = true;
        private boolean draw = true;

        private int width, height;
        private float scale;

        private float begin_x, move_x;
        private int touch_cnt = 0;

        private int bg_max_x;
        private Bitmap bg1, bg2, bg3, bg4..., bg20;
        private float bg1_x, bg1_x2, bg2_x, bg3_x, bg4_x..., bg20_x;
        private float bg1_y, bg2_y, bg3_y, bg4_y..., bg20_y;
        private float bg2_pr, bg3_pr, bg3_pr..., bg20_pr;
        private float bg1_offset_x, bg2_offset_x, bg3_offset_x, bg4_offset_x..., bg20_offset_x;


        private long current_time;
        private long last_update_time;
        private Matrix sm;
        Scroller mScroller;

        public mEngine() {
            context = getApplicationContext();
            mScroller = new Scroller(context);

            bg1 = getImage("bg1");
            bg2 = getImage("bg2");
            bg3 = getImage("bg3");
            bg4 = getImage("bg4");
            ...
            bg20 = getImage("bg20");

            handler.post(drawRunner);
        }

        @Override
        public void onVisibilityChanged(boolean visible) {
            this.visible = visible;
            if (visible) {
                draw = true;
                handler.post(drawRunner);
            } else {
                draw = false;
                handler.removeCallbacks(drawRunner);
            }
        }


        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            this.visible = false;
            handler.removeCallbacks(drawRunner);
        }

        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            if (this.width != width && this.height != height) {
                scale = (float) height / bg1.getHeight();
                this.width = width;
                this.height = height;

                sm = new Matrix();
                sm.setScale(scale, scale);

                bg1 = resizeImage(bg1);
                bg2 = resizeImage(bg2);
                bg3 = resizeImage(bg3);
                bg4 = resizeImage(bg4);
                ...
                bg20 = resizeImage(bg20);

                bg_max_x = bg1.getWidth() - width;
                bg1_x = bg_max_x / 2;
                bg1_y = 0;

                #scroll_speed getting from preferences, 0.1f - 1f
                scroll_length = bg_max_x * scroll_speed;

                mScroller.setFinalX((int) bg1_x);
                mScroller.abortAnimation();

                bg2_pr = 0.2f;
                bg2_offset_x = width / 2 - bg2.getWidth() / 2 + bg1_x * bg2_pr;
                bg2_y = height - bg2.getHeight();

                bg3_pr = 0.3f;
                bg3_offset_x = width / 2 - bg3.getWidth() / 2 + bg1_x * bg3_pr;
                bg3_y = height - bg3.getHeight();

                bg4_pr = 0.4f;
                bg4_offset_x = width / 2 - bg4.getWidth() / 2 + bg1_x * bg4_pr;
                bg4_y = height - bg4.getHeight();
                ...

                updatePosition();
            }

            super.onSurfaceChanged(holder, format, width, height);
        }

        private void updatePosition() {
            bg2_x = bg2_offset_x - bg1_x * bg2_pr;
            bg3_x = bg3_offset_x - bg1_x * bg3_pr;
            bg4_x = bg4_offset_x - bg1_x * bg4_pr;
            ...
            bg20_x = bg20_offset_x - bg1_x * bg20_pr;
        }

        @Override
        public void onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    begin_x = event.getX();
                    move_x = 0;
                    touch_cnt = 0;
                    break;
                case MotionEvent.ACTION_UP:
                    x = event.getX();
                    if (touch_cnt >= 1)
                        fling();
                    break;
                case MotionEvent.ACTION_MOVE:
                    x = event.getX();
                    touch_cnt++;
                    //TODO drag
                    break;
            }
        }

        private boolean fling() {
            if (!mScroller.isFinished()) {
                mScroller.forceFinished(true);
            }
            if (move_x != 0) {
                bg1_x2 = mScroller.getCurrX() + move_x;
                if (bg1_x2 <= 0) {
                    bg1_x2 = 0;
                } else if (bg1_x2 > bg_max_x) {
                    bg1_x2 = bg_max_x;
                }

                if (bg1_x != bg1_x2) {
                    mScroller.fling(mScroller.getCurrX(), (int) bg1_y, -(int) (bg1_x > bg1_x2 ? 10000 : -10000), 0, mScroller.getCurrX() - scroll_length <= 0 ? 0 : (int) (mScroller.getCurrX() - scroll_length), mScroller.getCurrX() + scroll_length >= bg_max_x ? bg_max_x : (int) (mScroller.getCurrX() + scroll_length), 0, bg1.getHeight());
                    return true;
                }
            }
            return false;
        }

        private void draw() {
            current_time = System.currentTimeMillis();

            if (mScroller.computeScrollOffset()) {
                bg1_x = mScroller.getCurrX();
                updatePosition();
                draw = true;
            }

            if (draw && current_time - last_update_time >= 25) {
                SurfaceHolder holder = getSurfaceHolder();
                Canvas c = null;
                try {
                    c = holder.lockCanvas();
                    if (c != null) {
                        c.drawBitmap(bg1, -bg1_x, bg1_y, null);
                        c.drawBitmap(bg2, bg2_x, bg2_y, null);
                        c.drawBitmap(bg3, bg3_x, bg3_y, null);
                        c.drawBitmap(bg4, bg4_x, bg4_y, null);
                        ...
                        c.drawBitmap(bg20, bg20_x, bg20_y, null);
                    }
                } finally {
                    if (c != null)
                        holder.unlockCanvasAndPost(c);
                }
                last_update_time = current_time;
                draw = false;
            }

            handler.removeCallbacks(drawRunner);
            if (visible) {
                handler.postDelayed(drawRunner, 1);
            }
        }


        private Bitmap resizeImage(Bitmap b) {
            return Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), sm, true);
        }

        private Bitmap getImage(String n) {
            File dir = context.getDir("theme", Context.MODE_PRIVATE);
            File file = new File(dir, n + ".png");
            if (file.exists()) {
                return BitmapFactory.decodeFile(file.getAbsolutePath());
            } else {
                return BitmapFactory.decodeResource(getResources(), getResources().getIdentifier(n, "drawable", getPackageName()));
            }
        }

        @Override
        public void onDestroy() {
            bg1.recycle();
            bg2.recycle();
            ...
            bg20.recycle();
        }

    }
}

MainActivity.java

import android.app.Activity;
import android.app.WallpaperManager;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;


public class MainActivity extends Activity {
    Intent service;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        service = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
        service.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, new ComponentName(this, mWallpaperService.class));
        service.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);

        startActivity(service);
        finish();
    }

    #button click
    public void openService(View view) {
        startActivity(service);
        finish();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}
Droid
  • 569
  • 4
  • 12
  • tried drawBitmap with Matrix param? – pskink Jun 29 '15 at 14:41
  • same result, 40% performance – Droid Jun 29 '15 at 14:45
  • so ScaleAnimation is terribly slow on your device as it uses canvas Scaling? . – pskink Jun 29 '15 at 14:47
  • 1
    also why do you use filteting and dithering? – pskink Jun 29 '15 at 14:49
  • used that to fix quality issue after calling Bitmap.createScaledBitmap, now it's useless, thx for notify that, but removing it doesn't increase performance. – Droid Jun 29 '15 at 15:02
  • post your whole code then – pskink Jun 29 '15 at 15:13
  • what part of code do you want me to post? i't s a lot of code... – Droid Jun 29 '15 at 15:27
  • the working minimalistic case – pskink Jun 29 '15 at 15:35
  • updated question, plz tell me if you need anything else. – Droid Jun 29 '15 at 16:38
  • Suggestion: if you're trying to achieve a *global* rescale, size the Surface with `setFixedSize()` so the hardware does the scaling, then leave your Canvas and Bitmaps alone. https://www.youtube.com/watch?v=qpRaD-ij2xc (demo), http://android-developers.blogspot.com/2013/09/using-hardware-scaler-for-performance.html (blog). – fadden Jun 29 '15 at 16:58
  • @fadden got "java.lang.UnsupportedOperationException: Wallpapers currently only support sizing from layout" tried holder.setFixedSize(480, 800); – Droid Jun 29 '15 at 17:51
  • Huh. Looks like WallpaperService explicitly rejects it -- checkin comment says something about not working with all animations: https://android.googlesource.com/platform/frameworks/base/+/a48a37f025dd875bfb1e46b673a3a54fb8e6b26b%5E%21/core/java/android/service/wallpaper/WallpaperService.java . The UID check was replaced with a hidden `setFixedSizeAllowed(boolean allowed)` in later releases, but that might be risky to mess with. – fadden Jun 29 '15 at 18:10
  • 1
    i dont see any difference: in fact when scaled drawing is faster... see http://codeshare.io/m08ap run it and see the logcat something like "draw *********** took 4.304" then comment out line 86: c.scale(4, 4 .....) and run it again – pskink Jun 29 '15 at 18:53
  • agree, there will be no difference if you draw 1-2 bitmaps, try to draw 10-20 and you will see it – Droid Jun 29 '15 at 19:12
  • there is no difference if you use "Canvas.scale + original Bitmap" and "no Canvas scaling + physically resized Bitmap" – pskink Jun 29 '15 at 19:46
  • Did what you said, without scale D/mEngine﹕ draw *********** took 1.52 with scale D/mEngine﹕ draw *********** took 2.102 – Droid Jun 30 '15 at 02:43
  • I've added bg file, can you check it please http://s000.tinyupload.com/index.php?file_id=27479363225396888358 – Droid Jun 30 '15 at 02:46
  • without scale took 3.5 and with scale took 5.3 – Droid Jun 30 '15 at 02:47
  • 1
    i mean there is no difference if you use 100x100 bitmap and scale the Canvas three times so the result on the screen is 300x300 or you use scaled 300x300 bitmap and no Canvas scaling, see http://codeshare.io/m08ap again, there is `USE_SCALED_BITMAPS` boolean switch, run it with true and false, no difference – pskink Jun 30 '15 at 06:41
  • thank you very much for help, maybe problem in another thing? my results running your code with canvas scale 3.5 and bitmap scale 2.6. I'm using android-studio. – Droid Jun 30 '15 at 09:16
  • and visually canvas scale slower than bitmap scale on emulator and on real device. – Droid Jun 30 '15 at 09:19
  • here is video http://s000.tinyupload.com/index.php?file_id=88692815816531032446 first run with USE_SCALED_BITMAPS = false, and then USE_SCALED_BITMAPS = true; – Droid Jun 30 '15 at 09:36
  • i am using genymotion 4.4 emulator and there is absolutely no difference – pskink Jun 30 '15 at 09:46
  • is it no difference on a real device? – Droid Jun 30 '15 at 10:19
  • i tested only on the emulator – pskink Jun 30 '15 at 10:23
  • I did it and there is difference between canvas scale and bitmap scale ( – Droid Jun 30 '15 at 10:26

3 Answers3

3

Try Canvas.drawBitmap (Bitmap bitmap, null, Rect dst, Paint paint)

The size of the "dst" rect will determine the scale, bitmap will be adjusted to fit inside it. That's what the native ImageView uses so it should be pretty fast.

BladeCoder
  • 12,779
  • 3
  • 59
  • 51
  • If this is slow, I don't know what can be faster. Make sure your benchmark is not flawed. I've seen that your getImage() method reads a bitmap from disk on the same thread and that for sure is slow and unpredictable. – BladeCoder Jul 06 '15 at 14:05
3

Looks like the performance decrease come from Bitmap.createBitmap (CPU resizing). I think for your case there is no reason to resize Bitmap using Bitmap.createBitmap. Instead, you should do it via GPU.

Your resizing code, Bitmap.createBitmap is doing CPU Bitmap resizing: allocate memory for new bitmap - do interpolation from the old bitmap, fill into new bitmap - all done by CPU.

The much better approach is keep bitmap without resizing from CPU. instead, load entire bitmap into GPU, and tell GPU to resize it. E.g, use:

drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)

Draw the bitmap using the specified matrix.

You can use the matrix parameter as the resize matrix for the bitmap being drawn.

At last, if you have memory usage problem (e.g, the original bitmap is too big), you can down size it (using Bitmap.createBitmap, CPU resize) once when you load it into memory. And no need to resize again in onSurfaceChanged (you probably need to re-calculate resize matrix though).

Community
  • 1
  • 1
Helin Wang
  • 4,002
  • 1
  • 30
  • 34
  • thank you, but result is the same as canvas.scale(), I've tried with a test code provided by @pskink with some matrix modifications. code link http://codeshare.io/m08ap – Droid Jul 08 '15 at 05:43
  • @Droid What api level is your device and what is your target api level? Maybe hardware acceleration is not turned on by default on your device: you need api level >= 11 to be able to have hardware acceleration, and if target api level >= 14 it's turned on by default (you don't have to do anything). reference: http://developer.android.com/guide/topics/graphics/hardware-accel.html – Helin Wang Jul 08 '15 at 16:17
  • lvl 16, if you can read comments under question, there will be a link to video – Droid Jul 09 '15 at 02:52
  • resizing byt scaled matrix and canvas.drawBitmap (or canvas.scale()) is done by gpu? – urSus Dec 17 '15 at 09:53
1

If I do understand your question, then I think this link might help: https://stackoverflow.com/a/4250279/2378691

Community
  • 1
  • 1
Ido Naveh
  • 2,442
  • 3
  • 26
  • 57