为什么保存位图后颜色数量会发生变化?

Why does the number of colors change after saving a bitmap?

我正在创建一个单一用途的应用程序,它可以拍摄图像并通过一些 Alpha 通道调整来保存它。这些调整是通过颜色矩阵进行的,旨在仅改变 alpha 通道。我希望输出在 RGB 值方面与原始输出相同。
我正在处理使用 Photoshop 或 After Effects 创建的 32 位 PNG 图像。我 save/render 文件没有任何压缩(至少有人告诉我)。

这是两个颜色矩阵,可以帮助我说明问题。

            ColorMatrix harmlessCm = new ColorMatrix(new float[][]{
                new float[]{1, 0, 0, 0, 0},
                new float[]{0, 1, 0, 0, 0},
                new float[]{0, 0, 1, 0, 0},
                new float[]{0, 0, 0, 1, 0},
                new float[]{0, 0, 0, 0, 0}});

            ColorMatrix luminanceToAlphaCm = new ColorMatrix(new float[][]{
                new float[]{1, 0, 0, .2125f, 0},
                new float[]{0, 1, 0, .7154f, 0},
                new float[]{0, 0, 1, .0721f, 0},
                new float[]{0, 0, 0, 0, 0},
                new float[]{0, 0, 0, 0, 0}});

场景 #1。文件:32 位 PNG,无透明区域,颜色数 5707.
应用 harmlessCm 对颜色数量没有任何影响。
应用 luminanceToAlphaCm 导致颜色数量减少:1052.

场景#2。文件:具有多个半透明区域的 32 位 PNG(相同图像但没有背景),颜色数 6244.
应用 harmlessCm 导致颜色数量减少:5990。
应用 luminanceToAlphaCm 导致颜色数量减少:1470。

完整 class:

using Microsoft.WindowsAPICodePack.Dialogs;
using Prism.Commands;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace RGBA_Playground.ViewModels
{
    class MainViewModel : BaseViewModel
    {
        public string Before { get; private set; }
        public Image After { get; private set; } 

        public DelegateCommand UploadCommand { get; }
        public DelegateCommand SaveCommand { get; }

        public MainViewModel()
        {
            UploadCommand = new DelegateCommand(() =>
            {
                using (var dialog = new CommonOpenFileDialog())
                {
                    dialog.Multiselect = false;

                    if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
                    {
                        var image = dialog.FileName;
                        Before = image;
                        After = GetChangedImage(image);
                        RaisePropertyChanged("Before");
                        RaisePropertyChanged("After");
                    }
                }
            });

            SaveCommand = new DelegateCommand(() =>
            {
                var changedImage = GetChangedImage(Before);

                using (Bitmap bm = new Bitmap(changedImage))
                {
                    bm.Save(Path.GetDirectoryName(Before) + "\" + Path.GetRandomFileName() + ".png", ImageFormat.Png);
                }
            });

        private static Bitmap GetChangedImage(string path)
        {
            Bitmap original = new Bitmap(path, false);
            Bitmap result = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppArgb);
            result.SetResolution(original.HorizontalResolution, original.VerticalResolution);

            ColorMatrix cm = new ColorMatrix(new float[][]{
                new float[]{1, 0, 0, .2125f, 0},
                new float[]{0, 1, 0, .7154f, 0},
                new float[]{0, 0, 1, .0721f, 0},
                new float[]{0, 0, 0, 0, 0},
                new float[]{0, 0, 0, 0, 0}});

            using (ImageAttributes ia = new ImageAttributes())
            {
                ia.SetColorMatrix(cm);

                using (Graphics g = Graphics.FromImage(result))
                {
                    g.DrawImage(original, 
                        new Rectangle(0, 0, original.Width, original.Height),
                        0, 0, 
                        original.Width, 
                        original.Height,
                        GraphicsUnit.Pixel, 
                        ia);
                }
            }

            return result;
        }
    }
}

我需要输出在两种情况下具有相同数量的颜色。 任何帮助将不胜感激。 谢谢!

如果我最初的 post 令人困惑,我深表歉意。 另一个编辑:我忘记在 post 到这里之前回滚一些编辑。我现在正在使用另一个位图构造函数。
Bitmap result = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppArgb);
场景和结果也进行了编辑。

有两种发布图像的方式:未压缩和通过有损过程压缩。 PNG 和JPG 是后者。* 基本思想与MP3 相同。没有人真正解压缩。

(*还有一些其他的,比如 SVG,但是这可能对你没有帮助。)

如果我截取桌面屏幕截图(2560x1440,64 色深),像素为 3,686,400。每个像素 64 位是每个像素 8 个字节。所以这张图片的时钟大小约为 29,491,200 字节或 29 兆字节。没有人愿意在互联网上移动或存储这些东西。

所以像 .jpg、.png 和 .mp3 这样的压缩只从源中选择 select 几个值,并做一些涉及高端人类 color/sound 解析科学的真正高级数学以减少文件大小并且希望没有 人类可察觉的 质量损失。这就是调色板如何得到这个既不是 16、32 也不是 64 位的奇数。

由于这些原因,.PNG、.JPG 和 .MP3 等格式非常适合在 Internet 上发布图像(包括邮件和网页)。不幸的是,这也使它们对于进一步的图像处理非常不利,而这正是您所做的。您应该始终对 bitmap/uncompressed 图像进行操作。如果它是来自数码相机的图像,甚至可能有数据的预处理原始数据。

最后警告:在运输或存储过程中如何压缩并不重要。一旦加载它进行处理或显示,所有压缩步骤都必须撤消。你最终得到的更接近你开始的位图,减去一些压缩伪影。

回答我自己的问题。那么为什么保存半透明位图后颜色数量会发生变化?

看起来像 class Graphics 虽然快速且易于使用,但在处理半透明图像时实际上并不那么精确。我设法通过使用 GetPixel()SetPixel() 方法获得了预期的结果。由于性能低下、缺乏灵活性等明显原因,我避免使用它们。但它们完成了工作。

        private static Image GetNewImage(string path)
        {
            Bitmap original = new Bitmap(path);
            Bitmap result = new Bitmap(original.Width, original.Height);

            for (int x = 0; x < original.Width; x++)
            {
                for (int y = 0; y < original.Height; y++)
                {
                    var px = original.GetPixel(x, y);
                    result.SetPixel(x, y, Color.FromArgb((int)GetColorLuminance(px), px.R, px.G, px.B));
                }
            }

            return result;
        }

        private static float GetColorLuminance(Color color)
        {
            var R = color.R * .2125f;
            var G = color.G * .7154f;
            var B = color.B * .0721f;

            return R + G + B;
        }