3

I'm trying to store large 4096x3072 SKBitmap images with lossless compression as fast as I can. I've tried storing them as PNG using SKImage.FromBitmap(bitmap).Encode(SKEncodedImageFormat.Png, 100) but this was really slow. Then using information from this question and this example code I made a method to store them as a Tiff image, which was a lot faster but still not fast enough for my purposes. The code has to work on Linux as well. This is my current code:

public static class SKBitmapExtensions
{
    public static void SaveToPng(this SKBitmap bitmap, string filename)
    {
        using (Stream s = File.OpenWrite(filename))
        {
            SKData d = SKImage.FromBitmap(bitmap).Encode(SKEncodedImageFormat.Png, 100);
            d.SaveTo(s);
        }
    }
    public static void SaveToTiff(this SKBitmap img, string filename)
    {
        using (var tifImg = Tiff.Open(filename, "w"))
        {
            // Set the tiff information
            tifImg.SetField(TiffTag.IMAGEWIDTH, img.Width);
            tifImg.SetField(TiffTag.IMAGELENGTH, img.Height);
            tifImg.SetField(TiffTag.COMPRESSION, Compression.LZW);
            tifImg.SetField(TiffTag.PHOTOMETRIC, Photometric.RGB);
            tifImg.SetField(TiffTag.ROWSPERSTRIP, img.Height);
            tifImg.SetField(TiffTag.BITSPERSAMPLE, 8);
            tifImg.SetField(TiffTag.SAMPLESPERPIXEL, 4);
            tifImg.SetField(TiffTag.XRESOLUTION, 1);
            tifImg.SetField(TiffTag.YRESOLUTION, 1);
            tifImg.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);
            tifImg.SetField(TiffTag.EXTRASAMPLES, 1, new short[] { (short)ExtraSample.UNASSALPHA });

            // Copy the data
            byte[] bytes = img.Bytes;

            // Swap red and blue
            convertSamples(bytes, img.Width, img.Height);

            // Write the image into the memory buffer
            for (int i = 0; i < img.Height; i++)
                tifImg.WriteScanline(bytes, i * img.RowBytes, i, 0);
        }
    }
    private static void convertSamples(byte[] data, int width, int height)
    {
        int stride = data.Length / height;
        const int samplesPerPixel = 4;

        for (int y = 0; y < height; y++)
        {
            int offset = stride * y;
            int strideEnd = offset + width * samplesPerPixel;

            for (int i = offset; i < strideEnd; i += samplesPerPixel)
            {
                byte temp = data[i + 2];
                data[i + 2] = data[i];
                data[i] = temp;
            }
        }
    }
}

And the test code:

SKBitmap bitmap = SKBitmap.Decode("test.jpg");

Stopwatch stopwatch = new();
stopwatch.Start();

int iterations = 20;
for (int i = 0; i < iterations; i++)
    bitmap.SaveToTiff("encoded.tiff");

stopwatch.Stop();
Console.WriteLine($"Average Tiff encoding time for a {bitmap.Width}x{bitmap.Height} image = {stopwatch.ElapsedMilliseconds / iterations} ms");

stopwatch.Restart();

for (int i = 0; i < iterations; i++)
    bitmap.SaveToPng("encoded.png");

stopwatch.Stop();
Console.WriteLine($"Average PNG encoding time for a {bitmap.Width}x{bitmap.Height} image = {stopwatch.ElapsedMilliseconds / iterations} ms");

As a result I get:

Average Tiff encoding time for a 4096x3072 image = 630 ms
Average PNG encoding time for a 4096x3072 image = 3092 ms

Is there any faster way to store these images? I can imagine that I can avoid copying the data at var bytes = img.Bytes but I'm not sure how. The encoded file size for the PNG is 10.3MB and for the Tiff it is 26MB now.

MrEighteen
  • 434
  • 4
  • 16

1 Answers1

2

If you are not so interested in making the most optimal png (from a file size point of view) then you can get access to some faster encoding options through:

SKBitmap bitmap = SKBitmap.Decode("test.jpg");
using(var pixmap= bitmap.PeekPixels())
{
    var filters = SKPngEncoderFilterFlags.NoFilters;
    int compress = 0;
    var options = new SKPngEncoderOptions(filters, compress);
    using (var data = pixmap.Encode(options))
    {
        byte[] bytes = data.ToArray();
        // use data - write bytes to file etc
    }
}

In the above example:

  • compress = 0 will use no zlib compression so the pngs will effectively be similar size to an uncompressed TIFF. You could try a higher value for compress (I think 9 is maximum but slowest).
  • filters = SKPngEncoderFilterFlags.NoFilters will be fastest under all scenarios. It could make files produced with compress != 0 larger in file size. The filters option is used to try to improve compressibility at the file with SKPngEncoderFilterFlags.AllFilters producing potentially the most compressed file.
Andrew Wyatt
  • 437
  • 2
  • 15