Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exif rotation not supported? #130

Closed
xleon opened this issue Jan 22, 2016 · 44 comments
Closed

Exif rotation not supported? #130

xleon opened this issue Jan 22, 2016 · 44 comments

Comments

@xleon
Copy link
Contributor

xleon commented Jan 22, 2016

Some devices rotate images in a way that a landscape picture can be shown in portrait mode and the opposite.

In another part of my app I´m handling / fixing this with android ExifInterface, but ImageViewAsync seams like not taking care of this (correct me if I´m wrong).

This piece of code ilustrates the fix: https://gist.github.com/xleon/7c6e2eff4d9ca3c6db0b

Is there any actual workaround for this?
Am I doing something wrong?
Are you planning to handle exif rotation in the future?

I would make a PR but I´m not sure where´s the best place to write this code

@molinch
Copy link
Collaborator

molinch commented Jan 22, 2016

Hi @xleon you are right Exif rotation is not yet handled. It's a good improvement for a future release. 👍
I will investigate where would be the best place for it.

For reference this is what SDWebImage does: https://github.com/rs/SDWebImage/blob/b185621812f90a4eb4f7f44e0f547014da4d1958/SDWebImage/UIImage%2BMultiFormat.m

And this is what Picasso does (there are other files involved):
https://github.com/square/picasso/blob/a35c785202ef7351f1e071a0191b201b745da168/picasso/src/main/java/com/squareup/picasso/BitmapHunter.java#L554

@xleon
Copy link
Contributor Author

xleon commented Jan 22, 2016

Cool.
For now I need a quick fix and I´m trying to figure out how to apply this code to the bitmap that will be shown in a ImageViewAsync. Any clue on how to do that? Even if it´s a temporal dirty hack

@ghost
Copy link

ghost commented Jan 23, 2016

I ended up adding a rotate button that uses the rotate transformation to get around this in my app

Sent from my iPhone

On Jan 22, 2016, at 4:59 PM, Diego Ponce de León [email protected] wrote:

Cool.
For now I need a quick fix and I´m trying to figure out how to apply this code to the bitmap that will be shown in a ImageViewAsync. Any clue on how to do that? Even if it´s a temporal dirty hack


Reply to this email directly or view it on GitHub.

@xleon
Copy link
Contributor Author

xleon commented Jan 23, 2016

That´s not an option in apps that don´t allow the user to edit the image

@molinch
Copy link
Collaborator

molinch commented Jan 23, 2016

Even if it's a temporary hack I would suggest having it in ImageLoaderTask.
You could insert your method call in GetImageAsync just before:

bool transformPlaceholdersEnabled = Parameters.TransformPlaceholdersEnabled.HasValue ? 
                    Parameters.TransformPlaceholdersEnabled.Value : ImageService.Config.TransformPlaceholders;

                if (Parameters.Transformations != null && Parameters.Transformations.Count > 0 
                    && (!isPlaceholder || (isPlaceholder && transformPlaceholdersEnabled)))
                {

@coonmoo
Copy link

coonmoo commented Jan 23, 2016

This has also high priority for us.

Would be awesome to get this build in FFImageLoading (we also have users who report their pictures uploaded with the wrong rotation).

@molinch
Copy link
Collaborator

molinch commented Jan 23, 2016

I guess Exif rotation isn't working on any platform: Android, iOS, WP (SL, UWP, RT).
Isn't it?

@coonmoo
Copy link

coonmoo commented Jan 23, 2016

I'm not sure. On iOS we had rotation problems (doing a landscape photo ended up on the server in portrait). These problems occured on iOS when fetching the image from disk and resizing it then using core graphics.

Since we use CachedImage on iOS with combination of image.GetImageAsync() the photos are rotated correctly. So my guess is the iOS ImageView automatically correctly rotates the taken photos?

@daniel-luberda
Copy link
Member

@coonmoo Yes, on iOS, UIImageView will read EXIFF orientation from image file and handle it (imageView.ImageOrientation will give you current UIImageOrientation).

@xleon xleon changed the title Exif rotation in Android not supported? Exif rotation not supported? Jan 23, 2016
@xleon
Copy link
Contributor Author

xleon commented Jan 23, 2016

I changed the issue title as we´re talking about other platforms

@coonmoo
Copy link

coonmoo commented Jan 25, 2016

Since reading the image orientation on android requires using ExifInterface with a "filepath" (which the CachedImage does not know), my proposal would be the following:

Add an optional overload to: GetImageAsJpgAsync
e.g. "rotation=0"

So I can use this method http://stackoverflow.com/a/11081918 in my app to determine the rotation.

and call cachedImage.GetImageAsJpgAsync(rotation: 90) which should internally use a matrix:

Matrix m = new Matrix();
m.PreRotate(rotation);

The currently implemented method should be switched from Bitmap.CreateScaledBitmap,
to CreateBitmap with a matrix (where you can apply scaling and rotation): e.g.
http://android-er.blogspot.de/2012/10/create-scaled-bitmap-using-matrix.html

Would be super awesome, if you could add a rotation paramter :)!!!

