-
Notifications
You must be signed in to change notification settings - Fork 549
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
Image auto orientation method #836
Comments
Does this feature exist in Skia? |
At the moment it doesn't exist. But it can be solved with this method. private static SKBitmap AutoOrient(SKBitmap bitmap, SKEncodedOrigin origin)
{
SKBitmap rotated;
switch (origin)
{
case SKEncodedOrigin.BottomRight:
using (var surface = new SKCanvas(bitmap))
{
surface.RotateDegrees(180, bitmap.Width / 2, bitmap.Height / 2);
surface.DrawBitmap(bitmap.Copy(), 0, 0);
}
return bitmap;
case SKEncodedOrigin.RightTop:
rotated = new SKBitmap(bitmap.Height, bitmap.Width);
using (var surface = new SKCanvas(rotated))
{
surface.Translate(rotated.Width, 0);
surface.RotateDegrees(90);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
case SKEncodedOrigin.LeftBottom:
rotated = new SKBitmap(bitmap.Height, bitmap.Width);
using (var surface = new SKCanvas(rotated))
{
surface.Translate(0, rotated.Height);
surface.RotateDegrees(270);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
default:
return bitmap;
}
} |
I don't understand the issue here. Exif meta data just tells the consumer of that image how it should be displayed but doesn't effect the actual pixel data. That meta data isn't required nor is it guaranteed to work everywhere. You can't rely on it. |
@Gillibald, the problem is that when the image is taken from a mobile phone it is always saved in the orientation the device camera is built into the device(In most cases it is landscape view). So, if you want to read the image correctly you have to read EXIF Orientation data how the device was rotated when the picture was taken. |
It is not guaranteed that the camera driver is writing that meta data to the produced image. So you need to deal with that anyways. |
@deivyd321 I agree that this would be good to have, but if something doesn't exist in Skia then it's unlikely to end up in SkiaSharp. So using your posted code will be best until this ends up in Skia and subsequently gets taken into SkiaSharp. |
Short of doing the auto rotation, it would be helpful to store SKEncodedOrigin in SKBitmap. Given that SKBitmap uses SKCodec internally, it should be easy to retain the encoded origin when available. |
It took my brain awhile to catch up on it, so to save others time here is the code I am using. I have tested this on all origins and it works. public Stream FixImageOrientation(Stream stream, out int width, out int height)
{
try { stream.Position = 0; } catch (NotSupportedException) { }
using (var inputStream = new SKManagedStream(stream))
{
using (var codec = SKCodec.Create(inputStream))
{
using (var original = SKBitmap.Decode(codec))
{
var useWidth = original.Width;
var useHeight = original.Height;
Action<SKCanvas> transform = canvas => { };
switch (codec.EncodedOrigin)
{
case SKEncodedOrigin.TopLeft:
break;
case SKEncodedOrigin.TopRight:
// flip along the x-axis
transform = canvas => canvas.Scale(-1, 1, useWidth / 2, useHeight / 2);
break;
case SKEncodedOrigin.BottomRight:
transform = canvas => canvas.RotateDegrees(180, useWidth / 2, useHeight / 2);
break;
case SKEncodedOrigin.BottomLeft:
// flip along the y-axis
transform = canvas => canvas.Scale(1, -1, useWidth / 2, useHeight / 2);
break;
case SKEncodedOrigin.LeftTop:
useWidth = original.Height;
useHeight = original.Width;
transform = canvas =>
{
// Rotate 90
canvas.RotateDegrees(90, useWidth / 2, useHeight / 2);
canvas.Scale(useHeight * 1.0f / useWidth, -useWidth * 1.0f / useHeight, useWidth / 2, useHeight / 2);
};
break;
case SKEncodedOrigin.RightTop:
useWidth = original.Height;
useHeight = original.Width;
transform = canvas =>
{
// Rotate 90
canvas.RotateDegrees(90, useWidth / 2, useHeight / 2);
canvas.Scale(useHeight * 1.0f / useWidth, useWidth * 1.0f / useHeight, useWidth / 2, useHeight / 2);
};
break;
case SKEncodedOrigin.RightBottom:
useWidth = original.Height;
useHeight = original.Width;
transform = canvas =>
{
// Rotate 90
canvas.RotateDegrees(90, useWidth / 2, useHeight / 2);
canvas.Scale(-useHeight * 1.0f / useWidth, useWidth * 1.0f / useHeight, useWidth / 2, useHeight / 2);
};
break;
case SKEncodedOrigin.LeftBottom:
useWidth = original.Height;
useHeight = original.Width;
transform = canvas =>
{
// Rotate 90
canvas.RotateDegrees(90, useWidth / 2, useHeight / 2);
canvas.Scale(-useHeight * 1.0f / useWidth, -useWidth * 1.0f / useHeight, useWidth / 2, useHeight / 2);
};
break;
default:
break;
}
var info = new SKImageInfo(useWidth, useHeight);
using (var surface = SKSurface.Create(info))
{
using (var paint = new SKPaint())
{
// high quality with antialiasing
paint.IsAntialias = true;
paint.FilterQuality = SKFilterQuality.High;
// rotate according to origin
transform.Invoke(surface.Canvas);
// draw the bitmap to fill the surface
surface.Canvas.DrawBitmap(original, info.Rect, paint);
surface.Canvas.Flush();
using (var image = surface.Snapshot())
{
var output = new MemoryStream();
using (var data = image.Encode(SKEncodedImageFormat.Jpeg, _azureStorageConfig.OriginalImageQuality))
{
data.SaveTo(output);
width = useWidth;
height = useHeight;
return output;
}
}
}
}
}
}
}
} |
@iainxt thanks for that. I'm thinking there might be a cause to have a property/method somewhere that could at least create a matrix for this so that you can draw an image using the matrix. And then have some member that does this auto-rotate. Definitely something to have, thanks for the work. |
Please note:
So if you need to auto-orient your images, just use |
This comment was marked as resolved.
This comment was marked as resolved.
In the private static SKBitmap? AutoOrient(SKBitmap bitmap, SKEncodedOrigin origin)
{
SKBitmap rotated;
switch (origin)
{
case SKEncodedOrigin.BottomRight:
rotated = new SKBitmap(bitmap.Width, bitmap.Height);
using (var surface = new SKCanvas(rotated))
{
surface.RotateDegrees(180, bitmap.Width / 2, bitmap.Height / 2);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
case SKEncodedOrigin.RightTop:
rotated = new SKBitmap(bitmap.Height, bitmap.Width);
using (var surface = new SKCanvas(rotated))
{
surface.Translate(rotated.Width, 0);
surface.RotateDegrees(90);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
case SKEncodedOrigin.LeftBottom:
rotated = new SKBitmap(bitmap.Height, bitmap.Width);
using (var surface = new SKCanvas(rotated))
{
surface.Translate(0, rotated.Height);
surface.RotateDegrees(270);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
default:
return null;
}
} I return null if there is no rotation to do, so in the calling method I can easilly know if I have to close the given SKBitmap after the execution of this method or not. for example: var newBitmap = AutoOrient(Bitmap, orientation);
if (newBitmap != null)
{
Bitmap.Dispose();
Bitmap = newBitmap;
} |
Would be great if Currently it is not supported and we either have to create an extra surface like people proposed above (which is a waste of memory and CPU, especially for 4K images) or manually apply rotation on byte array before creating image (which is complex to do correctly). |
How to get _azureStorageConfig.OriginalImageQuality ? |
When uploading an image from mobile, it is always rotated to landscape view. But when you access the image in code you usually want to save it in correct orientation.
At the moment, there is no easy way to rotate the image. So it would be nice to have something like
AutoOrient()
which would correctly rotate the image based on its EXIF data.The text was updated successfully, but these errors were encountered: