【问题标题】:Save as 8bit PNG另存为 8 位 PNG
【发布时间】:2014-11-21 11:25:05
【问题描述】:

我正在创建将用作数据 URI 的特定大小的占位符图像

Bitmap bitmap = new Bitmap(16, 10);

我进行了一些研究,但找不到将此位图保存为尽可能小的文件大小的好方法,这就是我想要 8 位 PNG 的原因。

我的问题是:如何将此位图作为 8 位 PNG 保存到文件/字节数组/流中?有什么好的图书馆吗?

【问题讨论】:

  • Leadtools 有一个很好的库来处理位图。
  • 嗯,您不能为此使用更好的解决方案吗?为什么需要位图?
  • @Vajura 你是对的。我不需要使用位图。我只需要一个小文件大小的透明 PNG。
  • 但是你为什么需要它呢?它们会是隐形按钮吗?稍后你会在它们下面放置图片?
  • 我正在通过 Javascript 加载响应式图像,但我需要透明占位符图像具有相同的纵横比以防止页面跳转

标签: c# asp.net .net


【解决方案1】:

您可以使用 nQuant 来执行此操作(您可以使用 nuget 安装它,或查看下面的参考资料)。下面的示例转换磁盘上的图像,并且很容易适应您的需要。

    public static bool TryNQuantify(string inputFilename, string outputFilename)
    {
        var quantizer = new nQuant.WuQuantizer();
        var bitmap = new Bitmap(inputFilename);
        if (bitmap.PixelFormat != System.Drawing.Imaging.PixelFormat.Format32bppArgb)
        {
            ConvertTo32bppAndDisposeOriginal(ref bitmap);
        }

        try
        {
            using (var quantized = quantizer.QuantizeImage(bitmap))
            {
                quantized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Png);
            }
        }
        catch
        {
            return false;
        }
        finally
        {
            bitmap.Dispose();
        }
        return true;
    }

    private static void ConvertTo32bppAndDisposeOriginal(ref Bitmap img)
    {
        var bmp = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
        using (var gr = Graphics.FromImage(bmp))
            gr.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));
        img.Dispose();
        img = bmp;
    }

欲了解更多信息,请参阅:

【讨论】:

  • 这对我来说效果很好,但重要的是要注意,当我处理高度大于 4550 像素的图像时,它无法生成图像并且输出是一个空的灰色图像。
【解决方案2】:

我喜欢 FreeImage 项目,它重量轻且易于使用。下面是创建透明 png 的示例。您可以轻松地将其包装在一个方法中并设置宽度和高度以及透明度值。

//create a new bit map
FIBITMAP dib = new FIBITMAP();

//allocate a 16x10 bitmap with 8bit depth
dib = FreeImage.Allocate(16, 10, 8);

//set the transpareny
byte[] Transparency = new byte[1];
Transparency[0] = 0x00;
FreeImage.SetTransparencyTable(dib, Transparency);

//save the bitmap
FreeImage.Save(FREE_IMAGE_FORMAT.FIF_PNG, dib, "C:\\temp\\tp.png", FREE_IMAGE_SAVE_FLAGS.DEFAULT);

【讨论】:

  • 我尝试使用 FreeImage。用 nuget 添加,但在我的项目中找不到。
  • 用老式的方法试试。从freeimage.sourceforge.net/download.html 下载,添加 freeimagenet.dll 作为参考并将 freeimage.dll 复制到您的构建文件夹。
【解决方案3】:

如果您只需要一个小的透明图像,为什么要停在 8 位?你可以直接降到1位!反正你只需要一种颜色,而且会更小。

事实上,您甚至不需要为此做任何特别的事情。由于新索引位图上的像素都将默认为 0,这意味着它们引用其第一个调色板颜色,您需要做的就是制作一个新的 1bpp 图像,并将第一个调色板颜色设置为透明:

public static Bitmap MakePlaceholderImage(Int32 width, Int32 height)
{
    Bitmap bm = new Bitmap(width, height, PixelFormat.Format1bppIndexed);
    // This colour can't be assigned directly since the .Palette getter actually makes a copy of the palette.
    ColorPalette pal = bm.Palette;
    pal.Entries[0] = Color.Transparent;
    bm.Palette = pal;
    return bm;
}

我对此进行了一些实验,并且保存为 png,最终结果似乎始终比使用 8bpp 执行相同代码的结果小 8 倍。对于 5000x5000 的图像,png 格式的文件大小几乎不超过 3 KiB。

【讨论】:

    【解决方案4】:

    Google 搜索让我找到了一个 6 年前的帖子,但我找不到我的解决方案。应该也有 6 年的可能,因为它使用了 .Net Framework 的 System.Drawing Libraray。

    此示例代码应该可以帮助任何想要编写 8 位索引 png 的人。

    static class SaveImages
    {
        public static void SaveGrayImage(Bitmap srcImg, string name)
        {
            Bitmap grayImg = new Bitmap(srcImg.Width, srcImg.Height, PixelFormat.Format8bppIndexed);
            var pal = grayImg.Palette;
            foreach (int i in Enumerable.Range(0, 256))
                pal.Entries[i] = Color.FromArgb(255, i, i, i);
            grayImg.Palette = pal;
    
            var data = grayImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
            var bytes = new byte[data.Stride * data.Height];
            foreach (int y in Enumerable.Range(0, srcImg.Height))
            {
                foreach (int x in Enumerable.Range(0, srcImg.Width))
                {
                    var colour = srcImg.GetPixel(x, y);
                    var c = (int)colour.R + (int)colour.G + (int)colour.B;
                    c /= 3;
                    bytes[data.Stride * y + x] = (byte)c;
                }
            }
    
            Marshal.Copy(bytes, 0, data.Scan0, bytes.Length);
            grayImg.Save(name, ImageFormat.Png);
        }
    }
    

    编辑:使用调色板添加透明度。

    【讨论】:

      【解决方案5】:

      1) 创建每像素 8 位格式的位图:

      Bitmap bmp = new Bitmap(20, 20, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
      

      更多格式请见PixelFormat

      2) 绘制位图 ...

      3)然后将其保存为PNG:

      bmp.Save(@"c:\temp\xyz.png", System.Drawing.Imaging.ImageFormat.Png);
      

      创建的文件将具有与bmp(8 位)相同的像素格式

      【讨论】:

      • 这似乎不支持透明度,而且它还会创建一个非常大的文件大小。
      • Format8bppIndexed的格式当然是不支持透明的,我只是用这个做例子,使用适合自己需要的格式,看PixelFormat枚举。与往常一样,不要只是复制/粘贴代码,将其用作提示并根据您的需要进行改进。
      • 这里没有索引的支持透明度。透明度对于占位符图像至关重要。
      • PNG 和 GIF 都支持索引图像的透明度。您必须确保位图的托盘包含透明颜色。
      • 本说明中的步骤2)会有问题;使用标准功能无法绘制 8 位图像。您需要使用LockBits 访问/提取支持数组以对其进行操作。
      猜你喜欢
      • 2014-07-27
      • 2016-07-27
      • 1970-01-01
      • 2011-09-24
      • 2011-08-10
      • 2021-06-04
      • 2010-12-18
      • 1970-01-01
      • 2018-09-05
      相关资源
      最近更新 更多