@daniel-luberda
Copy link
Member

In a exif-orientation-support branch I added some initial Android EXIF orientation support (untested) for file path image sources. What about web images? Should we handle it too? What do you think?

@coonmoo
Copy link

coonmoo commented Jan 25, 2016

Sorry yeah, your approach (rotating the image also in the view) is the right way to do it.

But, I had huge memory problems on low-end devices that's why in my head was the rotation overload for GetImageAsJpgAsync.

@daniel-luberda

I did the rotation previously like you in your exif-orientation-support branch, but on devices with < 1GB ram I run into frequent out of memory issues when
loading the image and rotating it (thus holding two large images in memory).

For the web image support: I don't need it, because when users can upload their images correctly rotated, they are also fetched from the server correctly.

@daniel-luberda
Copy link
Member

@coonmoo As to memory problems, the key is to do this: https://github.com/molinch/FFImageLoading/blob/exif-orientation-support/source/FFImageLoading.Droid/Extensions/ExifExtensions.cs#L57-L61

BTW: Notice the rotation takes place after downsampling, so if you have it correctly configured (for low-end devices capabilities) - it would be less memory costly operation to do the rotation.

@daniel-luberda
Copy link
Member

@molinch Do you think we could optimize this method to try to reuse bitmap from a reuse-pool? That would be even less memory costly operation.

@molinch
Copy link
Collaborator

molinch commented Jan 25, 2016

@daniel-luberda I don't think we can do better than that... Unfortunately it does not seem to be possible to use the pool in this case.

@molinch
Copy link
Collaborator

molinch commented Jan 26, 2016

Btw, I don't know if it changes anything but there is an overload of Bitmap.CreateBitmap that accepts the matrix as a parameter.
https://github.com/molinch/FFImageLoading/blob/exif-orientation-support/source/FFImageLoading.Droid/Extensions/ExifExtensions.cs#L43

@daniel-luberda
Copy link
Member

@molinch From what I've read, using canvas (as we do it) should be more efficient, but I didn't test the differences. If anyone has an explanation about which one is better, just let me know.

@daniel-luberda
Copy link
Member

BTW: Shouldn't we rotate bitmap before downsampling?
BTW2: Did some test it under Windows?

@molinch
Copy link
Collaborator

molinch commented Jan 26, 2016

To lower memory usage it's better to downsample and then rotate.

But we should enforce correct width/height... Because of EXIF tags width can be height when we downsample.

@daniel-luberda
Copy link
Member

Another changes. What do you think? 7114cbc Testers needed! :P

@coonmoo
Copy link

coonmoo commented Jan 26, 2016

I'll test it. Is there any chance you could provide a -pre release nuget package?
If it'll take you longer than 10minutes, I'd rather check out the source myself and build it.

@daniel-luberda
Copy link
Member

I uploaded dlls here: link

@daniel-luberda
Copy link
Member

Another idea:

To avoid memory problems when loading image eg. from gallery. We could make a new ImageSource type which will always get an image thumbnail. It could use platform APIs to retrieve thumbnail, so it will be efficient. Perfect for gallery like apps.

We could have ImageSource.FileThumbnail and ImageSource.UrlThumbnail

@molinch
Copy link
Collaborator

molinch commented Jan 27, 2016

Btw I agree with what @coonmoo said. If it comes from the web it's the responsability of the server to deliver correct images with correct orientation. It doesn't make sense to do it on device: it's slow and takes memory/cpu.
IMO we should make it clear on Wiki that:

  • Exif works for local files only.
  • It will remain so for performance reasons.

Which platform API are you thinking about?

@daniel-luberda
Copy link
Member

@molinch I agree, exif orientation for local files should be enough.

It doesn't make sense to do it on device: it's slow and takes memory/cpu.

Currently, rotation is done only for images which have exif orientation specified. There's no performance penalty for files which don't have it. It could be easily done for web downloaded images as we already have them on a disk - we only need to specify the full file path (we could add some helper method to DiskCache implementations).

Which platform API are you thinking about?

@molinch
Copy link
Collaborator

molinch commented Jan 27, 2016

Ah definitely not this API on Android :)
It's way more inefficient than the downsampling stuff we have.

I'd rather not change anything people can just use DownSample.

