如何在透明背景的大小调整PNG中避免无用的白色边框?

时间:2011-06-04 11:11:09

标签: c# image-processing resize transparency system.drawing.imaging

我有一个包含大约2500张PNG图像的文件夹,没有透明度。每张图片大约500 x 500(有些是491 x 433,其他是511 x 499等)。

我希望以编程方式将每个图像缩小到原始大小的10%,并将每个图像的白色背景设置为透明色。

为了测试我的应用程序的功能而不是每次调整2500张图像,我使用了15张台球图像作为“测试”文件夹。

现在我的问题是使用以下代码,我得到一个调整大小和裁剪的PNG,其中几乎透明背景。问题是左侧和顶部的白色边框出现在每个图像查看器中(Irfan View,Paint.Net和GIMP)

我该如何避免这种边界?

以下是我用于此的代码:

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

这是我提到的白色边框的一个例子。下载图像或将其拖动并在其下面放置黑色背景以查看边框:

The resized Image and the white border

- 编辑 -

我设法编写此函数而不是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));
            }
        }
    }
问题是我的台球现在看起来像这样:

Second version of the billiard ball

2 个答案:

答案 0 :(得分:4)

我无法解决具体的代码,但也许可以解释发生了什么。

newimg.MakeTransparent(Color.White);

这将采用一种颜色,并使其透明。问题是,你的台球边缘(橙色)和纯白色背景之间有一系列颜色。这是边缘的抗锯齿(它将从球的纯橙色到背景的纯白色混合)。

通过仅转动纯白色透明,您仍然会在对象周围留下白色的“光环”。

使用白色值作为alpha掩码可能有更好的方法来解决这个问题,但我不确定.net的图像库是否可以处理(我必须推迟到具有更多.net经验的人)。

在过渡期间,如果在进行调整大小之前设置透明度,可能会有所帮助。它不会是一个真正的修复,但可能会减少一些光环效应。

更新:

所以,我一直在考虑这个问题,而且我并不完全确定有一个自动创建alpha通道透明度的程序化解决方案,因为我预感到它涉及很多主观性。

脱离我的头脑,这就是我提出的:

  • 假设左上角的像素是100%透明的颜色(我们将说像素X)。
  • 假设您想要透明的背景是一种纯色(相对于图案)
  • 假设大约3px抗锯齿
那么你可以......

  • 检查X的相邻像素。对于与X的颜色匹配的X的每个相邻像素,我们将其设置为100%透明。
  • 如果x旁边的像素不相同,我们可以检查它的相对色调。
  • 从该像素分支并检查它周围的像素。
  • 这样做标记每个像素(a,b,c等),直到相对色调改变一定百分比和/或像素颜色与其邻居相同(具有一定的可变性范围)。如果是这样,我们将假设我们已经很好地进入了对象的内部。
  • 现在向前退一步你标记的像素,调整透明度...说c = 0%b = 33%a = 66%

但是,对于真正必须发生的事情来说,这是一个很大的过度简化。它做了很多假设,没有考虑图案背景,完全忽略了需要透明的内部区域(如甜甜圈洞)。

通常在图形编辑应用程序中,这是通过选择背景颜色的块,羽化所述选择的边缘,然后将其转换为alpha max来完成的。

这是一个非常有趣的问题/问题。我,唉,没有你的答案,但会好奇地看着这个帖子!

答案 1 :(得分:1)

您编辑的SetTransparent功能方向正确,您就在那里。

只需稍加修改即可尝试:

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

这个想法是为了避免影响billard球的alpha值,你只想减少非常接近零的颜色的alpha值。换句话说,当颜色远离白色时,它是一个从0迅速上升到255的函数。

这不会产生理想的结果,正如@DA所说,因为有些信息丢失(透明像素和非透明像素在对象边缘附近混合在一起)是不可恢复的。要制作完全无别名的alpha边缘,必须以透明的方式生成源图像本身。