Поддержка SkiaSharp Tiff - PullRequest
       19

Поддержка SkiaSharp Tiff

0 голосов
/ 13 мая 2018

В настоящее время SkiaSharp не поддерживает изображения в формате TIFF.(Он поддерживает jpg, gif, bmp, png и некоторые другие.)

Как преобразовать изображение tiff в объект SKBitmap?

Одна идея: возможно, существует эффективный способпреобразовать поток tiff> поток png> объект SKBitmap?Я не уверен, что System.Drawing может эффективно обрабатывать поток tiff> png.Другой возможный вариант - LibTiff.Net , хотя потребуется пример того, как преобразовать поток TIFF в поток PNG.

Другая идея: получить доступ к пикселям TIFF и нарисовать их непосредственно наSKCanvas?

Другие идеи?

Ответы [ 2 ]

0 голосов
/ 16 мая 2018

@ 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 мс.

Надеюсь, я не звучу слишком резко / негативно по отношению к вашему коду, я не имею в виду это никоим образом.

0 голосов
/ 13 мая 2018

Я не эксперт, поэтому я приветствую любого эксперта, который может сделать этот код более эффективным (или у него совершенно другие идеи, чтобы получить представление в 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
    }
}
...