@daniel-luberda
Copy link
Member

Did you test it? Is it really that bad in newer Android versions?:P

BTW:

Using MediaStore.Images.Thumbnails you can query and get two kinds of thumbnails: MINI_KIND: 512 x 384 thumbnail MICRO_KIND: 96 x 96 thumbnail. The advantage of using this call is that the thumbnails are cached by the MediaStore. So retrieval would be faster if the thumbnail was previously created.

@daniel-luberda
Copy link
Member

@coonmoo Did you test it?

@coonmoo
Copy link

coonmoo commented Jan 28, 2016

Hey Daniel, thanks for your effort!
I tested the dlls on various phones and discovered no issues.

But sadly I currently don't have access to the phone where the wrong orientation problems occured. So I cannot tell you if the orientation problem is fixed.

At least the change is stable on the phones I tested.

@coonmoo
Copy link

coonmoo commented Jan 30, 2016

I rolled out the test dll changes to some testers.

Some people report that their uploaded pictures are now completly black.

So what I do is load the camera picture in the CachedImage view (preview is totally fine), then I call (after loading finished) image.GetImageAsJpgAsync(500.500. 95) and upload the resulting Byte Array to the Server. The Server stores this raw Byte Array then (without any further Image processing applied).

So for some users the uploaded Picture (despite displaying correctly in preview) turns totally black.

Any ideas on this?

@daniel-luberda
Copy link
Member

@coonmoo I suppose it may be related to this: #135

@coonmoo
Copy link

coonmoo commented Feb 2, 2016

I'm not sure.

I just tested it on a low end device, and sometimes even the images loaded from the server in CachedImage display completly black.

@molinch
Copy link
Collaborator

molinch commented Feb 2, 2016

@coonmoo do you have anything special in the logs when it happens?

@coonmoo
Copy link

coonmoo commented Feb 3, 2016

Here is the log from a low end device.

  1. I opened the camera from my app and took a Picture
  2. The result of the camera is loaded into CachedImage (first line of log)
  3. The log line "Image was loaded" is when FFImageLoading finished loading
  4. The displayed Image in CachedImage is completly black
  5. The Image on the sdcard is fine, though
