iPhone 使用 SkiaSharp 调整大小时图像方向错误
iPhone image orientation wrong when resizing with SkiaSharp
我在 Xamarin.Forms 项目中使用 SkiaSharp 中的 SKBitmap.Resize() 方法来调整图像大小以供显示。我遇到的问题是在 iOS 上拍摄照片时,当拍摄纵向照片时,图像显示为右侧朝上。在 Android 上拍照,从 Android 和 iOS 设备上的照片库导入照片会保持方向,但在 iOS 中拍照则不会。如果我不使用 SkiaSharp 调整图像大小(仅显示图像而不进行任何调整),则图像会以正确的方向显示。但是,这不是解决方案,因为需要调整图像的大小。下面是我的代码 -
private byte[] GetResizedImageData(string imageName)
{
float resizeFactor = 0.5f;
var filePath = PathUtil.GetImagePath(imageName);
var ogBitmap = SKBitmap.Decode(filePath);
float fWidth = ogBitmap.Width * resizeFactor;
int width = (int) Math.Round(fWidth);
float fHeight = ogBitmap.Height * resizeFactor;
int height = (int) Math.Round(fHeight);
if (height >= 4096 || width >= 4096)
{
width = width * (int)resizeFactor;
height = height * (int)resizeFactor;
}
var scaledBitmap = ogBitmap.Resize(new SKImageInfo( width, height), SKBitmapResizeMethod.Box);
var image = SKImage.FromBitmap(scaledBitmap);
var data = image.Encode(SKEncodedImageFormat.Jpeg, 100);
return data.ToArray();
}
PathUtil.GetImagePath() 只是获取特定于平台的照片存储路径的助手。
SkiaSharp 实际上并没有为我提供一种方法来操纵和更改图像的方向。最后,我使用特定于平台的代码捕获并保存图像时改变了方向。
对于那些有同样问题的人,我做了以下工作,很乐意接受改进意见。
public static SKBitmap HandleOrientation(SKBitmap bitmap, SKCodecOrigin orientation)
{
SKBitmap rotated;
switch (orientation)
{
case SKCodecOrigin.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 SKCodecOrigin.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 SKCodecOrigin.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;
}
然后使用下面的方法得到原来的方向。
// TODO: Improve this.. I do not know how to "reset"
// the inputStream, so new it up twice. :/
using (var inputStream = new SKManagedStream(imageIn))
{
using (var codec = SKCodec.Create(inputStream))
{
orientation = codec.Origin;
}
}
.......
那么
SKBitmap orientedWExif = HandleOrientation(resized, orientation);
谢谢@ttugates 你的回答帮助我解决了方向旋转的问题。只是想更新您的答案,因为有一些已弃用的代码。
using (var inputStream = new SKManagedStream(await file.ReadAllStreamAsync()))
{
using (var codec = SKCodec.Create(inputStream))
{
orientation = codec.EncodedOrigin;
}
}
public static SKBitmap HandleOrientation(SKBitmap bitmap, SKEncodedOrigin orientation)
{
SKBitmap rotated;
switch (orientation)
{
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;
}
}
调整大小和手柄方向的可行解决方案
public class ImageResizer : IImageResizer
{
private const int Quality = 75;
public byte[] Resize(byte[] data, int newWidth, int newHeight)
{
using (var inputStream = new SKMemoryStream(data))
{
using (var codec = SKCodec.Create(inputStream))
{
using (var original_old = SKBitmap.Decode(codec))
{
int sourceWidth = original_old.Width;
int sourceHeight = original_old.Height;
float nPercentW = ((float) newWidth / (float) sourceWidth);
float nPercentH = ((float) newHeight / (float) sourceHeight);
float nPercent = nPercentH < nPercentW ? nPercentH : nPercentW;
int destWidth = (int) (sourceWidth * nPercent);
int destHeight = (int) (sourceHeight * nPercent);
using (SKBitmap original = original_old.Resize(new SKImageInfo(destWidth, destHeight), SKFilterQuality.Medium))
{
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;
}
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 (SKImage image = surface.Snapshot())
{
return image.Encode(SKEncodedImageFormat.Jpeg, Quality).ToArray();
}
}
}
}
}
}
}
}
}
我在 Xamarin.Forms 项目中使用 SkiaSharp 中的 SKBitmap.Resize() 方法来调整图像大小以供显示。我遇到的问题是在 iOS 上拍摄照片时,当拍摄纵向照片时,图像显示为右侧朝上。在 Android 上拍照,从 Android 和 iOS 设备上的照片库导入照片会保持方向,但在 iOS 中拍照则不会。如果我不使用 SkiaSharp 调整图像大小(仅显示图像而不进行任何调整),则图像会以正确的方向显示。但是,这不是解决方案,因为需要调整图像的大小。下面是我的代码 -
private byte[] GetResizedImageData(string imageName)
{
float resizeFactor = 0.5f;
var filePath = PathUtil.GetImagePath(imageName);
var ogBitmap = SKBitmap.Decode(filePath);
float fWidth = ogBitmap.Width * resizeFactor;
int width = (int) Math.Round(fWidth);
float fHeight = ogBitmap.Height * resizeFactor;
int height = (int) Math.Round(fHeight);
if (height >= 4096 || width >= 4096)
{
width = width * (int)resizeFactor;
height = height * (int)resizeFactor;
}
var scaledBitmap = ogBitmap.Resize(new SKImageInfo( width, height), SKBitmapResizeMethod.Box);
var image = SKImage.FromBitmap(scaledBitmap);
var data = image.Encode(SKEncodedImageFormat.Jpeg, 100);
return data.ToArray();
}
PathUtil.GetImagePath() 只是获取特定于平台的照片存储路径的助手。
SkiaSharp 实际上并没有为我提供一种方法来操纵和更改图像的方向。最后,我使用特定于平台的代码捕获并保存图像时改变了方向。
对于那些有同样问题的人,我做了以下工作,很乐意接受改进意见。
public static SKBitmap HandleOrientation(SKBitmap bitmap, SKCodecOrigin orientation)
{
SKBitmap rotated;
switch (orientation)
{
case SKCodecOrigin.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 SKCodecOrigin.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 SKCodecOrigin.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;
}
然后使用下面的方法得到原来的方向。
// TODO: Improve this.. I do not know how to "reset"
// the inputStream, so new it up twice. :/
using (var inputStream = new SKManagedStream(imageIn))
{
using (var codec = SKCodec.Create(inputStream))
{
orientation = codec.Origin;
}
}
....... 那么
SKBitmap orientedWExif = HandleOrientation(resized, orientation);
谢谢@ttugates 你的回答帮助我解决了方向旋转的问题。只是想更新您的答案,因为有一些已弃用的代码。
using (var inputStream = new SKManagedStream(await file.ReadAllStreamAsync()))
{
using (var codec = SKCodec.Create(inputStream))
{
orientation = codec.EncodedOrigin;
}
}
public static SKBitmap HandleOrientation(SKBitmap bitmap, SKEncodedOrigin orientation)
{
SKBitmap rotated;
switch (orientation)
{
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;
}
}
调整大小和手柄方向的可行解决方案
public class ImageResizer : IImageResizer
{
private const int Quality = 75;
public byte[] Resize(byte[] data, int newWidth, int newHeight)
{
using (var inputStream = new SKMemoryStream(data))
{
using (var codec = SKCodec.Create(inputStream))
{
using (var original_old = SKBitmap.Decode(codec))
{
int sourceWidth = original_old.Width;
int sourceHeight = original_old.Height;
float nPercentW = ((float) newWidth / (float) sourceWidth);
float nPercentH = ((float) newHeight / (float) sourceHeight);
float nPercent = nPercentH < nPercentW ? nPercentH : nPercentW;
int destWidth = (int) (sourceWidth * nPercent);
int destHeight = (int) (sourceHeight * nPercent);
using (SKBitmap original = original_old.Resize(new SKImageInfo(destWidth, destHeight), SKFilterQuality.Medium))
{
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;
}
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 (SKImage image = surface.Snapshot())
{
return image.Encode(SKEncodedImageFormat.Jpeg, Quality).ToArray();
}
}
}
}
}
}
}
}
}