Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

android scale bitmap performance

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();
    }
}
like image 488
Droid Avatar asked Jun 29 '15 14:06

Droid


People also ask

How do you handle bitmaps in Android?

For most cases, we recommend that you use the Glide library to fetch, decode, and display bitmaps in your app. Glide abstracts out most of the complexity in handling these and other tasks related to working with bitmaps and other images on Android.

How do I set the width and height of a bitmap in android programmatically?

scaleToFitWidth(bitmap, 100); public static Bitmap scaleToFitWidth(Bitmap b, int width) { float factor = width / (float) b. getWidth(); return Bitmap. createScaledBitmap(b, width, (int) (b. getHeight() * factor), true); } // Scale and maintain aspect ratio given a desired height // BitmapScaler.

Why bitmap is used in Android?

Android provides Bitmap class to handle images.

How do I check image size on android?

On android go to photos, select your photo and click the ... in the top right. Scroll to bottom of page to find image size.


2 Answers

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.

like image 122
BladeCoder Avatar answered Nov 07 '22 10:11

BladeCoder


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

like image 20
Helin Wang Avatar answered Nov 07 '22 11:11

Helin Wang