Task: I want to resize and move an image across the screen. I want to do this smoothly no matter how big the image is. The code should be supported by the API level 8.
Problem: I tried to use ImageView
with scaleType="matrix"
. Calling ImageView.setMatrix()
and then ImageView.invalidate()
works great with small images but horrible with big ones. No matter how big the ImageView
is.
Can I somehow speed up repainting of the ImageView
so it will not recalculate whole image? Maybe there is a way to accomplish the task using different component?
EDIT: More information on what I am trying to achieve.
I want to display a part of the image on the screen. Properties x, y, fw and fh are changing constantly. I am looking for a part of code (idea), or components which for these 8 specified variables will quickly generate and display the part of the image.
EDIT 2: Info on pw and ph
I assume pw and ph can hold values from 1 to infinity. If this approach causes a lot of trouble we can assume the picture is not bigger than the picture taken with the device's camera.
With your help (community) I figured out the solution. I am sure that there are other better ways to do it but my solution is not very complicated and should work with any image, any Android since API level 8.
The solution is to use two ImageView
objects instead of one.
The first ImageView
will be working like before but loaded image will be scaled down so that it's width will be smaller than the width of the ImageView
and it's height will be smaller than the height of the ImageView
.
The second ImageView
will be blank at the start. Everytime the x, y, fw and fh properties are changing the AsyncTask
will be executed to load only visible part of the image. When properties are changing fast the AsyncTask
will not be able to finish in time. It will have to be canceled and new one will be started. When it finishes the result Bitmap
will be loaded onto the second ImageView
so it will be visible to user. When the properties changes again loaded Bitmap
will be deleted, so it will not cover moving Bitmap
loaded to the first ImageView
. Note: BitmapRegionDecoder
which I will use to load sub-image is available since Android API level 10, so API 8 and API 9 users will only see scaled down image. I decided it is OK.
Code needed:
ImageView
scaleType="matrix"
(best in XML)ImageView
scaleType="fitXY"
(best in XML)NOTE: Notice the ||
operator instead of &&
while calculating inSampleSize
. We want the image loaded to be smaller than ImageView
so that we are sure we have enough RAM to load it. (I presume ImageView
size is not bigger than the size of the device display. I also presume that the device has enough memory to load at least 2 Bitmaps
of the size of the device display. Please tell me if I am making a mistake here.)
NOTE 2: I am loading images using InputStream
. To load a file different way you will have to change code in try{...} catch(...){...}
blocks.
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
|| (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public Bitmap decodeSampledBitmapFromResource(Uri fileUri,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
try {
InputStream is = this.getContentResolver().openInputStream(fileUri);
BitmapFactory.decodeStream(is, null, options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
try {
InputStream is = this.getContentResolver().openInputStream(fileUri);
return BitmapFactory.decodeStream(is, null, options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
NOTE: Size of Rectangle that will be cut out of source image is relative to the image. Values that specify it are from 0 to 1 because the size of the ImageView
and loaded Bitmap
s differs from the size of the original image.
public Bitmap getCroppedBitmap (Uri fileUri, int outWidth, int outHeight,
double rl, double rt, double rr, double rb) {
// rl, rt, rr, rb are relative (values from 0 to 1) to the size of the image.
// That is because image moving will be smaller than the original.
if (Build.VERSION.SDK_INT >= 10) {
// Ensure that device supports at least API level 10
// so we can use BitmapRegionDecoder
BitmapRegionDecoder brd;
try {
// Again loading from URI. Change the code so it suits yours.
InputStream is = this.getContentResolver().openInputStream(fileUri);
brd = BitmapRegionDecoder.newInstance(is, true);
BitmapFactory.Options options = new BitmapFactory.Options();
options.outWidth = (int)((rr - rl) * brd.getWidth());
options.outHeight = (int)((rb - rt) * brd.getHeight());
options.inSampleSize = calculateInSampleSize(options,
outWidth, outHeight);
return brd.decodeRegion(new Rect(
(int) (rl * brd.getWidth()),
(int) (rt * brd.getHeight()),
(int) (rr * brd.getWidth()),
(int) (rb * brd.getHeight())
), options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
else
return null;
}
AsyncTask
loading the sub-image Bitmap
.NOTE: notice declaring a variable of the type of this class. It will be used later.
private LoadHiResImageTask loadHiResImageTask = new LoadHiResImageTask();
private class LoadHiResImageTask extends AsyncTask<Double, Void, Bitmap> {
/** The system calls this to perform work in a worker thread and
* delivers it the parameters given to AsyncTask.execute() */
protected Bitmap doInBackground(Double... numbers) {
return getCroppedBitmap(
// You will have to change first parameter here!
Uri.parse(imagesToCrop[0]),
numbers[0].intValue(), numbers[1].intValue(),
numbers[2], numbers[3], numbers[4], numbers[5]);
}
/** The system calls this to perform work in the UI thread and delivers
* the result from doInBackground() */
protected void onPostExecute(Bitmap result) {
ImageView hiresImage = (ImageView) findViewById(R.id.hiresImage);
hiresImage.setImageBitmap(result);
hiresImage.postInvalidate();
}
}
This function will be called every time the x, y, fw or fh property changes.
NOTE: hiresImage in my code is the id
of the second (top) ImageView
private void updateImageView () {
// ... your code to update ImageView matrix ...
//
// imageToCrop.setImageMatrix(m);
// imageToCrop.postInvalidateDelayed(10);
if (Build.VERSION.SDK_INT >= 10) {
ImageView hiresImage = (ImageView) findViewById(R.id.hiresImage);
hiresImage.setImageDrawable(null);
hiresImage.invalidate();
if (loadHiResImageTask.getStatus() != AsyncTask.Status.FINISHED) {
loadHiResImageTask.cancel(true);
}
loadHiResImageTask = null;
loadHiResImageTask = new LoadHiResImageTask();
loadHiResImageTask.execute(
(double) hiresImage.getWidth(),
(double) hiresImage.getHeight(),
// x, y, fw, fh are properties from the question
(double) x / d.getIntrinsicWidth(),
(double) y / d.getIntrinsicHeight(),
(double) x / d.getIntrinsicWidth()
+ fw / d.getIntrinsicWidth(),
(double) y / d.getIntrinsicHeight()
+ fh / d.getIntrinsicHeight());
}
}
Try loading source bitmap by BitmapFactory Options in just decode bounds
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
After writing these 2 methods
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
set bitmap on imageview like this
then try scaling the image it will do for a large bitmap efficiently
You can read here further
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
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