24

I am wondering is it possible to rotate an image stored on sdcard without loading it's to memory.

The reason is why I am looking for that is famous OutOfMemoryError. I know I can avoid it by downsampling large image but in fact I don't want to reduce size of that image, I want to have original image but rotated on 90 degrees.

Any suggestions about that are warmly appreciated :)

endryha
  • 6,703
  • 7
  • 35
  • 58
  • 2
    *Comment is for those who are still looking for solution* *Well this kind of scenario most probably arise when you need to send image to server. After searching for hours i found its not possible in android to pre-rotate the image without loading it in the memory. The only solution to handle this is over the server side using exif data and rotating it. Here is a useful link http://stackoverflow.com/questions/7489742/php-read-exif-data-and-adjust-orientation – Muhammad Babar Jun 04 '14 at 07:40

6 Answers6

31

enter image description here

For the 90-degrees rotations I really embrace RenderScript which is exactly designed to deal with bitmaps and is unexpectedly even faster than the default Bitmap.createBitmap(). The in-process bitmap isn't stored on the Java heap, therefore not pushing you into OutOfMemoryError.

After you set up the RenderScript support in your project with few lines, here is the RenderScript algorithm to use:

1) Create app\src\main\rs\rotator.rs RenderScript file with the following content.

#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;
}

Pay attention to ua.kulku.rs, that's some package name you choose for the auto-generate RS Java interface.

2) Reference it in your Java code:

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

For 180-degrees rotations, NDK solution outperformed RenderScript, as for me, due to making use of the sequential array item access, as the 180-degree rotation is actually the reversion of the image's pixel array. The NDK algorithm I've used in these comparisons is from https://github.com/AndroidDeveloperLB/AndroidJniBitmapOperations . The in-process bitmap is also not stored on the Java heap, preventing OutOfMemoryError.

The stats bars indicate what I got in milliseconds on my Samsung S4 (Android 5.0) for the 13 MP photo.

riwnodennyk
  • 7,312
  • 4
  • 31
  • 36
5

you should decode decode the images using Bitmap. you should follow Loading Large Image presented by google on how to do it.. it helped me alot, you'll notice the large difference in the RAM usage..

UPDATE if all you want is just rotate the image you can use this code

Matrix matrix = new Matrix();
matrix.setRotate(90);
result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),bitmap.getHeight(), matrix, false);

if you just need to set the image orientation (for example the photo orientation when it's was taken) you can use

ExifInterface exif = new ExifInterface(filePath);

with the attribute ExifInterface.TAG_ORIENTATION I hope this helps you

ColdFire
  • 6,116
  • 6
  • 32
  • 50
  • 5
    yep, I am aware about this technique, but if I load resized bitmap in memory, rotate and then save it on disk, there will be this downsampled image, but I want to have original size image but rotated (no quality or size loss) – endryha Aug 20 '12 at 21:29
  • 7
    @A.A Applying a matrix to the in-memory bitmap is not going to help as the whole problem is that the bitmap is presumably too large to reasonably load into memory. – kabuko Aug 20 '12 at 21:53
  • then sorry, I guess I'm out of ideas :p – ColdFire Aug 20 '12 at 21:57
  • thanks, looks like exif interface is only correct solution, it helped – endryha Aug 22 '12 at 14:51
  • @endryha can you please share the solution, i'm having exactly same problem. – Muhammad Babar Jun 04 '14 at 06:45
  • 11
    This answer is not correct. Rotating as you say, you'll need to have two huge bitmaps loaded into memory. The OP wanted to avoid this, I don't know why he mark this as the answer. – Corbella Dec 16 '14 at 12:59
  • @Corbella You can set the image orientation using EXIF. This will not automatically rotate the image, but it may be enough for you if whatever displays the image handles the EXIF data. If you absolutely need the bitmap itself to be rotated, then this answer won't help you. Instead check answers from riwnodennyk or android developer. – user276648 Jul 28 '15 at 07:22
4

NOTE: that this answer is really just expanding on making riwnodennyk's answer a bit easier to implement. It's all renderscript and not concerned with anything NDK.

Covers all common rotations and getting Orientation.

To get the rotation you need to use the ExifInterface. Use the one in the support library as there are issues with the sdk version. It works with JPEG and RAW (and similar) Files as this information is embedded in the file (not in the decoded Bitmap).

add to build.gradle

implementation "com.android.support:exifinterface:28.0.0"

Use this if you have handle to the file

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);
}

For the Bitmap rotation itself

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");
    }

}

And the renderscript, Create file in src/main/rs/rotator.rs, This is the default location that Renderscript is used from, but you'll have to create the directory yourself.

Change the your.application.package.rs as appropriate

#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;
}
aaronvargas
  • 9,289
  • 2
  • 42
  • 45
1

i've made a very slow yet memory-friendly solution here .

i'm sure there are better ways and would love to know of them

Community
  • 1
  • 1
android developer
  • 106,412
  • 122
  • 641
  • 1,128
0

If you have to handle different formats then it's going to be a pain. You'll have to be able to understand the different formats and be able to read/write/transform them, probably through streams. On a regular PC I'd say to look at ImageMagick which has very large image support. I searched for an Android port and came up with this. It might be worth a try. It looks unfinished though, so you'd probably have to do some work for better format coverage.

kabuko
  • 35,009
  • 7
  • 75
  • 92
0

I will suggest you to use some 3rd party library that does not store data on process heap while operating on bitmaps. In my case I used ffmpeg that I was already using for other purpose in my project.

Zain Ali
  • 13,997
  • 14
  • 87
  • 103