SKBitmap图像的快速无损编码

3

我正在尝试以尽可能快的速度存储大型4096x3072的SKBitmap图像,使用无损压缩。我尝试将它们存储为PNG,使用SKImage.FromBitmap(bitmap).Encode(SKEncodedImageFormat.Png, 100),但是这非常慢。然后使用问题和示例代码中的信息,我制作了一种方法来将它们存储为Tiff图像,这样速度就快多了,但对于我的目的仍然不够快。该代码必须在Linux上工作。这是我的当前代码:

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;
            }
        }
    }
}

测试代码如下:

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");

作为结果,我得到:
Average Tiff encoding time for a 4096x3072 image = 630 ms
Average PNG encoding time for a 4096x3072 image = 3092 ms

有没有更快的方法来存储这些图片呢?我可以想象我可以避免在var bytes = img.Bytes处复制数据,但我不确定如何操作。现在PNG的编码文件大小为10.3MBTiff的编码文件大小为26MB

1个回答

3

如果您对制作最优化的PNG(从文件大小的角度来看)不太感兴趣,那么您可以通过以下方式获得一些更快的编码选项:

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
    }
}

在上面的示例中:
  • compress = 0 将不使用zlib压缩,因此png文件的大小与未压缩的TIFF文件大小相似。您可以尝试更高的压缩值(我认为9是最大但速度最慢的)。
  • filters = SKPngEncoderFilterFlags.NoFilters 在所有情况下都是最快的。它可能会使使用 compress != 0 生成的文件更大。过滤器选项用于尝试在文件中提高可压缩性,使用 SKPngEncoderFilterFlags.AllFilters 可能会产生最大程度的压缩文件。

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