3

I have a folder containing about 2500 PNG images, with no transparency. Every image is about 500 x 500 (some are 491 x 433, others 511 x 499 etc).

I want to programatically downsize every image to 10% of its original size, and to set the white background of every image as the transparent color.

To test the functionality of my application without resizing 2500 images every time, I used 15 images of billiard balls as a "test" folder.

Now my problem is with the following code, I get a resized and cropped PNG, whith a almost transparent background. The problem is that a white border on the left and top appears in every image viewer (Irfan View, Paint.Net and GIMP)

How can I avoid this border?

Here is the code I used for this:

void ResizeI(string[] Paths, string OutPut, Methods m, PointF Values, bool TwoCheck, bool Overwrite, float[] CropVals)
    {
        for (int i = 0; i < Paths.Length; i++)//Paths is the array of all images
        {
            string Path = Paths[i];//current image
            Bitmap Oimg = (Bitmap)Bitmap.FromFile(Path);//original image
            Bitmap img = new Bitmap((int)(Oimg.Width - CropVals[0] - CropVals[1]), (int)(Oimg.Height - CropVals[2] - CropVals[3]));//cropped image
            Graphics ggg = Graphics.FromImage(img);
            ggg.DrawImage(Oimg, new RectangleF(((float)-CropVals[0]), ((float)-CropVals[2]), Oimg.Width - CropVals[1], Oimg.Height - CropVals[3]));
            ggg.Flush(System.Drawing.Drawing2D.FlushIntention.Flush);
            ggg.Dispose();
            PointF scalefactor = GetScaleFactor(img, Values, TwoCheck);//the scale factor equals 0.1 for 10%
            Bitmap newimg = new Bitmap((int)(Math.Ceiling(((float)img.Width) * scalefactor.X)), (int)(Math.Ceiling(((float)img.Height) * scalefactor.Y)));
            System.Drawing.Imaging.ImageFormat curform = img.RawFormat;
            string OutPath = System.IO.Path.Combine(OutPut, System.IO.Path.GetFileName(Path));
            OutPath = CheckPath(OutPath, Overwrite);//Delete if exsits
            Graphics g = Graphics.FromImage(newimg);
            g.InterpolationMode = GetModeFromMethod(m);//Bicubic interpolation
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            g.ScaleTransform(scalefactor.X, scalefactor.Y);
            g.DrawImage(img, new Rectangle(0, 0, (int)Math.Ceiling(((float)newimg.Width) / scalefactor.X) + 1, (int)Math.Ceiling(((float)newimg.Height) / scalefactor.Y) + 1));
            //g.Flush(System.Drawing.Drawing2D.FlushIntention.Flush);
            newimg.MakeTransparent(Color.White);
            newimg.Save(OutPath, curform);
            g.Dispose();
            img.Dispose();
        }
    }

And here is a example of the white border I mentioned. Download the image or drag it around and put a black background under it to see the border:

The resized Image and the white border

-- EDIT --

I managed to write this function instead of newimg.MakeTransparent(...):

 void SetTransparent(ref Bitmap b)
    {
        for (int i = 0; i < b.Width; i++)
        {
            for (int ii = 0; ii < b.Height; ii++)
            {
                Color cc = b.GetPixel(i, ii);
                int tog = cc.R + cc.G + cc.B;
                float durch = 255f - (((float)tog) / 3f);
                b.SetPixel(i, ii, Color.FromArgb((int)durch, cc.R, cc.G, cc.B));
            }
        }
    }

the problem is that my billiard ball now look like this:

Second version of the billiard ball

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
alex
  • 1,228
  • 1
  • 16
  • 38

2 Answers2

4

I can't help with the specific code, but maybe can explain what's happening.

newimg.MakeTransparent(Color.White);

This will take one color, and make it transparent. The catch is that, there's a spectrum of colors between the edge of your billiard ball (orange) and the pure white background. This is the antialiasing of the edge (which will be a blend of colors from the pure orange of the ball to the pure white of the background).

By turning only pure white transparent, you are still left with this 'halo' of white-ish colors around the object.

There's perhaps a better way to handle this using white values as an alpha mask but I'm not sure if .net's image library can handle that (I'll have to defer to someone with more .net experience comes along).

In the interim, though, what may help is if you set the transparency before you do the resize. It won't be a true fix, but might reduce the halo effect some.

UPDATE:

So, I've been thinking about this some more, and I'm not entirely sure there's a programmatic solution for creating alpha channel transparency automatically, as I have a hunch there's a lot of subjectivity involved.

