SkiaSharp的Tiff支持

4

目前,SkiaSharp不支持tiff图像。(它支持jpg、gif、bmp、png和其他一些格式。)

如何将tiff图像转换为SKBitmap对象?

一个想法:也许有一种有效的方法可以将tiff流转换为png流,然后再将其转换为SKBitmap对象?我不确定System.Drawing是否能够高效地处理tiff>png流。另一个可能的选择是LibTiff.Net,但需要一个将tiff流转换为png流的示例。

另一个想法:访问tiff像素并直接绘制到SKCanvas上?

其他想法?

2个回答

15

@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毫秒。

我希望我对您的代码听起来不太严厉/负面,我并没有以任何方式表达这种意思。


1
嗨@Matthew。有两件事情:1)我认为你应该通过var info = new SKImageInfo(width, height, SKImageInfo.PlatformColorType, SKAlphaType.Premul, SKColorSpace.CreateSrgb());设置目标ColorSpace,这样Skia的任何进一步处理都将允许Skia知道ColorSpace是什么。2)此代码处理的CMYK Tif会导致颜色过度饱和。我们该如何解决这个问题? - Doug S
@Matthew 如果是多页TIF文件,应该怎么处理? - MShah
我对LibTiff.Net库不是很熟悉,但我认为有一种方法可以读取每一页。我使用了ReadRGBAImageOriented,因为这是原始答案中的内容。我会查看“Directory”的用法,因为我认为它是他们用作页面的东西。 - Matthew
@DougS,你找到第二个问题的解决方案了吗?(使用此代码处理的CMYK Tif会导致颜色过饱和) - maxlego
@Matthew 很棒的答案和示例代码!基于您的示例,我正在尝试编写一个函数来以高性能的方式将 SKBitmap 保存为 tiff 图像。我在这里提出了一个问题(https://dev59.com/8nUOtIcB2Jgan1znrhfW),也许您可以给予指导?;) - MrEighteen
显示剩余2条评论

2
我不是专家,所以欢迎任何能够使这个代码更高效(或者有完全不同的想法将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
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接