V/DroidLogger(31544): 2016/02/03 14:22:36:918 Going to load image /storage/sdcard0/Android/data/com.myapp/files/Pictures/myapp/IMG_20160203_142225.jpg
D/KeyguardViewMediator( 2035): setHidden false
D/VoIPInterfaceManager( 2035): getState()...
D/VoIPInterfaceManager( 2035):     Not exist call session
D/VoIPInterfaceManager( 2035): getState()...
D/VoIPInterfaceManager( 2035):     Not exist call session
D/ReuseBitmapDrawableCache(31544): Reuse pool is not full, refusing reuse request
I/libblt_hw( 1688): Library opened (handle = 0, fd = 30)
D/WindowManager( 2035): PhoneWindowManager: focusChangedLw
D/KeyguardViewMediator( 2035): setHidden false
D/KeyguardViewMediator( 2035): setHidden false
D/STATUSBAR-StatusBarManagerService( 2035): setSystemUiVisibility(0x0)
D/STATUSBAR-StatusBarManagerService( 2035): manageDisableList what=0x0 pkg=WindowManager.LayoutParams
D/STATUSBAR-QuickSettingPanel( 2203): mDisplayButtonCnt : 9 mCurButtonNum : 5
W/STATUSBAR-QuickSettingPanel( 2203): prepareTranslationXfalse  237
D/STATUSBAR-PhoneStatusBar( 2203): onConfigurationChanged
D/STATUSBAR-NetworkController( 2203): onReceive() - ACTION_CONFIGURATION_CHANGED
D/STATUSBAR-Clock( 2203): onReceive() - ACTION_CONFIGURATION_CHANGED
D/STATUSBAR-Clock( 2203): onReceive() - ACTION_CONFIGURATION_CHANGED
D/KeyguardViewMediator( 2035): setHidden false
D/KeyguardViewMediator( 2035): setHidden false
V/WindowManager( 2035): rotationForOrientationLw(orient=1, last=0); user=0 USER_ROTATION_LOCKED mLidState=-1 mCoverState=-1 mDockMode=0 mHdmiPlugged=false mAccelerometerDefault=false sensorRotation=-1
D/KeyguardViewMediator( 2035): setHidden false
D/KeyguardViewMediator( 2035): setHidden false
D/KeyguardViewMediator( 2035): setHidden false
D/KeyguardViewMediator( 2035): setHidden false
D/KeyguardViewMediator( 2035): setHidden false
D/KeyguardViewMediator( 2035): setHidden false
W/IInputConnectionWrapper(14599): showStatusIcon on inactive InputConnection
V/Camera  (14264): onDestroy
D/KeyguardViewMediator( 2035): setHidden false
D/KeyguardViewMediator( 2035): setHidden false
I/power   ( 2035): *** release_dvfs_lock : lockType : 1
D/PowerManagerService( 2035): releaseDVFSLockLocked : all DVFS_MIN_LIMIT are released
W/ActivityManager( 2035): mDVFSLock.release()
V/MenuResourceDepot(14264): onDestroy
V/MenuResourceDepot(14264): clearAllMenus
V/MenuResourceDepot(14264): clearing...
V/MenuResourceDepot(14264): clearing...
V/SoundPoolThread(14264): Got message m=1, mData=0
V/SoundPoolThread(14264): goodbye
V/SoundPoolThread(14264): return from quit
V/AudioPolicyManagerBase( 1706): releaseOutput() 2
V/SoundPoolThread(14264): return from quit
I/AudioService( 2035):  AudioFocus  abandonAudioFocus() from 14264
V/SoundPoolThread(14264): Got message m=1, mData=0
V/SoundPoolThread(14264): goodbye
V/SoundPoolThread(14264): return from quit
V/SoundPoolThread(14264): return from quit
V/AbstractCameraActivity(14264): onDestroy
D/KeyguardViewMediator( 2035): setHidden false
D/ReuseBitmapDrawableCache(31544): OnEntryAdded(key = /storage/sdcard0/Android/data/com.myapp/files/Pictures/myapp/IMG_20160203_142225.jpg;0x300)
D/KeyguardViewMediator( 2035): setHidden false
D/dalvikvm(31544): GC_FOR_ALLOC freed 530K, 15% free 29373K/34439K, paused 42ms, total 44ms
D/KeyguardViewMediator( 2035): setHidden false
I/dalvikvm-heap(31544): Grow heap (frag case) to 32.158MB for 2457616-byte allocation
D/KeyguardViewMediator( 2035): setHidden false
W/PowerManagerService( 2035): Timer 0x3->0x3|0x0
V/CropImage(14599): onDestroy
D/KeyguardViewMediator( 2035): setHidden false
D/dalvikvm(31544): GC_CONCURRENT freed 21K, 14% free 31751K/36871K, paused 14ms+5ms, total 66ms
D/dalvikvm(31544): WAIT_FOR_CONCURRENT_GC blocked 53ms
V/DroidLogger(31544): 2016/02/03 14:22:37:908 Image was loaded - Image size: 19827, Path: /storage/sdcard0/Android/data/com.myapp/files/Pictures/myapp/IMG_20160203_142225.jpg
D/dalvikvm( 2035): WAIT_FOR_CONCURRENT_GC blocked 0ms
D/dalvikvm( 2035): GC_EXPLICIT freed 479K, 51% free 23418K/46983K, paused 6ms+14ms, total 181ms

@daniel-luberda
Copy link
Member

@coonmoo I don't see anything useful information in logs. My guess: Do you have TransparencyEnabled set to true?

@coonmoo
Copy link

coonmoo commented Feb 3, 2016

I'm using the FFImageLoading defaults.

CachedImage is configured like this:

DownsampleToViewSize=true;
DownsampleUseDipUnits=true;
CacheDuration = TimeSpan.FromDays(10);
DownsampleWidth = 200;
Aspect=Aspect.AspectFill;

@daniel-luberda
Copy link
Member

Try with TransparencyEnabled set to true. Could send me source file so I could analyze its exif data? (my email is on my github profle) (/storage/sdcard0/Android/data/com.myapp/files/Pictures/myapp/IMG_20160203_142225.jpg)

@coonmoo
Copy link

coonmoo commented Feb 8, 2016

I sent you the image.

Btw it happens on the specific device to every image captured from the camera.

TransparencyEnabled=true makes not difference.

@coonmoo
Copy link

coonmoo commented Feb 9, 2016

The issue with black Images is definitely caused by the exif rotation change.

I reverted to FFImageLoading 2.0.7 alpha and have no issues when capturing images.

Then I tried again your dlls and the captured image was black again.

Can I assist you any further with this issue?

@coonmoo
Copy link

coonmoo commented Feb 15, 2016

I spent a few hours debugging this issue on my low end devices.

I finally have a solution which works very well across all devices:
http://pastebin.com/5FVrJ68Y

I modified: ExifExtensions.ToRotatedBitmap with the provided function.

@daniel-luberda
Copy link
Member

@coonmoo Thanks, sorry for answering so late, I had a very busy week. I'll test your changes and merge it into master. Great! Nice work :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants