Android - 减小图像尺寸不会按相同比例减小尺寸
Android - reducing Image dimensions does not reduce size in the same proportion
我有一个小方法,用于调整图像(存储为文件)的大小以生成具有某个预定义文件大小(在我的例子中为 512 KB)的新图像。该方法类似于以下内容:
private static final int maximumFileSizeInKBs = 512;
public static Uri resizeImage(Context context, Uri imageUri) {
// get image width and height
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap imageBitmap = BitmapFactory.decodeFile(new File(imageUri.getPath()).getAbsolutePath(), options);
int imageWidth = options.outWidth;
int imageHeight = options.outHeight;
// get image file size (in KBs)
File file = new File(imageUri.getPath());
int fileSizeInKBs = Integer.parseInt(String.valueOf(file.length()/1024));
// get the scaling factor
// (square root because when we scale both the width and height by the
// square root of the scaling factor, then the size of the final image
// will be scaled by the scaling factor)
double scalingFactor = Math.sqrt(fileSizeInKBs / maximumFileSizeInKBs);
// no need to scale if file already within maximum file size limit
if(scalingFactor <= 1) {
return imageUri;
}
// get scaled dimensions
int scaledImageWidth = (int) Math.floor(imageWidth / scalingFactor);
int scaledImageHeight = (int) Math.floor(imageHeight / scalingFactor);
// load original bitmap (load a scaled copy to avoid OutOfMemory errors)
options = new BitmapFactory.Options();
options.inSampleSize = Math.max(imageHeight/scaledImageWidth, imageHeight/scaledImageHeight);
imageBitmap = BitmapFactory.decodeFile(new File(imageUri.getPath()).getAbsolutePath(), options);
// the scaled image above will be scaled by a value equal to the
// nearest power of 2, hence it wont be scaled to the exact value
// that we want. So, we need to calculate new scaling factors
// based on the scaled loaded bitmap
Matrix m = new Matrix();
RectF inRect = new RectF(0, 0, imageWidth, imageHeight);
RectF outRect = new RectF(0, 0, scaledImageWidth, scaledImageHeight);
m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
float[] values = new float[9];
m.getValues(values);
// create a scaled bitmap with required file size
Bitmap scaledBitmap = Bitmap.createScaledBitmap(imageBitmap, (int) (imageWidth*values[0]), (int) (imageHeight*values[4]), true);
// delete original image
new File(imageUri.getPath()).delete();
// write bitmap to file
File newImageFile = BaseResourceClass.makeTempResourceFile(Slide.ResourceType.IMAGE, context);
try {
newImageFile.createNewFile();
FileOutputStream fos = new FileOutputStream(newImageFile);
scaledBitmap.compress(Bitmap.CompressFormat.PNG, 1, fos);
fos.close();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(context, "Something went wrong", Toast.LENGTH_SHORT).show();
}
// recycle bitmaps to reallocate memory
Storage.recycleBitmap(imageBitmap);
Storage.recycleBitmap(scaledBitmap);
// scaling done, return new Image
return Uri.fromFile(newImageFile);
}
当此方法运行时,图像尺寸会缩小到准确大小(如我所期望的那样),但以 KB 为单位的图像尺寸不会按相同比例缩小。例如,在 运行 这种方法之后,我得到以下数字
before resizing
imageWidth : 3120, imageHeight : 4160, fileSize : 2960 KB
after resizing
imageWidth : 1395, imageHeight : 1860, fileSize : 2491 KB
我不明白,在从图像中删除了这么多像素之后,到底是什么占用了这么多space以保持图像文件大小如此之大。试图查看其他 Whosebug 问题和 android BitmapFactory 和 Bitmap 类 文档,但没有任何地方提到这个问题。如有任何帮助,我们将不胜感激!
发生这种情况的原因有很多,这取决于您正在阅读的图像类型(JPG、GIF、PNG、BMP)、压缩质量(如果是 JPG ),以及在缩小图像尺寸后如何保存它。
在您的示例中,您没有显示您使用的图像或图像使用的压缩类型。但是从代码中我看到你将它保存为 PNG 图片。
如果您拍摄一张质量很好的 2MB jpg 图片,然后像以前那样缩小尺寸,但将其另存为 PNG(无损质量),您最终可能会得到比以前更大的文件。
JPG 图像使用压缩算法来存储尽可能与原始图像相似但占用更少的图像 space。根据质量的不同,您可以获得看起来几乎与原始文件大小相同但只有几千字节的糟糕图像。
当您将图像保存为 PNG 时,您将获得与原始图像完全相同的图像(这就是它被称为无损图像的原因),这样一来,您无法通过压缩节省太多文件大小。
如果您使用 PNG 作为输入,并将缩小版本保存为 PNG,您可能会更接近您的 "proportional filesize" 想法。虽然减小尺寸可以通过抗锯齿在生成的图像中创建新颜色并稍微降低压缩性能。
使用 JPG 图像使其更加不可预测,因为原始图像中使用的质量和用于压缩结果的质量都会影响结果的文件大小。
如果图像未压缩,您可以预期图像文件大小与图像宽度 x 高度成正比。仅适用于 BMP 格式。
您正在处理以 PNG 格式存储的图像。 PNG是使用压缩来存储图像。这就是为什么图像大小更多地取决于您的图像有多少细节而不是图像宽度和高度。
在您的情况下,图像大小主要取决于第二个参数质量。
scaledBitmap.compress(Bitmap.CompressFormat.PNG, 1, fos);
您可以将其更改为 0 以获得更小的文件。
我有一个小方法,用于调整图像(存储为文件)的大小以生成具有某个预定义文件大小(在我的例子中为 512 KB)的新图像。该方法类似于以下内容:
private static final int maximumFileSizeInKBs = 512;
public static Uri resizeImage(Context context, Uri imageUri) {
// get image width and height
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap imageBitmap = BitmapFactory.decodeFile(new File(imageUri.getPath()).getAbsolutePath(), options);
int imageWidth = options.outWidth;
int imageHeight = options.outHeight;
// get image file size (in KBs)
File file = new File(imageUri.getPath());
int fileSizeInKBs = Integer.parseInt(String.valueOf(file.length()/1024));
// get the scaling factor
// (square root because when we scale both the width and height by the
// square root of the scaling factor, then the size of the final image
// will be scaled by the scaling factor)
double scalingFactor = Math.sqrt(fileSizeInKBs / maximumFileSizeInKBs);
// no need to scale if file already within maximum file size limit
if(scalingFactor <= 1) {
return imageUri;
}
// get scaled dimensions
int scaledImageWidth = (int) Math.floor(imageWidth / scalingFactor);
int scaledImageHeight = (int) Math.floor(imageHeight / scalingFactor);
// load original bitmap (load a scaled copy to avoid OutOfMemory errors)
options = new BitmapFactory.Options();
options.inSampleSize = Math.max(imageHeight/scaledImageWidth, imageHeight/scaledImageHeight);
imageBitmap = BitmapFactory.decodeFile(new File(imageUri.getPath()).getAbsolutePath(), options);
// the scaled image above will be scaled by a value equal to the
// nearest power of 2, hence it wont be scaled to the exact value
// that we want. So, we need to calculate new scaling factors
// based on the scaled loaded bitmap
Matrix m = new Matrix();
RectF inRect = new RectF(0, 0, imageWidth, imageHeight);
RectF outRect = new RectF(0, 0, scaledImageWidth, scaledImageHeight);
m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
float[] values = new float[9];
m.getValues(values);
// create a scaled bitmap with required file size
Bitmap scaledBitmap = Bitmap.createScaledBitmap(imageBitmap, (int) (imageWidth*values[0]), (int) (imageHeight*values[4]), true);
// delete original image
new File(imageUri.getPath()).delete();
// write bitmap to file
File newImageFile = BaseResourceClass.makeTempResourceFile(Slide.ResourceType.IMAGE, context);
try {
newImageFile.createNewFile();
FileOutputStream fos = new FileOutputStream(newImageFile);
scaledBitmap.compress(Bitmap.CompressFormat.PNG, 1, fos);
fos.close();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(context, "Something went wrong", Toast.LENGTH_SHORT).show();
}
// recycle bitmaps to reallocate memory
Storage.recycleBitmap(imageBitmap);
Storage.recycleBitmap(scaledBitmap);
// scaling done, return new Image
return Uri.fromFile(newImageFile);
}
当此方法运行时,图像尺寸会缩小到准确大小(如我所期望的那样),但以 KB 为单位的图像尺寸不会按相同比例缩小。例如,在 运行 这种方法之后,我得到以下数字
before resizing
imageWidth : 3120, imageHeight : 4160, fileSize : 2960 KB
after resizing
imageWidth : 1395, imageHeight : 1860, fileSize : 2491 KB
我不明白,在从图像中删除了这么多像素之后,到底是什么占用了这么多space以保持图像文件大小如此之大。试图查看其他 Whosebug 问题和 android BitmapFactory 和 Bitmap 类 文档,但没有任何地方提到这个问题。如有任何帮助,我们将不胜感激!
发生这种情况的原因有很多,这取决于您正在阅读的图像类型(JPG、GIF、PNG、BMP)、压缩质量(如果是 JPG ),以及在缩小图像尺寸后如何保存它。
在您的示例中,您没有显示您使用的图像或图像使用的压缩类型。但是从代码中我看到你将它保存为 PNG 图片。
如果您拍摄一张质量很好的 2MB jpg 图片,然后像以前那样缩小尺寸,但将其另存为 PNG(无损质量),您最终可能会得到比以前更大的文件。
JPG 图像使用压缩算法来存储尽可能与原始图像相似但占用更少的图像 space。根据质量的不同,您可以获得看起来几乎与原始文件大小相同但只有几千字节的糟糕图像。
当您将图像保存为 PNG 时,您将获得与原始图像完全相同的图像(这就是它被称为无损图像的原因),这样一来,您无法通过压缩节省太多文件大小。
如果您使用 PNG 作为输入,并将缩小版本保存为 PNG,您可能会更接近您的 "proportional filesize" 想法。虽然减小尺寸可以通过抗锯齿在生成的图像中创建新颜色并稍微降低压缩性能。
使用 JPG 图像使其更加不可预测,因为原始图像中使用的质量和用于压缩结果的质量都会影响结果的文件大小。
如果图像未压缩,您可以预期图像文件大小与图像宽度 x 高度成正比。仅适用于 BMP 格式。
您正在处理以 PNG 格式存储的图像。 PNG是使用压缩来存储图像。这就是为什么图像大小更多地取决于您的图像有多少细节而不是图像宽度和高度。
在您的情况下,图像大小主要取决于第二个参数质量。
scaledBitmap.compress(Bitmap.CompressFormat.PNG, 1, fos);
您可以将其更改为 0 以获得更小的文件。