0

I couldn't help myself so once again I'm asking you for help. This time I will show the problem better than last time, I hope.

I'm writing a program to check if Quantization have any influence on image sizes. To do that I need to have implemented :

  1. Open PNG Image (done)
  2. "Quantize" pixel by pixel till the end of the image (done)
  3. Save (this is the problem)

PNG filter method 0 defines five basic filter types: Type Name

0 - None, 1 - Sub, 2 - Up, 3 - Average, 4 - Paeth

And now I'm standing with an image in memory that I want to save using one of that filters, but after checking multiple of PNG libraries, none of them allow me to choose one. Can anyone help me with that or at least with one filter? Here you go with some code :

private void btnSelectImg_Click(object sender, EventArgs e)
    {
        openFileDialog1.Filter = "PNG Image | *.png";
        DialogResult result = openFileDialog1.ShowDialog();

        if (result == DialogResult.OK)
        {

            string imgPath = openFileDialog1.FileName;

            tbSourceImageFile.Text = imgPath;
            string[] NameCutter = imgPath.Split('\\');
            lblFileName.Text = NameCutter.Last();

            ImageToWork = Image.FromFile(imgPath);
            System.Drawing.Imaging.ImageFormat Format = ImageToWork.RawFormat;
            tbInfo.Text += string.Format("Resolution : {0}x{1} | Bits : {2:n0} | Format : {3}", ImageToWork.Width, ImageToWork.Height, ImageToWork.Width * ImageToWork.Height, GetFilenameExtension(Format));


        }

    }
 private void btnSave_Click(object sender, EventArgs e)
    {
        #region Check Image
        if (tbSourceImageFile.Text == "")
        {
            MessageBox.Show("File not selected. Select file first.");
            return;
        }
        #endregion
        #region Operations on image

        Bitmap Image111 = new Bitmap(tbSourceImageFile.Text, true);

        #region Progress Bar Settings
        ProgressBar.Visible = true;
        ProgressBar.Value = 1;
        ProgressBar.Maximum = Image111.Width;
        ProgressBar.Step = 1;
        #endregion
        if (cboxEnableScale.Checked == true)
        {
            int red, green, blue, red2=0, blue2=0, green2=0;
            int scale = int.Parse(cbSelectorScale.SelectedItem.ToString());
            for (int w = 0; w < Image111.Width; w++)
            {
                for (int h = 0; h < Image111.Height; h++)
                {
                    Color PixelColor = Image111.GetPixel(w, h);
                    #region Quantization

                    red = PixelColor.R;
                    green = PixelColor.G;
                    blue = PixelColor.B;

                    Color newColor = Color.FromArgb(Valuator_v3(red, scale), Valuator_v3(green, scale), Valuator_v3(blue, scale));
                    Image111.SetPixel(w, h, newColor);

                    #endregion
                }
                ProgressBar.PerformStep();
            }
        }
        #endregion


        #region Saving
        string SaveDirectory = tbSaveDestination.Text + '\\' + tbSaveFileName.Text + ".bmp";

        SaveDirectory = tbSaveDestination.Text + '\\' + tbSaveFileName.Text + ".jpeg";

        Image111.Save(SaveDirectory, System.Drawing.Imaging.ImageFormat.Png);

        ProgressBar.Visible = false;
        MessageBox.Show("Saved successfully.");

        #endregion
    }

In region "Saving" I want to select which filter will be used and save it using it.

  • A good place to start might be saving it *as* a PNG in the first place rather than a Jpeg? `Image111.Save(SaveDirectory, ImageFormat.Jpeg);` ? Then you call `Save` a second time and only pass the directory? – sab669 Jun 26 '17 at 16:59
  • @sab669 Oh, I was making some tests and forgot to change it. Of course saving that image looks like : `Image111.Save(SaveDirectory, System.Drawing.Imaging.ImageFormat.Png);` – Łukasz Kochanowski Jun 26 '17 at 17:03
  • There's a PNG encoder [here](https://github.com/oltur/seeyourtravel.com/blob/master/tools/WicResize/ThumbnailPNG.ashx) which has [these filter options](https://github.com/oltur/seeyourtravel.com/blob/master/tools/WicResize/InteropServices/ComTypes/WinCodec.cs#L2409) – stuartd Jun 26 '17 at 17:13
  • @stuartd Thanks totally! But now I'm even more lost, cause I don't have an idea how to use it. I mean I know how to reference to .dll's but there I can't even see how to use that encoder. – Łukasz Kochanowski Jun 26 '17 at 17:30
  • check out https://github.com/JimBobSquarePants/ImageSharp we have fully managed encoders for pngs that could help you. – tocsoft Jun 26 '17 at 18:40
  • _"after checking multiple of PNG libraries, none of them allow me to choose one. Can anyone help me with that"_ -- put another way, you are really just asking for help finding a library that _does_ support selection of the different filters. Unfortunately, that's an off-topic question. You can, of course, use built-in features of .NET to save .png files. There are at least two different media-handling APIs (associated with Winforms and WPF, respectively) that can save PNG, but they don't provide configurable filter selection. – Peter Duniho Jun 26 '17 at 21:15

