Android:旋转图像而不将其加载到内存中

时间:2012-08-20 20:30:32

标签: android

我想知道是否可以旋转存储在SD卡上的图像而不将其加载到内存中。

原因是我正在寻找着名的OutOfMemoryError。我知道我可以通过对大图像进行下采样来避免它,但实际上我不想减小该图像的大小,我想要原始图像但是要旋转90度。

对此有任何建议都受到热烈赞赏:)

5 个答案:

答案 0 :(得分:25)

enter image description here

对于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;
    }

enter image description here

对于180度旋转, NDK 解决方案优于RenderScript,因为我使用顺序数组项访问,因为180度旋转实际上是图像的反转&# 39;像素阵列。我在这些比较中使用的NDK算法来自https://github.com/AndroidDeveloperLB/AndroidJniBitmapOperations。进程内位图也未存储在Java堆上,阻止了OutOfMemoryError

统计栏显示我在三星S4(Android 5.0)上拍摄的13 MP照片的毫秒数。

答案 1 :(得分:5)

您应使用Bitmap对图像进行解码解码。你应该按照谷歌提出的Loading Large Image关于如何做到这一点..它帮助了我很多,你会注意到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 我希望这可以帮助你

答案 2 :(得分:1)

我已经制作了一个非常缓慢且对内存友好的解决方案here

我确信有更好的方法,并且很想知道它们

答案 3 :(得分:0)

如果你必须处理不同的格式,那将是一个痛苦。您必须能够理解不同的格式,并能够通过流来读取/写入/转换它们。在常规PC上,我会说要查看具有非常大的图像支持的ImageMagick。我搜索了一个Android端口并提出了this。这可能值得一试。虽然它看起来没有完成,所以你可能不得不做一些工作以获得更好的格式覆盖。

答案 4 :(得分:0)

注意:这个答案实际上只是在扩展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中创建文件,这是使用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;
}