skip to Main Content

I’m creating a single-purpose app that would take an image and save it with some alpha channel adjustments. The adjustments are made with a color matrix and are meant to alter only the alpha channel. I want the otput to be indentical to the original in terms of RGB values.
I’m working with 32-bit PNG images I create using either Photoshop or After Effects. I save/render the files without any compression (at least that’s what I’m told).

These are two color matrices that will help me illustrate the issue.

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

Scenario #1. File: 32-bit PNG with no transparent areas, number of colors 5707.
Applying the harmlessCm doesn’t have any effect on number of colors.
Applying the luminanceToAlphaCm led to decrease in number of colors: 1052.

Scenario #2. File: 32-bit PNG with multiple semi-tranparent areas (same image but without a background), number of colors 6244.
Applying the harmlessCm led to decrease in number of colors: 5990.
Applying the luminanceToAlphaCm led to decrease in number of colors: 1470.

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

I need the output to have the same number of colors in both scenarios.
Any help will be very much appreciated.
Thanks!

I’m sorry if my initial post was confusing.
Another edit: I forgot to roll back some edits before posting here. I’m using another Bitmap constructor now.
Bitmap result = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppArgb);
The scenarios and results are edited too.

2

Answers


  1. Chosen as BEST ANSWER

    Answering my own question. So why does the number of colors change after saving a semi-transparent bitmap?

    Seems like class Graphics though fast and easy to use, is acutally not that precise when working with semi-transparent images. I managed to get the desired results by using GetPixel() and SetPixel() methods. I was avoiding using them for obvious reasons such as low performance, lack of flexibility, etc. But they get the work done.

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

  2. There are two ways to publish a image: Uncompressed and compressed via a lossy process. PNG and JPG are the later.* The basic idea is the same as MP3. Nobody really does uncompressed.

    (*There are some others like SVG, but that propably will not help you here.)

    If I take a screenshoot of my desktop (2560×1440 at 64 color depth) that is 3,686,400 pixel. 64 bits per pixel is 8 bytes per pixel. So this image would be clock around 29,491,200 byte or 29 Megabyte. Nobody wants to move that stuff around the internet or store it.

    So compressions like .jpg, .png and .mp3 only pick a select few values from the source and do some really advanced math involving high end human color/sound parsing science to reduce the filesize and hopefully without human percetible quality loss. That is how the palette got this odd number that is neither 16, 32 nor 64 bits.

    For those reasons formats like .PNG, .JPG and .MP3 are very good for publishing images on the internet (including mails and webpages). Unfortunately that also makes them very bad for further image manipulation, wich is exactly what you do. You should always do manipulation on the bitmap/uncompressed image. If it is a image from a Digital camera, there might even be a pre-processing raw of the data.

    And as a final warning: It does not matter how it is compressed during transport or storage. Once you load it for processing or display, all compression steps have to be undone. What you end up is closer to the bitmap you started with, minus some compression artifacts.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search