我在一个文件中有一个大的位图(比如3888x2592)。现在,我想将位图大小调整为800x533,并将其保存到另一个文件中。
我通常通过调用bitmap来缩放位图。createBitmap方法,但它需要一个源位图作为第一个参数,我不能提供,因为加载原始图像到位图对象当然会超过内存(例如,见这里)。
我也不能读取位图,例如,BitmapFactory.decodeFile(文件,选项),提供BitmapFactory.Options。inSampleSize,因为我想把它调整到精确的宽度和高度。使用inSampleSize将位图大小调整为972x648(如果我使用inSampleSize=4)或778x518(如果我使用inSampleSize=5,这甚至不是2的幂)。
我还希望避免在第一步中使用inSampleSize读取图像,例如,在第一步中使用972x648,然后在第二步中将其大小调整为800x533,因为与直接调整原始图像的大小相比,质量会较差。
总结一下我的问题:
是否有一种方法来读取一个10MP或更多的大图像文件,并将其保存到一个新的图像文件,调整到一个特定的新宽度和高度,而不得到OutOfMemory异常?
我还尝试了BitmapFactory.decodeFile(文件,选项)和设置选项。out theight and Options。outidth值手动为800和533,但它不是这样工作的。
这可能对其他研究这个问题的人有用。我重写了Justin的代码,以允许该方法也接收所需的目标大小对象。这在使用Canvas时工作得非常好。所有的功劳都应该归于JUSTIN的出色的初始代码。
private Bitmap getBitmap(int path, Canvas canvas) {
Resources resource = null;
try {
final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
resource = getResources();
// Decode image size
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resource, path, options);
int scale = 1;
while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) >
IMAGE_MAX_SIZE) {
scale++;
}
Log.d("TAG", "scale = " + scale + ", orig-width: " + options.outWidth + ", orig-height: " + options.outHeight);
Bitmap pic = null;
if (scale > 1) {
scale--;
// scale to max possible inSampleSize that still yields an image
// larger than target
options = new BitmapFactory.Options();
options.inSampleSize = scale;
pic = BitmapFactory.decodeResource(resource, path, options);
// resize to desired dimensions
int height = canvas.getHeight();
int width = canvas.getWidth();
Log.d("TAG", "1th scale operation dimenions - width: " + width + ", height: " + height);
double y = Math.sqrt(IMAGE_MAX_SIZE
/ (((double) width) / height));
double x = (y / height) * width;
Bitmap scaledBitmap = Bitmap.createScaledBitmap(pic, (int) x, (int) y, true);
pic.recycle();
pic = scaledBitmap;
System.gc();
} else {
pic = BitmapFactory.decodeResource(resource, path);
}
Log.d("TAG", "bitmap size - width: " +pic.getWidth() + ", height: " + pic.getHeight());
return pic;
} catch (Exception e) {
Log.e("TAG", e.getMessage(),e);
return null;
}
}
Justin的代码非常有效地减少了使用大型位图的开销。
贾斯汀的答案翻译成代码(对我来说很完美):
private Bitmap getBitmap(String path) {
Uri uri = getImageUri(path);
InputStream in = null;
try {
final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
in = mContentResolver.openInputStream(uri);
// Decode image size
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(in, null, options);
in.close();
int scale = 1;
while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) >
IMAGE_MAX_SIZE) {
scale++;
}
Log.d(TAG, "scale = " + scale + ", orig-width: " + options.outWidth + ",
orig-height: " + options.outHeight);
Bitmap resultBitmap = null;
in = mContentResolver.openInputStream(uri);
if (scale > 1) {
scale--;
// scale to max possible inSampleSize that still yields an image
// larger than target
options = new BitmapFactory.Options();
options.inSampleSize = scale;
resultBitmap = BitmapFactory.decodeStream(in, null, options);
// resize to desired dimensions
int height = resultBitmap.getHeight();
int width = resultBitmap.getWidth();
Log.d(TAG, "1th scale operation dimenions - width: " + width + ",
height: " + height);
double y = Math.sqrt(IMAGE_MAX_SIZE
/ (((double) width) / height));
double x = (y / height) * width;
Bitmap scaledBitmap = Bitmap.createScaledBitmap(resultBitmap, (int) x,
(int) y, true);
resultBitmap.recycle();
resultBitmap = scaledBitmap;
System.gc();
} else {
resultBitmap = BitmapFactory.decodeStream(in);
}
in.close();
Log.d(TAG, "bitmap size - width: " +resultBitmap.getWidth() + ", height: " +
resultBitmap.getHeight());
return resultBitmap;
} catch (IOException e) {
Log.e(TAG, e.getMessage(),e);
return null;
}
我使用了这样的代码:
String filePath=Environment.getExternalStorageDirectory()+"/test_image.jpg";
BitmapFactory.Options options=new BitmapFactory.Options();
InputStream is=new FileInputStream(filePath);
BitmapFactory.decodeStream(is, null, options);
is.close();
is=new FileInputStream(filePath);
// here w and h are the desired width and height
options.inSampleSize=Math.max(options.outWidth/460, options.outHeight/288); //Max 460 x 288 is my desired...
// bmp is the resized bitmap
Bitmap bmp=BitmapFactory.decodeStream(is, null, options);
is.close();
Log.d(Constants.TAG, "Scaled bitmap bytes, "+bmp.getRowBytes()+", width:"+bmp.getWidth()+", height:"+bmp.getHeight());
我试过原始图像是1230 x 1230,得到的位图是330 x 330。
如果尝试2590 x 3849,我将得到OutOfMemoryError。
我跟踪了它,它仍然抛出OutOfMemoryError在行"BitmapFactory.decodeStream(is, null, options);",如果原始位图太大…
这可能对其他研究这个问题的人有用。我重写了Justin的代码,以允许该方法也接收所需的目标大小对象。这在使用Canvas时工作得非常好。所有的功劳都应该归于JUSTIN的出色的初始代码。
private Bitmap getBitmap(int path, Canvas canvas) {
Resources resource = null;
try {
final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
resource = getResources();
// Decode image size
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resource, path, options);
int scale = 1;
while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) >
IMAGE_MAX_SIZE) {
scale++;
}
Log.d("TAG", "scale = " + scale + ", orig-width: " + options.outWidth + ", orig-height: " + options.outHeight);
Bitmap pic = null;
if (scale > 1) {
scale--;
// scale to max possible inSampleSize that still yields an image
// larger than target
options = new BitmapFactory.Options();
options.inSampleSize = scale;
pic = BitmapFactory.decodeResource(resource, path, options);
// resize to desired dimensions
int height = canvas.getHeight();
int width = canvas.getWidth();
Log.d("TAG", "1th scale operation dimenions - width: " + width + ", height: " + height);
double y = Math.sqrt(IMAGE_MAX_SIZE
/ (((double) width) / height));
double x = (y / height) * width;
Bitmap scaledBitmap = Bitmap.createScaledBitmap(pic, (int) x, (int) y, true);
pic.recycle();
pic = scaledBitmap;
System.gc();
} else {
pic = BitmapFactory.decodeResource(resource, path);
}
Log.d("TAG", "bitmap size - width: " +pic.getWidth() + ", height: " + pic.getHeight());
return pic;
} catch (Exception e) {
Log.e("TAG", e.getMessage(),e);
return null;
}
}
Justin的代码非常有效地减少了使用大型位图的开销。
我使用Integer。numberOfLeadingZeros计算最佳样本量,性能更好。
kotlin完整代码:
@Throws(IOException::class)
fun File.decodeBitmap(options: BitmapFactory.Options): Bitmap? {
return inputStream().use {
BitmapFactory.decodeStream(it, null, options)
}
}
@Throws(IOException::class)
fun File.decodeBitmapAtLeast(
@androidx.annotation.IntRange(from = 1) width: Int,
@androidx.annotation.IntRange(from = 1) height: Int
): Bitmap? {
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
decodeBitmap(options)
val ow = options.outWidth
val oh = options.outHeight
if (ow == -1 || oh == -1) return null
val w = ow / width
val h = oh / height
if (w > 1 && h > 1) {
val p = 31 - maxOf(Integer.numberOfLeadingZeros(w), Integer.numberOfLeadingZeros(h))
options.inSampleSize = 1 shl maxOf(0, p)
}
options.inJustDecodeBounds = false
return decodeBitmap(options)
}
这对我很管用。该函数获取sd卡上文件的路径,并返回可显示的最大大小的位图。
代码是从Ofir与一些变化,如图像文件在sd代替资源和witdth和高度是从显示对象。
private Bitmap makeBitmap(String path) {
try {
final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
//resource = getResources();
// Decode image size
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
int scale = 1;
while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) >
IMAGE_MAX_SIZE) {
scale++;
}
Log.d("TAG", "scale = " + scale + ", orig-width: " + options.outWidth + ", orig-height: " + options.outHeight);
Bitmap pic = null;
if (scale > 1) {
scale--;
// scale to max possible inSampleSize that still yields an image
// larger than target
options = new BitmapFactory.Options();
options.inSampleSize = scale;
pic = BitmapFactory.decodeFile(path, options);
// resize to desired dimensions
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.y;
int height = size.x;
//int height = imageView.getHeight();
//int width = imageView.getWidth();
Log.d("TAG", "1th scale operation dimenions - width: " + width + ", height: " + height);
double y = Math.sqrt(IMAGE_MAX_SIZE
/ (((double) width) / height));
double x = (y / height) * width;
Bitmap scaledBitmap = Bitmap.createScaledBitmap(pic, (int) x, (int) y, true);
pic.recycle();
pic = scaledBitmap;
System.gc();
} else {
pic = BitmapFactory.decodeFile(path);
}
Log.d("TAG", "bitmap size - width: " +pic.getWidth() + ", height: " + pic.getHeight());
return pic;
} catch (Exception e) {
Log.e("TAG", e.getMessage(),e);
return null;
}
}