SkiaSharp Tiff 支持
SkiaSharp Tiff support
目前,SkiaSharp 不支持 tiff 图像。 (它支持 jpg、gif、bmp、png 和其他一些格式。)
如何将 tiff 图像转换为 SKBitmap 对象?
一个想法:也许有一种有效的方法来转换 tiff 流 > png 流 > SKBitmap 对象?我不确定 System.Drawing
能否有效处理 tiff>png 流。另一个可能的选择是 LibTiff.Net,但需要一个示例来说明如何将 tiff 流转换为 png 流。
另一个想法:访问 tiff 像素并将其直接绘制到 SKCanvas 上?
其他想法?
我不是专家,所以我欢迎任何可以使此代码更高效(或有完全不同的想法将 tiff 转换为 SKBitmap)的专家。
这使用了LibTiff.Net
using BitMiracle.LibTiff.Classic;
. . . .
public static void ConvertTiffToSKBitmap(MemoryStream tifImage)
{
SKColor[] pixels;
int width, height;
// open a Tiff stored in the memory stream, and grab its pixels
using (Tiff tifImg = Tiff.ClientOpen("in-memory", "r", tifImage, new TiffStream()))
{
FieldValue[] value = tifImg.GetField(TiffTag.IMAGEWIDTH);
width = value[0].ToInt();
value = tifImg.GetField(TiffTag.IMAGELENGTH);
height = value[0].ToInt();
// Read the image into the memory buffer
int[] raster = new int[width * height];
if (!tifImg.ReadRGBAImageOriented(width, height, raster, Orientation.TOPLEFT))
{
// Not a valid TIF image.
}
// store the pixels
pixels = new SKColor[width * height];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int arrayOffset = y * width + x;
int rgba = raster[arrayOffset];
pixels[arrayOffset] = new SKColor((byte)Tiff.GetR(rgba), (byte)Tiff.GetG(rgba), (byte)Tiff.GetB(rgba), (byte)Tiff.GetA(rgba));
}
}
}
using (SKBitmap bitmap = new SKBitmap(width, height))
{
bitmap.Pixels = pixels;
// do something with the SKBitmap
}
}
@DougS
您的实现大部分是正确的,但由于多次内存分配和复制,性能不是很好。
我注意到您正在创建 3 个内存块,每个内存块的总大小为(w*h*4 字节):
// the int[]
raster = new int[width * height];
// the SKColor[]
pixels = new SKColor[width * height];
// the bitmap
bitmap = new SKBitmap(width, height)
你也在多次复制内存之间的像素:
// decode the TIFF (first copy)
tifImg.ReadRGBAImageOriented(width, height, raster, Orientation.TOPLEFT)
// convert to SKColor (second copy)
pixels[arrayOffset] = new SKColor(...);
// set bitmap pixels (third copy)
bitmap.Pixels = pixels;
我想我设法创建了一个类似的解码流的方法,只有一个副本和内存分配:
public static SKBitmap OpenTiff(Stream tiffStream)
{
// open a TIFF stored in the stream
using (var tifImg = Tiff.ClientOpen("in-memory", "r", tiffStream, new TiffStream()))
{
// read the dimensions
var width = tifImg.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
var height = tifImg.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
// create the bitmap
var bitmap = new SKBitmap();
var info = new SKImageInfo(width, height);
// create the buffer that will hold the pixels
var raster = new int[width * height];
// get a pointer to the buffer, and give it to the bitmap
var ptr = GCHandle.Alloc(raster, GCHandleType.Pinned);
bitmap.InstallPixels(info, ptr.AddrOfPinnedObject(), info.RowBytes, null, (addr, ctx) => ptr.Free(), null);
// read the image into the memory buffer
if (!tifImg.ReadRGBAImageOriented(width, height, raster, Orientation.TOPLEFT))
{
// not a valid TIF image.
return null;
}
// swap the red and blue because SkiaSharp may differ from the tiff
if (SKImageInfo.PlatformColorType == SKColorType.Bgra8888)
{
SKSwizzle.SwapRedBlue(ptr.AddrOfPinnedObject(), raster.Length);
}
return bitmap;
}
}
要点住在这里:https://gist.github.com/mattleibow/0a09babdf0dc9d2bc3deedf85f9b57d6
让我解释一下代码...我基本上是像您一样创建 int[]
,然后将其传递给 SKBitmap
并让它接管。我固定它,因为 SKBitmap
位于非托管内存中,GC 可能会移动它,但我肯定会在处理位图时取消固定它。
这是更详细的步骤:
// this does not actually allocate anything
// - the size is 0x0 / 0 bytes of pixels
var bitmap = new SKBitmap();
// I create the only buffer for pixel data
var raster = new int[width * height];
// pin the managed array so it can be passed to unmanaged memory
var ptr = GCHandle.Alloc(raster, GCHandleType.Pinned);
// pass the pointer of the array to the bitmap
// making sure to free the pinned memory in the dispose delegate
// - this is also not an allocation, as the memory already exists
bitmap.InstallPixels(info, ptr.AddrOfPinnedObject(), info.RowBytes, null, (addr, ctx) => ptr.Free(), null);
// the first and only copy from the TIFF stream into memory
tifImg.ReadRGBAImageOriented(width, height, raster, Orientation.TOPLEFT)
// an unfortunate extra memory operation for some platforms
// - this is usually just for Windows as it uses a BGR color format
// - Linux, macOS, iOS, Android all are RGB, so no swizzle is needed
SKSwizzle.SwapRedBlue(ptr.AddrOfPinnedObject(), raster.Length);
仅对于调试会话中的一些原始统计数据,您的代码对我的一张图像大约需要 500 毫秒,但我的代码只需要 20 毫秒。
我希望我对你的代码听起来不会太过harsh/negative,我不是那个意思。
目前,SkiaSharp 不支持 tiff 图像。 (它支持 jpg、gif、bmp、png 和其他一些格式。)
如何将 tiff 图像转换为 SKBitmap 对象?
一个想法:也许有一种有效的方法来转换 tiff 流 > png 流 > SKBitmap 对象?我不确定 System.Drawing
能否有效处理 tiff>png 流。另一个可能的选择是 LibTiff.Net,但需要一个示例来说明如何将 tiff 流转换为 png 流。
另一个想法:访问 tiff 像素并将其直接绘制到 SKCanvas 上?
其他想法?
我不是专家,所以我欢迎任何可以使此代码更高效(或有完全不同的想法将 tiff 转换为 SKBitmap)的专家。
这使用了LibTiff.Net
using BitMiracle.LibTiff.Classic;
. . . .
public static void ConvertTiffToSKBitmap(MemoryStream tifImage)
{
SKColor[] pixels;
int width, height;
// open a Tiff stored in the memory stream, and grab its pixels
using (Tiff tifImg = Tiff.ClientOpen("in-memory", "r", tifImage, new TiffStream()))
{
FieldValue[] value = tifImg.GetField(TiffTag.IMAGEWIDTH);
width = value[0].ToInt();
value = tifImg.GetField(TiffTag.IMAGELENGTH);
height = value[0].ToInt();
// Read the image into the memory buffer
int[] raster = new int[width * height];
if (!tifImg.ReadRGBAImageOriented(width, height, raster, Orientation.TOPLEFT))
{
// Not a valid TIF image.
}
// store the pixels
pixels = new SKColor[width * height];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int arrayOffset = y * width + x;
int rgba = raster[arrayOffset];
pixels[arrayOffset] = new SKColor((byte)Tiff.GetR(rgba), (byte)Tiff.GetG(rgba), (byte)Tiff.GetB(rgba), (byte)Tiff.GetA(rgba));
}
}
}
using (SKBitmap bitmap = new SKBitmap(width, height))
{
bitmap.Pixels = pixels;
// do something with the SKBitmap
}
}
@DougS
您的实现大部分是正确的,但由于多次内存分配和复制,性能不是很好。
我注意到您正在创建 3 个内存块,每个内存块的总大小为(w*h*4 字节):
// the int[]
raster = new int[width * height];
// the SKColor[]
pixels = new SKColor[width * height];
// the bitmap
bitmap = new SKBitmap(width, height)
你也在多次复制内存之间的像素:
// decode the TIFF (first copy)
tifImg.ReadRGBAImageOriented(width, height, raster, Orientation.TOPLEFT)
// convert to SKColor (second copy)
pixels[arrayOffset] = new SKColor(...);
// set bitmap pixels (third copy)
bitmap.Pixels = pixels;
我想我设法创建了一个类似的解码流的方法,只有一个副本和内存分配:
public static SKBitmap OpenTiff(Stream tiffStream)
{
// open a TIFF stored in the stream
using (var tifImg = Tiff.ClientOpen("in-memory", "r", tiffStream, new TiffStream()))
{
// read the dimensions
var width = tifImg.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
var height = tifImg.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
// create the bitmap
var bitmap = new SKBitmap();
var info = new SKImageInfo(width, height);
// create the buffer that will hold the pixels
var raster = new int[width * height];
// get a pointer to the buffer, and give it to the bitmap
var ptr = GCHandle.Alloc(raster, GCHandleType.Pinned);
bitmap.InstallPixels(info, ptr.AddrOfPinnedObject(), info.RowBytes, null, (addr, ctx) => ptr.Free(), null);
// read the image into the memory buffer
if (!tifImg.ReadRGBAImageOriented(width, height, raster, Orientation.TOPLEFT))
{
// not a valid TIF image.
return null;
}
// swap the red and blue because SkiaSharp may differ from the tiff
if (SKImageInfo.PlatformColorType == SKColorType.Bgra8888)
{
SKSwizzle.SwapRedBlue(ptr.AddrOfPinnedObject(), raster.Length);
}
return bitmap;
}
}
要点住在这里:https://gist.github.com/mattleibow/0a09babdf0dc9d2bc3deedf85f9b57d6
让我解释一下代码...我基本上是像您一样创建 int[]
,然后将其传递给 SKBitmap
并让它接管。我固定它,因为 SKBitmap
位于非托管内存中,GC 可能会移动它,但我肯定会在处理位图时取消固定它。
这是更详细的步骤:
// this does not actually allocate anything
// - the size is 0x0 / 0 bytes of pixels
var bitmap = new SKBitmap();
// I create the only buffer for pixel data
var raster = new int[width * height];
// pin the managed array so it can be passed to unmanaged memory
var ptr = GCHandle.Alloc(raster, GCHandleType.Pinned);
// pass the pointer of the array to the bitmap
// making sure to free the pinned memory in the dispose delegate
// - this is also not an allocation, as the memory already exists
bitmap.InstallPixels(info, ptr.AddrOfPinnedObject(), info.RowBytes, null, (addr, ctx) => ptr.Free(), null);
// the first and only copy from the TIFF stream into memory
tifImg.ReadRGBAImageOriented(width, height, raster, Orientation.TOPLEFT)
// an unfortunate extra memory operation for some platforms
// - this is usually just for Windows as it uses a BGR color format
// - Linux, macOS, iOS, Android all are RGB, so no swizzle is needed
SKSwizzle.SwapRedBlue(ptr.AddrOfPinnedObject(), raster.Length);
仅对于调试会话中的一些原始统计数据,您的代码对我的一张图像大约需要 500 毫秒,但我的代码只需要 20 毫秒。
我希望我对你的代码听起来不会太过harsh/negative,我不是那个意思。