Off the top of my head, this is what I came up with:

  • assuming the top left pixel is your 100% transparent color (we'll say pixel X).
  • assuming your background that you want transparent is one solid color (vs. a pattern)
  • assume a roughly 3px anti-aliasing

you could then...

  • check for neighboring pixels to X. For each neighboring pixel to X that matches the color of X, we set that 100% transparent.
  • if a pixel next to x is NOT the same, we could check it's relative hue.
  • branch from that pixel and check it's surrounding pixels.
  • do this marking each pixel (a, b, c, etc) until the relative hue changes a certain percentage and/or the pixel color is the same as it's neighbor (with a certain margin of variability). If it does, we'll assume we're well into the interior of the object.
  • now step backwards through the pixels you marked, adjusting the transparency...say c=0% b=33% a=66%

But still, that's a large oversimplification of what would really have to happen. It's making a lot of assumptions, not taking into account a patterned background, and completely ignores interior areas that need to also be transparent (such as a donut hole).

Normally in a graphics editing app, this is done via selecting blocks of the background color, feathering the edges of said selection, then turning that into an alpha max.

It's a really interesting question/problem. I, alas, don't have the answer for you but will be watching this thread with curiosity!

DA.
  • 39,848
  • 49
  • 150
  • 213
  • Yeah, I thought of that already but I have no idea how to access the PNGs alpha channel through .NET? BTW for me the border looks like a 3D border of some controls... – alex Jun 04 '11 at 11:32
  • I'm not sure you can do that problematically. The challenges is you want white outside the image transparent, the anti aliasing translucent, and the interior opaque. The script would have to some how distinguish from a particular gray that's part of a black anti-aliased edge (which you'd want partially transparent) vs. that same gray that's a solid color of the image itself (which you'd want blank). Let me think a bit and I'll update my answer... – DA. Jun 04 '11 at 20:22
  • Okay thanks! The 15 billiard balls I did with Paint.Net manually but for 1500 images I'll need a program... – alex Jun 04 '11 at 20:25
  • I updated my answer. Alas, I'm not sure it'll be much help. If this is more of a one-off task, maybe spend some time digging through The GIMP or Photoshop plugins to see if there may be an automated macro to handle it. – DA. Jun 04 '11 at 20:34
  • I thought of that too but I'm really good with C# and I just thought it might be a little challenge for me to test my skills. Obviously I didn't even manage _creating_ an alpha channel without some googling. Now for the semi-transparent pixels around that ball: I recently worked on a A* implementation and I think there is a similar way to handle this... – alex Jun 04 '11 at 22:56
  • What if I iterate throug every pixel and mark the pixels, which are background-colored. Than I'll inspect every neighbouring pixel to the marked ones and apply an transparency of `t` where `t = 255f - (x.R + x.G + x.B) / 3f);` (x is the neigbouring pixel)? – alex Jun 04 '11 at 23:00
  • @alex: a rigorous mathematical treatment of recovering transparency from blurred edges can be found in the paper [Single Image Motion Deblurring Using Transparency](http://goo.gl/RdK6Z) – rwong Jun 04 '11 at 23:10
1

Your edited SetTransparent function is on the right direction, and you're almost there.

Just a slight modification you can try this:

void SetTransparent(ref Bitmap b)    
{   
    const float selectivity = 20f;  // set it to some number much larger than 1 but less than 255
    for (int i = 0; i < b.Width; i++)
    {        
        for (int ii = 0; ii < b.Height; ii++)
        {
            Color cc = b.GetPixel(i, ii);
            float avgg = (cc.R + cc.G + cc.B) / 3f;
            float durch = Math.Min(255f, (255f - avgg) * selectivity);
            b.SetPixel(i, ii, Color.FromArgb((int)durch, cc.R, cc.G, cc.B));
        }
    }
}

The idea is that to avoid affecting the alpha value of the billard ball, you will only want to reduce the alpha for colors that are very close to zero. In other words, it is a function that rises rapidly from 0 to 255 as the color moves away from white.

This will not produce the ideal result, as @DA said, because there is some information lost (transparent pixels and non-transparent pixels being blended together near the object's edges) that is unrecoverable. To make perfectly alias-free alpha edges, the source image itself must be generated with transparency.

rwong
  • 6,062
  • 1
  • 23
  • 51
  • Maybe youre right but the images are already generated and look very similar to these billiard balls. I just thought, if I manage to handle transparency automatically on these 15 diffrent billiard balls, than the code will work for these 1500 images too! – alex Jun 04 '11 at 22:53