Konwersja GIF do skali szarości w .NET (C#)


(somekind) #1

Sprawa jest taka: potrzebuję hurtem skonwertować kilka tysięcy plików *.gif do skali szarości. (Dla ciekawskich: są to pliki graficzne portalu internetowego, klient chce, aby w razie żałoby, cała strona była w odcieniach szarości.)

Operacje plikowe, algorytm konwersji, wszystko jest i działa.

Co prawda pewno mogłoby szybciej, gdyż przeliczam piksel po pikselu, a pewno w .NET są jakieś gotowe funkcje do tego, tylko przez dwa dni nie mogę znaleźć :confused: (Może ktoś wie i zarzuci pomysłem?)

Ale nie to jest głównym problemem. Ten jest dopiero z kolorem przezroczystości, gdyż obecna wersja programu traktuje go jak zwykły i także konwertuje. Efekt jest tragiczny - obrazki bez przezroczystości wyglądają zupełnie inaczej.

Czy ktoś wie, jak temu zaradzić? Jak wyciągnąć ten kolor z pliku *.gif, przy użyciu .NET?


(W Kowaluk) #2

google znalazło coś takiego: http://www.bobpowell.net/giftransparency.htm


(Ryan) #3

Nie ma funkcji która robi to automagicznie, jeśli o to pytasz. Przykładowy kod:

public Bitmap ToGrayscale(Bitmap source)

{

  int x, y, luma;

  Bitmap b = new Bitmap(source.Width, source.Height);

  for (y = 0; y < b.Height; ++y)

  {

    for (x = 0; x < b.Width; ++x)

    {

      Color c = source.GetPixel(x, y);

      luma = (int)(c.R * 0.2126 + c.G * 0.7152 + c.B * 0.0722);

      b.SetPixel(x, y, Color.FromArgb(luma, luma, luma));

    }

  }

  return b;

}

O co chodzi z lumą:

http://en.wikipedia.org/wiki/Luma_(video

Jeśli nie odpowiada Ci "szarość" przeczytaj fragment o różnicach w zaleceniach 601 i 709.


(somekind) #4

Dzięki, ale co tam jest takiego, co mogłoby mi pomóc?

Jakbym chciał auto, to bym poszedł do salonu, jakbym chciał magię to do wróżki :slight_smile: Ja tylko poważnie spytałem.

Pomyślałem sobie tylko, że może da się zrobić obiekt (typu Image, Bitmap, czy jakiegoś innego, mi nieznanego), któremu można ustawić szarą paletę kolorów i wczytać do niego gifa z dysku - to by było najprostsze rozwiązanie.

Dzięki, ale ten kod od mojego różnił się tylko współczynnikami do mnożenia składowych kolorów. Z mądrej Wikipedii dowiedziałem się, że te, których ja użyłem pochodzą ze standardu CCIR601. Poza tym nic mi to nie dało. Oczywiście rozwiązanie problemu okazało się banalne. Jest nim kanał Alfa. Gdy jego wartość wynosi 0 piksel jest całkowicie przezroczysty, gdy 255 całkowicie nieprzezroczysty. Zatem kod wygląda teraz tak:

private Bitmap Poszarz(Image img, double[] mn)

        {            

            Bitmap bmp = new Bitmap(img);

            Bitmap szara = new Bitmap(bmp.Width, bmp.Height);         


            for (int i = 0; i < szara.Width; i++)

            {

                for (int j = 0; j < szara.Height; j++)

                {

                    Color kolor = bmp.GetPixel(i, j);

                    if (kolor.A == 0) //jeśli jest przezroczysty

                    {

                        szara.SetPixel(i, j, Color.FromArgb(0, 255, 255, 255));

                    }

                    else

                    {

                        int k = (int)(mn[0] * (double)kolor.R + mn[1] * (double)kolor.G + mn[2] * (double)kolor.B);

                        szara.SetPixel(i, j, Color.FromArgb(255, k, k, k));

                    }

                }

            }

            return szara;

        }

A wracając do szarości - okazuje się, że żadna nie jest poprawna. JPEG przetworzony tym algorytmem wygląda pięknie, natomiast GIF już nie. Wie ktoś dlaczego?

I czemu w dowolnym programie graficznym taka operacja zajmuje ułamek sekundy, a u mnie trwa to tyle czasu? Da się jakoś przyspieszyć ten algorytm?


(Ryan) #5

Automagicznie to nie jest złe słowo. To piękne określenie na coś, co się dzieje w API i działa, choć nikt nie zastanawia się dlaczego. To także jak najbardziej poważne i techniczne słowo. :wink:

Zmiana palety nie wyjdzie, bo grafiki w C# reprezentowane sa w pełnej gamie kolorów a nie z wykorzystaniem palety, więc nawet wczytany GIF palety nie ma. Oczywiście można trochę sprawę przyspieszyć dla dużych GIFów i konwersję przenieść do funkcji, która sprawdza lookup table czy dany kolor nie był już konwertowany, pobiera nową wartość albo tworzy nowy wpis w tablicy. Jak mówię - dla dużych gifów można trochę przyspieszyć proces. Choć ilość kodu nie jest tego chyba warta.

Alternatywą jest wejście na wotsit.org, wygrzebanie specyfikacji GIFa, wczytanie pliku jako tablicy bajtów, konwersja samej palety, zapis i odczytanie nowego GIFa. Całkiem sprytny i szybki sposób, ale obarczony opóźnieniami IO (czyli wolniejszy niż jakiekolwiek operacje w pamięci).

A ogólnie to przepraszam - umknęła mi część o przezroczystości. Oczywiście rozwiązaniem jest alfa. Zamieniłbym jeszcze to:

if (kolor.A == 0) //jeśli jest przezroczysty 

                    { 

                        szara.SetPixel(i, j, Color.FromArgb(0, 255, 255, 255)); 

                    } 

                    else 

                    { 

                        int k = (int)(mn[0] * (double)kolor.R + mn[1] * (double)kolor.G + mn[2] * (double)kolor.B); 

                        szara.SetPixel(i, j, Color.FromArgb(255, k, k, k)); 

                    }

na to:

szara.SetPixel(i, j, Color.FromArgb(kolor.A, 255, 255, 255));

:slight_smile:


(somekind) #6

Nie mówię, że złe, ale dla mnie zabawnie brzmi :slight_smile:

Mam już specyfikację GIFa od dawna. Ale stwierdziłem, że za darmo, aż tak bardzo nie będę się wysilał, żeby ją przestudiować. Choć faktycznie masz rację, pewno "normalne" programy tak robią. Że też na to nie wpadłem :oops:

Nie ozumiem sensu tej zmiany... Ja sprawdzam, czy kolor jest przezroczysty, jeśli tak, to zastępuję go "przezroczystą bielą", jeśli nie, to wyliczam mu szarość i wstawiam. A Ty chcesz wszystko ustawić na biało, z różną tylko alfą?


(Ryan) #7

:oops: Tak to jest jak się robi szybkie copy/paste. Powinno być:

int k = (int)(mn[0] * (double)kolor.R + mn[1] * (double)kolor.G + mn[2] * (double)kolor.B);

szara.SetPixel(i, j, Color.FromArgb(kolor.A, k, k, k));

Nie ma znaczenia jaki kolor podasz przy 100% przezroczystości. Nie musi to być 255/255/255, może być k/k/k. Takie było przesłanie spartolonego kodu, który poprzednio wkleiłem.


(somekind) #8

Teraz wszystko jasne, gdyby nie to, że nie zgodzę się z tym...

Też tak myślałem. Wiem, że to głupie, ale jak np. wstawiałem Color.FromArgb(0, 0, 0, 0), to przezroczystość była czarna i widoczna w podglądzie.


(Ryan) #9

A na tle jakiej barwy wyświetlałeś? I jak wyświetlałeś? Nie ma wymogu, który mówiłby, że przezroczysty jest tylko 0 255 255 255. Jeśli gdzieś coś takiego jest wymuszone, to jest to bug - albo we frameworku, albo w kodzie alfę wykorzystującym.


(somekind) #10

No wiem, że to dziwne, ale tak to wyglądało. Czy w IrfanView, czy w Windowsowym podglądzie. I raczej nie byłem wtedy nietrzeźwy...


(Ryan) #11

Czyli po zapisie. Zweryfikuję.


(somekind) #12

Dobra, zrobiłem tak, jak radziłeś, rozkminiłem specyfikację (3 najmniej znaczące bity 11 bajtu oznaczają rozmiar tabeli kolorów, bajty od 13 do któregoś tam, wynikającego z rozmiaru to właśnie ta tabela). Program przyspieszył stukrotnie dając jakość lepszą niż Photoshop :slight_smile:

Został mi już tylko jeden problem - niektóre pliki się nie przetworzyły - po zbadaniu ich WinHexem okazało się, że są niezgodne ze specyfikacją, zawierają dodatkowe komentarze, itp.