我想知道是否可能在不加载图像到内存的情况下旋转存储在SD卡上的图像。
原因是我遇到了著名的OutOfMemoryError。我知道可以通过缩小大图像来避免它,但实际上我不想减小该图像的大小,我想保留原始图像但将其旋转90度。
对此有任何建议都非常感谢 :)
我想知道是否可能在不加载图像到内存的情况下旋转存储在SD卡上的图像。
原因是我遇到了著名的OutOfMemoryError。我知道可以通过缩小大图像来避免它,但实际上我不想减小该图像的大小,我想保留原始图像但将其旋转90度。
对此有任何建议都非常感谢 :)
对于90度旋转,我非常推荐使用RenderScript,它专门设计用来处理位图,而且比默认的Bitmap.createBitmap()
还要快。这种在进程中处理位图不会将其存储在Java堆上,因此不会导致OutOfMemoryError
。
在您的项目中设置了几行代码之后,下面是要使用的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;
}
请注意选用的自动生成 RS Java 接口的包名为 ua.kulku.rs
。
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照片所用的时间,单位为毫秒。
Error:(8, 33) error: Non-root compute kernel rotate_90_clockwise() is not supported in SDK levels 11-15
的错误提示。 - VoyBitmap
解码图片。你需要按照Google提供的大图加载指南进行操作,它帮助我很多,你会注意到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
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");
}
}
并且,Renderscript可以在 src/main/rs/rotator.rs
中创建文件。这是Renderscript默认使用的位置,但您需要自己创建目录。
根据情况更改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端口,找到了this。也许值得一试。不过它看起来还没有完成,所以你可能需要做一些工作来获得更好的格式覆盖率。