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.
Without resize, works very good, perfomance 100%
bitmap1 = getImage("bitmap1");
...
bitmap20 = getImage("bitmap20");
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);
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();
}
}
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.
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.
Android provides Bitmap class to handle images.
On android go to photos, select your photo and click the ... in the top right. Scroll to bottom of page to find image size.
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With