2 Answers2

1

If the PNG libraries don't do what you want, just roll your own filters. It's not that difficult.

The filtering should take place inside the #region Quantization. As far as I unterstand it, the Valuator_v3() method converts the RGB channels separately, then you store the transformed pixel with Image111.SetPixel(). The PNG filter needs to be inserted between the two calls.

PNG filters work on the current pixel color and one, two, or three previously read neighboring pixels. They never look ahead. So you just use Image111.GetPixel() to retrieve previous pixels and use them to transform the current pixel. In the case of the filter type "None", there's nothing to do, and you just store the quantized pixel.

In the case of "Sub", you test if you're in the leftmost column (i.e., w == 0). If so, you leave the pixel as is. Otherwise, you call Image111.GetPixel (w-1, h) and subtract the resulting RGB values from the current pixel:

Color pixelLeft = Image111.GetPixel (w-1, h);
newColor.R -= pixelLeft.R;
newColor.G -= pixelLeft.G;
newColor.B -= pixelLeft.B;

That's it. The "Up" transform is likewise trivial. You just check for h == 0 this time, and call Image111.GetPixel (w, h-1) if the current pixel is not in the top row. The "Average" filter requires both the left and upper pixels, and computes the arithmetic mean of the RGB channel values. Note that pixelLeft = 0 in case of w == 0, and pixelTop = 0 in case of h == 0:

Color pixelLeft = Image111.GetPixel (w-1, h);
Color pixelTop  = Image111.GetPixel (w, h-1);
newColor.R -= (Byte) (((UInt64) pixelLeft.R + (UInt64) pixelTop.R) >> 1);
newColor.G -= (Byte) (((UInt64) pixelLeft.G + (UInt64) pixelTop.G) >> 1);
newColor.B -= (Byte) (((UInt64) pixelLeft.B + (UInt64) pixelTop.B) >> 1);

The Paeth filter is more complex. It uses three neighboring pixels, pixelLeft, pixelTop, and pixelTopLeft. Once again you need to check the special border cases appropriately. The following predictor is computed seperately for each channel, e.g. red:

Int64 valueLeft     = pixelLeft.R;
Int64 valueTop      = pixelTop.R;
Int64 valueTopLeft  = pixelTopLeft.R;
Int64 valueCombined = valueLeft + valueTop - valueTopLeft;

Int64 deltaLeft     = Math.Abs (valueCombined - valueLeft);
Int64 deltaTop      = Math.Abs (valueCombined - valueTop);
Int64 deltaTopLeft  = Math.Abs (valueCombined - valueTopLeft);

newColor.R -= (deltaLeft <= deltaTop) && (deltaLeft <= deltaTopLeft)
              ? pixelLeft.R
              : deltaTop <= deltaTopLeft ? pixelTop.R : pixelTopLeft.R);

Although the Paeth filter looks quite promising, my own tests have shown that the "Up" filter yields the best results in most cases. Don't know why. So by default I'm using the "Sub" filter for the first row, and the "Up" filter for all subsequent ones.

So now you've got the filtered image in memory. What you need now is a standard DEFLATE encoder, like ZLIB uses. The encoder input is the filtered RGB data. Note that PNG requires you to emit the filter type (0..4) as a literal code at the beginning of each row. The compressed DEFLATE stream is packaged into an IDAT chunk of a PNG container, which is not a difficult task.

SBS
  • 806
  • 5
  • 13
0

but after checking multiple of PNG libraries, none of them allow me to choose one

Have you tried PngCs? (there are several forks out there). See PngWriter. FilterWriteStrategy(...) method

leonbloy
  • 73,180
  • 20
  • 142
  • 190