Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Picasso auto rotates image

I am using Picasso to load images from the web in my application. I have noticed that some images are shown rotated by 90degrees although when I open the image in my browser I see it correctly positioned. I assume that these images have EXIF data. Is there any way to instruct Picasso to ignore EXIF?

like image 738
Panos Avatar asked Jan 13 '15 21:01

Panos


People also ask

How do I rotate a JPEG in Android?

Drag the dots to the edges of your desired photo or tap Auto. To rotate a photo 90 degrees, tap Rotate .

How do I rotate my gallery?

Open a picture from the gallery and then press the menu button. This menu is only available when previewing a photo by itself. Now, select More from this menu. Editing choices will appear in the new pop-up menu, such as Details, Set as, Crop, Rotate Left, and Rotate Right.


1 Answers

As we know, Picasso supports EXIF from local storage, this is done via Android inner Utils. Providing the same functionality can't be done easy due to ability to use custom Http loading libraries. My solution is simple: we must override caching and apply Exif rotation before item is cached.

    OkHttpClient client = new OkHttpClient.Builder()
        .addNetworkInterceptor(chain -> {
            Response originalResponse = chain.proceed(chain.request());
            byte[] body = originalResponse.body().bytes();
            ResponseBody newBody = ResponseBody
                .create(originalResponse.body().contentType(), ImageUtils.processImage(body));
            return originalResponse.newBuilder().body(newBody).build();
        })
        .cache(cache)
        .build();

Here we add NetworkInterceptor that can transform request and response before it gets cached.

public class ImageUtils {

    public static byte[] processImage(byte[] originalImg) {
        int orientation = Exif.getOrientation(originalImg);
        if (orientation != 0) {
            Bitmap bmp = BitmapFactory.decodeByteArray(originalImg, 0, originalImg.length);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            rotateImage(orientation, bmp).compress(Bitmap.CompressFormat.PNG, 100, stream);

            return stream.toByteArray();
        }
        return originalImg;
    }

    private static Bitmap rotateImage(int angle, Bitmap bitmapSrc) {
        Matrix matrix = new Matrix();
        matrix.postRotate(angle);
        return Bitmap.createBitmap(bitmapSrc, 0, 0,
                bitmapSrc.getWidth(), bitmapSrc.getHeight(), matrix, true);
    }
}

Exif transformation:

public class Exif {
    private static final String TAG = "Exif";

    // Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
    public static int getOrientation(byte[] jpeg) {
        if (jpeg == null) {
            return 0;
        }

        int offset = 0;
        int length = 0;

        // ISO/IEC 10918-1:1993(E)
        while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
            int marker = jpeg[offset] & 0xFF;

            // Check if the marker is a padding.
            if (marker == 0xFF) {
                continue;
            }
            offset++;

            // Check if the marker is SOI or TEM.
            if (marker == 0xD8 || marker == 0x01) {
                continue;
            }
            // Check if the marker is EOI or SOS.
            if (marker == 0xD9 || marker == 0xDA) {
                break;
            }

            // Get the length and check if it is reasonable.
            length = pack(jpeg, offset, 2, false);
            if (length < 2 || offset + length > jpeg.length) {
                Log.e(TAG, "Invalid length");
                return 0;
            }

            // Break if the marker is EXIF in APP1.
            if (marker == 0xE1 && length >= 8 &&
                    pack(jpeg, offset + 2, 4, false) == 0x45786966 &&
                    pack(jpeg, offset + 6, 2, false) == 0) {
                offset += 8;
                length -= 8;
                break;
            }

            // Skip other markers.
            offset += length;
            length = 0;
        }

        // JEITA CP-3451 Exif Version 2.2
        if (length > 8) {
            // Identify the byte order.
            int tag = pack(jpeg, offset, 4, false);
            if (tag != 0x49492A00 && tag != 0x4D4D002A) {
                Log.e(TAG, "Invalid byte order");
                return 0;
            }
            boolean littleEndian = (tag == 0x49492A00);

            // Get the offset and check if it is reasonable.
            int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
            if (count < 10 || count > length) {
                Log.e(TAG, "Invalid offset");
                return 0;
            }
            offset += count;
            length -= count;

            // Get the count and go through all the elements.
            count = pack(jpeg, offset - 2, 2, littleEndian);
            while (count-- > 0 && length >= 12) {
                // Get the tag and check if it is orientation.
                tag = pack(jpeg, offset, 2, littleEndian);
                if (tag == 0x0112) {
                    // We do not really care about type and count, do we?
                    int orientation = pack(jpeg, offset + 8, 2, littleEndian);
                    switch (orientation) {
                        case 1:
                            return 0;
                        case 3:
                            return 180;
                        case 6:
                            return 90;
                        case 8:
                            return 270;
                    }
                    Log.i(TAG, "Unsupported orientation");
                    return 0;
                }
                offset += 12;
                length -= 12;
            }
        }

        Log.i(TAG, "Orientation not found");
        return 0;
    }

    private static int pack(byte[] bytes, int offset, int length,
                            boolean littleEndian) {
        int step = 1;
        if (littleEndian) {
            offset += length - 1;
            step = -1;
        }

        int value = 0;
        while (length-- > 0) {
            value = (value << 8) | (bytes[offset] & 0xFF);
            offset += step;
        }
        return value;
    }
}

This solution is experimental and must be tested for leaks and probably improved. In most cases Samsung and iOs devices return 90 DEG rotation and this solution works. Other cases also must be tested.

like image 66
Ph0en1x Avatar answered Oct 04 '22 12:10

Ph0en1x