我想知道是否可以旋转存储在 sdcard 上的图像而不将其加载到内存中。
原因是我正在寻找著名的 OutOfMemoryError。我知道我可以通过对大图像进行下采样来避免它,但实际上我不想减小该图像的大小,我想要原始图像但旋转 90 度。
对此的任何建议都将受到热烈赞赏:)
我想知道是否可以旋转存储在 sdcard 上的图像而不将其加载到内存中。
原因是我正在寻找著名的 OutOfMemoryError。我知道我可以通过对大图像进行下采样来避免它,但实际上我不想减小该图像的大小,我想要原始图像但旋转 90 度。
对此的任何建议都将受到热烈赞赏:)
对于 90 度的旋转,我真的很喜欢RenderScript,它是专为处理位图而设计的,而且出乎意料地比默认的Bitmap.createBitmap()
. 进程内位图不存储在 Java 堆中,因此不会将您推入OutOfMemoryError
.
用几行代码在项目中设置 RenderScript 支持后,这里是要使用的 RenderScript 算法:
1) 创建app\src\main\rs\rotator.rs
具有以下内容的 RenderScript 文件。
#pragma version(1)
#pragma rs java_package_name(ua.kulku.rs)
rs_allocation inImage;
int inWidth;
int inHeight;
uchar4 __attribute__ ((kernel)) rotate_90_clockwise (uchar4 in, uint32_t x, uint32_t y) {
uint32_t inX = inWidth - 1 - y;
uint32_t inY = x;
const uchar4 *out = rsGetElementAt(inImage, inX, inY);
return *out;
}
uchar4 __attribute__ ((kernel)) rotate_270_clockwise (uchar4 in, uint32_t x, uint32_t y) {
uint32_t inX = y;
uint32_t inY = inHeight - 1 - x;
const uchar4 *out = rsGetElementAt(inImage, inX, inY);
return *out;
}
请注意ua.kulku.rs
,这是您为自动生成 RS Java 接口选择的一些包名称。
2) 在您的 Java 代码中引用它:
import ua.kulku.rs.ScriptC_rotator;
public Bitmap rotate(Bitmap bitmap) {
RenderScript rs = RenderScript.create(mContext);
ScriptC_rotator script = new ScriptC_rotator(rs);
script.set_inWidth(bitmap.getWidth());
script.set_inHeight(bitmap.getHeight());
Allocation sourceAllocation = Allocation.createFromBitmap(rs, bitmap,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
bitmap.recycle();
script.set_inImage(sourceAllocation);
int targetHeight = bitmap.getWidth();
int targetWidth = bitmap.getHeight();
Bitmap.Config config = bitmap.getConfig();
Bitmap target = Bitmap.createBitmap(targetWidth, targetHeight, config);
final Allocation targetAllocation = Allocation.createFromBitmap(rs, target,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
script.forEach_rotate_90_clockwise(targetAllocation, targetAllocation);
targetAllocation.copyTo(target);
rs.destroy();
return target;
}
对于 180 度旋转,NDK解决方案优于 RenderScript,就我而言,由于使用了顺序数组项访问,因为 180 度旋转实际上是图像像素数组的反转。我在这些比较中使用的 NDK 算法来自 https://github.com/AndroidDeveloperLB/AndroidJniBitmapOperations。进程内位图也不存储在 Java 堆上,防止OutOfMemoryError
.
统计条显示我在三星 S4 (Android 5.0) 上获得的 13 MP 照片的毫秒数。
您应该使用 decode 解码图像Bitmap
。你应该按照谷歌提供的加载大图像来了解如何做到这一点。它对我有很大帮助,你会注意到 RAM 使用量的巨大差异。
更新如果你想要的只是旋转你可以使用这个代码的图像
Matrix matrix = new Matrix();
matrix.setRotate(90);
result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),bitmap.getHeight(), matrix, false);
如果您只需要设置图像方向(例如拍摄时的照片方向),您可以使用
ExifInterface exif = new ExifInterface(filePath);
ExifInterface.TAG_ORIENTATION
我希望这对你有帮助
注意:这个答案实际上只是扩展了 riwnodennyk 的答案更容易实现。这都是渲染脚本,不关心任何 NDK。
涵盖所有常见的旋转和获取方向。
要获得旋转,您需要使用 ExifInterface。使用支持库中的那个,因为 sdk 版本存在问题。它适用于 JPEG 和 RAW(及类似)文件,因为此信息嵌入在文件中(而不是在解码的位图中)。
添加到 build.gradle
implementation "com.android.support:exifinterface:28.0.0"
如果您有文件的句柄,请使用它
import android.renderscript.Allocation;
import android.renderscript.RenderScript;
import your.application.package.rs.ScriptC_rotator;
...
public static Bitmap getCorrectlyRotatedBitmap(@NonNull Context context, @NonNull File imageFile) throws IOException {
ExifInterface ei = new ExifInterface(imageFile.getPath());
Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getPath());
int neededRotationClockwise = ei.getRotationDegrees() % 360;
return rotateClockwise(context, bitmap, neededRotationClockwise);
}
对于位图旋转本身
public static Bitmap rotateClockwise(@NonNull Context context, @NonNull Bitmap bitmap, int degrees) {
Log.i(TAG, "rotate bitmap degrees: " + degrees);
if (degrees == 0F) return bitmap;
RenderScript rs = RenderScript.create(context);
ScriptC_rotator script = new ScriptC_rotator(rs);
script.set_inWidth(bitmap.getWidth());
script.set_inHeight(bitmap.getHeight());
Allocation sourceAllocation = Allocation.createFromBitmap(rs, bitmap,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
bitmap.recycle();
script.set_inImage(sourceAllocation);
Bitmap.Config config = bitmap.getConfig();
switch (degrees) {
case 90: {
Bitmap target = Bitmap.createBitmap(bitmap.getHeight(), bitmap.getWidth(), config);
final Allocation targetAllocation = Allocation.createFromBitmap(rs, target,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
script.forEach_rotate_90_clockwise(targetAllocation, targetAllocation);
targetAllocation.copyTo(target);
rs.destroy();
return target;
}
case 180: {
Bitmap target = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), config);
final Allocation targetAllocation = Allocation.createFromBitmap(rs, target,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
script.forEach_rotate_180(targetAllocation, targetAllocation);
targetAllocation.copyTo(target);
rs.destroy();
return target;
}
case 270: {
Bitmap target = Bitmap.createBitmap(bitmap.getHeight(), bitmap.getWidth(), config);
final Allocation targetAllocation = Allocation.createFromBitmap(rs, target,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
script.forEach_rotate_270_clockwise(targetAllocation, targetAllocation);
targetAllocation.copyTo(target);
rs.destroy();
return target;
}
default:
throw new IllegalArgumentException("rotateClockwise() only supports 90 degree increments");
}
}
还有渲染脚本,在其中创建文件src/main/rs/rotator.rs
,这是使用渲染脚本的默认位置,但您必须自己创建目录。
your.application.package.rs
酌情更改
#pragma version(1)
#pragma rs java_package_name(your.application.package.rs)
rs_allocation inImage;
int inWidth;
int inHeight;
uchar4 __attribute__ ((kernel)) rotate_270_clockwise (uchar4 in, uint32_t x, uint32_t y) {
uint32_t inX = inWidth - 1 - y;
uint32_t inY = x;
const uchar4 *out = rsGetElementAt(inImage, inX, inY);
return *out;
}
uchar4 __attribute__ ((kernel)) rotate_90_clockwise (uchar4 in, uint32_t x, uint32_t y) {
uint32_t inX = y;
uint32_t inY = inHeight - 1 - x;
const uchar4 *out = rsGetElementAt(inImage, inX, inY);
return *out;
}
uchar4 __attribute__ ((kernel)) rotate_180 (uchar4 in, uint32_t x, uint32_t y) {
uint32_t inX = inWidth - 1 - x;
uint32_t inY = inHeight - 1 - y;
const uchar4 *out = rsGetElementAt(inImage, inX, inY);
return *out;
}
uchar4 __attribute__ ((kernel)) flip_vertical (uchar4 in, uint32_t x, uint32_t y) {
uint32_t inX = x;
uint32_t inY = inHeight - 1 - y;
const uchar4 *out = rsGetElementAt(inImage, inX, inY);
return *out;
}
uchar4 __attribute__ ((kernel)) flip_horizontal (uchar4 in, uint32_t x, uint32_t y) {
uint32_t inX = inWidth - 1 - x;
uint32_t inY = y;
const uchar4 *out = rsGetElementAt(inImage, inX, inY);
return *out;
}
我在这里做了一个非常缓慢但对内存友好的解决方案。
我确信有更好的方法并且很想知道它们
如果您必须处理不同的格式,那将会很痛苦。您必须能够理解不同的格式并能够读取/写入/转换它们,可能是通过流。在普通 PC 上,我会说查看具有非常大图像支持的ImageMagick 。我搜索了一个 Android 端口并想出了这个。可能值得一试。不过,它看起来还没有完成,因此您可能需要做一些工作才能获得更好的格式覆盖率。
我建议您使用一些在位图上操作时不将数据存储在进程堆上的第三方库。就我而言,我使用了我已经在项目中用于其他目的的 ffmpeg。