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

发布于 2024-11-14 09:04:07 字数 3087 浏览 2 评论 0原文

我有一个文件夹,其中包含大约 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();
        }
    }

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

调整大小的图像和白色边框

-- 编辑 --

我设法编写了这个函数而不是 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));
            }
        }
    }

问题是我的台球现在看起来像这样:

第二版台球

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

情话墙 2024-11-21 09:04:07

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

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 来完成的。

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

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!

得不到的就毁灭 2024-11-21 09:04:07

您编辑的 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));
        }
    }
}

其想法是,为了避免影响台球的 alpha 值,您只需减少非常接近于零的颜色的 alpha 值。换句话说,它是一个随着颜色远离白色而从 0 快速上升到 255 的函数。

正如 @DA 所说,这不会产生理想的结果,因为丢失了一些无法恢复的信息(透明像素和非透明像素在对象边缘附近混合在一起)。为了制作完美无锯齿的 Alpha 边缘,源图像本身必须以透明度生成。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文