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

[Android] EXIF rotation #1123

Closed
deakjahn opened this issue Sep 25, 2018 · 7 comments
Closed

[Android] EXIF rotation #1123

deakjahn opened this issue Sep 25, 2018 · 7 comments

Comments

@deakjahn
Copy link

deakjahn commented Sep 25, 2018

Description

I'm aware of #130 but that's is old and I still have problems, although only on Android. Could it be that the platform doesn't support all orientations and FF simply relies on what the platform provides?

Steps to Reproduce

    <StackLayout Orientation="Vertical">
      <ff:CachedImage x:Name="i1" HeightRequest="150" />
      <ff:CachedImage x:Name="i2" HeightRequest="150" />
      <ff:CachedImage x:Name="i3" HeightRequest="150" />
      <ff:CachedImage x:Name="i4" HeightRequest="150" />
      <ff:CachedImage x:Name="i5" HeightRequest="150" />
      <ff:CachedImage x:Name="i6" HeightRequest="150" />
      <ff:CachedImage x:Name="i7" HeightRequest="150" />
      <ff:CachedImage x:Name="i8" HeightRequest="150" />
    </StackLayout>
    public MainPage() {
      InitializeComponent();

      i1.Source = ImageSource.FromUri(new System.Uri("https://github.com/recurser/exif-orientation-examples/raw/master/Landscape_1.jpg"));
      i2.Source = ImageSource.FromUri(new System.Uri("https://github.com/recurser/exif-orientation-examples/raw/master/Landscape_2.jpg"));
      i3.Source = ImageSource.FromUri(new System.Uri("https://github.com/recurser/exif-orientation-examples/raw/master/Landscape_3.jpg"));
      i4.Source = ImageSource.FromUri(new System.Uri("https://github.com/recurser/exif-orientation-examples/raw/master/Landscape_4.jpg"));
      i5.Source = ImageSource.FromUri(new System.Uri("https://github.com/recurser/exif-orientation-examples/raw/master/Landscape_5.jpg"));
      i6.Source = ImageSource.FromUri(new System.Uri("https://github.com/recurser/exif-orientation-examples/raw/master/Landscape_6.jpg"));
      i7.Source = ImageSource.FromUri(new System.Uri("https://github.com/recurser/exif-orientation-examples/raw/master/Landscape_7.jpg"));
      i8.Source = ImageSource.FromUri(new System.Uri("https://github.com/recurser/exif-orientation-examples/raw/master/Landscape_8.jpg"));
    }

The pictures used are from the https://github.com/recurser/exif-orientation-examples project, two sets of 8 images each with all possible EXIF orientations.

Expected Behavior

All images rotated as the EXIF dictates.

Actual Behavior

screenshot_2018-09-25-19-49-18

Basic Information

  • Version with issue: 2.4.3840
@daniel-luberda
Copy link
Member

Hi @deakjahn This is the code responsible for EXIF rotation on Android: https://github.com/luberda-molinet/FFImageLoading/blob/master/source/FFImageLoading.Droid/Decoders/BaseDecoder.cs#L62-L83

Do you see any issue? (PR welcome)

@deakjahn
Copy link
Author

deakjahn commented Sep 27, 2018

Well, these are the most often used values, that's true, however, in theory, there are eight values... I don't know how sure you can be of those never ever popping up. Even if not that likely, it would be rather easy to add.

Send a new argument like ToRotatedBitmap(this Bitmap sourceBitmap, int rotationDegrees, int scale), and pass that value from the switch you show as well.

@daniel-luberda
Copy link
Member

@deakjahn Feel free to make a PR :)

@deakjahn
Copy link
Author

I could very easily send you source code but sorry, I don't have the tools installed and don't want to disturb my current work setup in order to be able to create real requests. If this suits you, I'm completely game. :-)

@daniel-luberda
Copy link
Member

Sure :) That would be great too.

@deakjahn
Copy link
Author

deakjahn commented Nov 16, 2018

You check for the specific values of 3, 6 and 8 in BaseDecoder.DecodeAsync(). Actually, you can be cleverer than that if you realize that the EXIF orientation value is in fact a bit field where the individual bits describe various rotating and flipping transformations (see https://magnushoff.com/jpeg-orientation.html). So, what I would suggest is to send the original orientationValue instead of exifRotation to ToRotatedBitmap() and solve all orientations there:

    public static Bitmap ToRotatedBitmap(this Bitmap bitmap, int orientation) {
      if (orientation == 0)
        return bitmap;
      else {
        var rotated = (orientation.IsBitSet(2)) ? new SKBitmap(size.Height, size.Width) : new SKBitmap(size.Width, size.Height);
        using (var canvas = new SKCanvas(rotated)) {
          var matrix = SKMatrix.MakeIdentity();
          if (orientation.IsBitSet(2)) {
            SKMatrix.PostConcat(ref matrix, SKMatrix.MakeRotationDegrees(90, rotated.Width / 2, rotated.Height / 2));
            SKMatrix.PostConcat(ref matrix, SKMatrix.MakeScale(-1, 1, rotated.Width / 2, rotated.Height / 2));
          }
          if (orientation.IsBitSet(1))
            SKMatrix.PostConcat(ref matrix, SKMatrix.MakeScale(-1, -1, rotated.Width / 2, rotated.Height / 2));
          if (orientation.IsBitSet(0))
            SKMatrix.PostConcat(ref matrix, SKMatrix.MakeScale(-1, 1, rotated.Width / 2, rotated.Height / 2));
          canvas.SetMatrix(matrix);
          canvas.DrawBitmap(bitmap, (rotated.Width - size.Width) / 2, (rotated.Height - size.Height) / 2);
          bitmap.Dispose();
          return rotated;
        }
      }
    }

IsBitSet() is just a convenience function I use, it could be simply inlined:

public static bool IsBitSet(this int value, int bit) => ((value & (1 << bit)) > 0);

@daniel-luberda
Copy link
Member

Fixed. Thanks @deakjahn for details. Cheers.

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

2 participants