Как избежать бесполезной белой рамки в измененном PNG с прозрачным фоном? - PullRequest
3 голосов
/ 04 июня 2011

У меня есть папка, содержащая около 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 ]

4 голосов
/ 04 июня 2011

Я не могу помочь с конкретным кодом, но, возможно, могу объяснить, что происходит.

newimg.MakeTransparent(Color.White);

Это займет один цвет и сделает его прозрачным.Загвоздка в том, что между краем вашего бильярдного шара (оранжевым) и чисто белым фоном есть спектр цветов.Это сглаживание края (которое будет представлять собой смесь цветов от чистого оранжевого шарика до чисто белого фона).

Если вы сделаете только прозрачный белый цвет прозрачным, у вас все еще остается это«Ореол» белого цвета вокруг объекта.

Возможно, есть лучший способ справиться с этим, используя значения белого в качестве альфа-маски, но я не уверен, что библиотека изображений .net справится с этим (мне придется обратиться к кому-то с большим опытом работы с .netвместе).

Тем временем, что может помочь, если вы установите прозрачность перед изменением размера.Это не будет правильным решением, но может немного уменьшить эффект ореола.

ОБНОВЛЕНИЕ:

Итак, я думал об этом еще немного, и я не совсем уверен, что есть программное решение для автоматического создания прозрачности альфа-канала, так как у меня есть догадкабольшая субъективность вовлечена.

Вверху моей головы, вот что я придумал:

  • в предположении, что верхний левый пиксель - ваш 100% прозрачный цвет (скажем, пиксель X).
  • при условии, что ваш фон, который вы хотите сделать прозрачным, - это один сплошной цвет (по сравнению с рисунком)
  • при условии сглаживания примерно в 3 пикселя

, который вы можете затем.

  • проверка соседних пикселей с X. Для каждого соседнего пикселя с X, который соответствует цвету X, мы устанавливаем это на 100% прозрачным.
  • , если пиксель рядом с x равенНЕ то же самое, мы могли бы проверить его относительный оттенок.
  • ответвляются от этого пикселя и проверить его окружающие пиксели.
  • делайте эту маркировку для каждого пикселя (a, b, c и т. Д.), Пока относительный оттенок не изменит определенный процент и / или цвет пикселя не станет таким же, как у его соседа (с определенным запасом изменчивости).Если это произойдет, мы будем предполагать, что мы хорошо внутри объекта.
  • теперь шаг назад через пиксели, которые вы отметили, настраивая прозрачность ... скажем, c = 0% b = 33% a = 66%

Но все же это большое упрощениео том, что действительно должно было случиться.Он делает много предположений, не принимая во внимание узорчатый фон, и полностью игнорирует внутренние области, которые также должны быть прозрачными (например, дырку от бублика).

Обычно в приложении для редактирования графики это делаетсяпосредством выделения блоков цвета фона, растушевки краев указанного выделения, затем превращения их в альфа-макс.

Это действительно интересный вопрос / проблема.У меня, увы, нет ответа для вас, но я буду с любопытством наблюдать за этой веткой!

1 голос
/ 05 июня 2011

Ваша отредактированная функция 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));
        }
    }
}

Идея состоит в том, что, чтобы избежать влияния альфа-значения шара, вы должны уменьшать альфа только для цветов, которые очень близки к нулю. Другими словами, эта функция быстро увеличивается от 0 до 255 по мере удаления цвета от белого.

Это не приведет к идеальному результату, как сказал @DA, потому что теряется некоторая информация (прозрачные пиксели и непрозрачные пиксели, смешанные вместе возле краев объекта), которые невозможно восстановить. Для создания абсолютно не псевдонимных альфа-краев исходное изображение должно быть сгенерировано с прозрачностью.

...