Pokazywanie postów oznaczonych etykietą C#. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą C#. Pokaż wszystkie posty

poniedziałek, 24 grudnia 2012

Bitmapowy zawrót głowy

Dziś bez zbędnych wprowadzeń i opisów - po prostu fragmenty kodu odpowiedzialne za konwersję pomiędzy trzema klasami odpowiedzialnymi za grafikę w .NET: Bitmap, BitmapImage oraz BitmapSource. Natomiast w ramach bonusu świątecznego konwersja z i do Byte[], która z pewnością w przypadku trzymania obrazów w plikach/bazach danych przyda się "od kopa". Przedstawiony kod jest w postaci metod rozszerzających (ang. extension methods), więc umożliwi przejrzyste i proste wykorzystanie.

Bitmap

using System;
using System.Drawing;
using System.Windows.Media.Imaging;
using System.IO;
using System.Drawing.Imaging;
using System.Windows.Interop;

namespace Runaurufu.Extension
{
    public static class ExtensionBitmap
    {
        public static BitmapImage ToBitmapImage(this Bitmap A)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                A.Save(ms, ImageFormat.Bmp);
                ms.Position = 0;
                BitmapImage bi = new BitmapImage();
                bi.BeginInit();
                bi.StreamSource = ms;
                bi.CacheOption = BitmapCacheOption.OnLoad;
                bi.EndInit();
                return bi;
            }
        }

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        private static extern bool DeleteObject(IntPtr hObject);

        public static BitmapSource ToBitmapSource(this Bitmap A)
        {
            IntPtr hBitmap = A.GetHbitmap();
            BitmapSource result;
            try
            {
                result = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, System.Windows.Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
            }
            finally
            {
                DeleteObject(hBitmap);
            }

            return result;
        }

        public static Byte[] ToByteArray(this Bitmap A)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                A.Save(ms, ImageFormat.Bmp);
                ms.Position = 0;
                return ms.ToArray();
            }
        }
    }
}

BitmapSource

using System;
using System.Drawing;
using System.IO;
using System.Windows.Media.Imaging;

namespace Runaurufu.Extension
{
    public static class ExtensionBitmapSource
    {
        public static BitmapImage ToBitmapImage(this BitmapSource A)
        {
            return A.ToBitmapImage(new BmpBitmapEncoder());
        }

        public static BitmapImage ToBitmapImage(this BitmapSource A, BitmapEncoder encoder)
        {
            BitmapImage bi = new BitmapImage();

            using (MemoryStream ms = new MemoryStream())
            {
                encoder.Frames.Add(BitmapFrame.Create(A));
                encoder.Save(ms);
                ms.Position = 0;

                bi.BeginInit();
                bi.StreamSource = ms;
                bi.CacheOption = BitmapCacheOption.OnLoad;
                bi.EndInit();
            }

            return bi;
        }

        public static Bitmap ToBitmap(this BitmapSource A)
        {
            return A.ToBitmap(new BmpBitmapEncoder());
        }

        public static Bitmap ToBitmap(this BitmapSource A, BitmapEncoder encoder)
        {
            System.Drawing.Bitmap bitmap;
            using (MemoryStream outStream = new MemoryStream())
            {
                encoder.Frames.Add(BitmapFrame.Create(A));
                encoder.Save(outStream);
                bitmap = new System.Drawing.Bitmap(outStream);
            }
            return bitmap;
        }

        public static Byte[] ToByteArray(this BitmapSource A)
        {
            return A.ToByteArray(new BmpBitmapEncoder());
        }

        public static Byte[] ToByteArray(this BitmapSource A, BitmapEncoder encoder)
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                encoder.Frames.Add(BitmapFrame.Create(A));
                encoder.Save(memoryStream);
                return memoryStream.GetBuffer();
            }
        }
    }
}

BitmapImage

using System;
using System.Drawing;
using System.IO;
using System.Windows.Media.Imaging;

namespace Runaurufu.Extension
{
    public static class ExtensionBitmapImage
    {
        public static Bitmap ToBitmap(this BitmapImage A)
        {
            return A.ToBitmap(new BmpBitmapEncoder());
        }

        public static Bitmap ToBitmap(this BitmapImage A, BitmapEncoder encoder)
        {
            Bitmap bitmap;
            using (MemoryStream outStream = new MemoryStream())
            {
                encoder.Frames.Add(BitmapFrame.Create(A));
                encoder.Save(outStream);
                bitmap = new System.Drawing.Bitmap(outStream);
            }
            return bitmap;
        }

        public static Byte[] ToByteArray(this BitmapImage A)
        {
            return A.ToByteArray(new BmpBitmapEncoder());
        }

        public static Byte[] ToByteArray(this BitmapImage A, BitmapEncoder encoder)
        {
            Byte[] byteArr;
            using (MemoryStream ms = new MemoryStream())
            {
                encoder.Frames.Add(BitmapFrame.Create(A));
                encoder.Save(ms);
                ms.Position = 0;
                byteArr = ms.GetBuffer();
            }
            return byteArr;
        }
    }
}

Byte[]

using System;
using System.Drawing;
using System.IO;
using System.Windows.Media.Imaging;

namespace Runaurufu.Extension
{
    public static class ExtensionByte
    {
        public static Bitmap ToBitmap(this Byte[] A)
        {
            using (MemoryStream ms = new MemoryStream(A))
            {
                return new Bitmap(ms);
            }
        }

        public static BitmapImage ToBitmapImage(this Byte[] A)
        {
            using (MemoryStream ms = new MemoryStream(A))
            {
                ms.Position = 0;
                BitmapImage bi = new BitmapImage();
                bi.BeginInit();
                bi.StreamSource = ms;
                bi.CacheOption = BitmapCacheOption.OnLoad;
                bi.EndInit();
                return bi;
            }
        }
    }
}

Warto zwrócić uwagę, że w przypadku BitmapSource czyli obrazach rodem z WPF/Silverlighta konieczne jest podanie odpowiedniego enkodera - wybór np. JpegEncoder na obrazie z kanałem alfa (przeźroczystość) sprawi, że ów kanał alfa trafi szlag... :) Dodatkowo z oczywistych przyczyn nie ma metody do konwersji BitmapImage -> BitmapSource - a jeśli ktoś szuka bo z jakiś powodów potrzebuje to może wykorzystać ten fragment:
        public static BitmapSource ToBitmapSource (this BitmapImage A)
        {
            return (BitmapSource)A;
        }

A wszystkim, którzy choć od czasu do czasu zaglądają w me skromne progi życzę Wesołych Świąt :)

Keywords: C#, . NET, Bitmap, BitmapImage, BitmapSource, Bitmap conversions, Silverlight, WPF, WinForm, Image, Picture, Transparent BitmapImage.

niedziela, 4 listopada 2012

Ree7 Tile Toolkit i obrazy na kafelkach

Jeśli kiedykolwiek poczujecie chęć stworzenia własnych "kafelek" do swojej aplikacji to zapewne odnajdziecie w czeluściach Internetu bibliotekę "Ree7 Tile Toolkit for Windows Phone 7" umożliwiającą tworzenie całkowicie dowolnych kafelek. I żeby była jasność - poprzez "całkowitą dowolność" mam na myśli tworzenie zawartości Tile w ten sam sposób co zwykłego User Control (oczywiście Tile musi być statyczny, więc animacji w nim nie odtworzymy) - czyli tak za pomocą XAML jak i bezpośrednio przy użyciu kodu w VB czy C#.

Niestety przy tworzeniu własnych kafelek napotkać możemy jeden podstawowy problem wynikający ze sposobu obsługi obrazów w Silverlight. Obrazy generalnie nie są ładowane do pamięci aż do momentu, w którym są wyświetlane, tak więc tworzone kafelki po prostu renderowane są bez elementów pochodzących z plików graficznych. Dodatkowo w przykładowej aplikacji biblioteki ten problem jest nie dostrzegalny, gdyż tam kafelki są uprzednio wyświetlane w drzewie aplikacji:
SimpleTileContainer.Children.Add(myCustomTile1);
a tworzone są dopiero po kliknięciu w odpowiedni przycisk.
ShellTile.Create(new Uri("/MainPage.xaml?src=SampleTile", UriKind.Relative), myCustomTile1.GetShellTileData());

środa, 10 października 2012

Dźwięki w aplikacji na Windows Phone?

Jeśli chcemy mieć dźwięk przy zwykłej aplikacji, która nie jest pisana w XNA (czyli wg marketu nie jest grą) możemy go zdobyć na kilka sposobów... ale i tak koniec końców najlepiej użyć frameworku XNA :D

No, może nie wprost :] Ale o tym za chwilę.
Generalnie zapewne pierwszym sposobem, z którego większość by skorzystała jest natywna kontrolka "MediaElement", która fakt faktem udostępnia możliwość odtwarzania filmów i dźwięków niejako z marszu. Ot wrzucamy do XAML, dodajemy źródło naszej "zawartości medialnej" i mamy słyszalną zawartość. Czyli w skrócie wystarczy zrobić tak:
<MediaElement Height="100" Width="100" Name="media" Source="sound.mp3" AutoPlay="True" />
Prawda, że pięknie i prosto? No właśnie, nie do końca. Bo o ile jest to bardzo łatwe, to nadaje się de facto jedynie do odtwarzania zawartości na aktualnej stronie - wszak kontrolka jest sztywno przypisana do Page i po zmianie aktywnej strony po prostu przestanie wytwarzać jakiekolwiek dźwięki (co można obchodzić zmiennymi globalnymi, ale takie podejście jest po prostu marnowaniem zasobów telefonu). Z drugiej strony w przypadku wrzucenia apki używającej MediaElement do samo wyzwalającego się materiału (np. po pacnięciu w guzik) na market możemy spotkać się z odrzuceniem podczas certyfikacji, które to wynika z guidelines i domyślnego działania tej kontrolki:
  • When the user is already playing music on the phone when the application is launched, the application must not pause, resume, or stop the active music in the phone
    If the application plays its own background music or adjusts background music volume, it must ask the user for consent to stop playing/adjust the background music (e.g. message dialog or settings menu). This prompt must occur each time the application launches, unless there is an opt-in setting provided to the user and the user has used this setting to opt-in.
Czyli generalnie MediaElement jest super jeśli chcemy odtwarzać muzykę tła na pojedynczej stronie (lub wyświetlić filmik). Oczywiście nie możemy wtedy zapomnieć, że jeśli użytkownik słucha jakiejś muzyki to musimy się go zapytać czy wyraża zgodę na puszczenie muzyki z naszej aplikacji oraz zapewnić konfigurację dla naszego muzycznego tła:
  • If an application plays background music, the application must provide the user with configurable settings for both the background music, and the background music volume.
 Pewnym rozsądnym rozwiązaniem jest użycie BackgroundAudioPlayer, który pozwala nie tylko na puszczanie muzyki w tle wewnątrz całej aplikacji, ale również dzięki agentom wewnątrz innych aplikacji naszego telefonu (czyli możemy stworzyć odtwarzacz muzyki). Klasa ta jest bardzo przydatna, bo nie dość, że pozwala nam na wspomniane odtwarzanie tła, to również umożliwia przewijanie piosenek i wybór miejsca, od którego chcemy utwór odtworzyć (pod tym względem jest to nawet lepsze rozwiązanie od MediaPlayera z XNA). Rozwiązanie to ma jednak tą małą wadę, że plik z dźwiękiem albo musi być dostarczony z IsolatedStorage albo z zewnętrznego adresu URI (poradnik od MS dotyczący BackgroundAudioPlayer).

Jednak jeśli chcemy mieć dźwięki efektów, czyli np. odgłos gdaczącej kury po kliknięciu na guzik, to już tak MediaElement jak i BackgroundAudioPlayer się nie nadaje (bo m.in. wstrzymuje obecnie odgrywany dźwięk). Natomiast do tego celu nadaje się coś innego - klasa SoundEffect pochodząca z XNA :D
            using (var stream = TitleContainer.OpenStream("sound.wav"))
            {
                var effect = SoundEffect.FromStream(stream);
                FrameworkDispatcher.Update();
                effect.Play();
            }
Aż chciałoby się wykorzystać SoundEffect do odtwarzania muzyki w tle (zwłaszcza, że po stworzeniu instancji efektu możemy bez trudu odtwarzanie muzyki zapętlić)... niestety guideline jest bardzo stanowczy pod tym względem:
  • The SoundEffect class must not be used to play a continuous background music track in an application.
Ach Ci źli ludzie z Microsoftu! Na szczęście XNA daje nam dużo, dużo więcej możliwości, z których chyba najprzyjemniejszą jest połączenie klas Song i MediaPlayer:
            Song song = Song.FromUri("Nasze audio", new Uri("sound.mp3", UriKind.Relative));
            if (MediaPlayer.GameHasControl)
            {
                MediaPlayer.Play(song);
            }
Klasa ta jak widać nie tylko z gracją rozwiązuje problem nakładania się naszego muzycznego tła na muzykę odtwarzaną przez użytkownika dzięki polu GameHasControl i pozwala na odtwarzanie pojedynczych piosenek (Song), ale również umożliwia tworzenie ich całych kolekcji (SongCollection) oraz łatwe nawigowanie pomiędzy nimi. Tak więc myśląc o dźwięku w aplikacjach na Windows Phone bazujących na silverlight i tak prędzej czy później wykorzystamy biblioteki XNA, bo po prostu dają one ogromną swobodę i wbrew pozorom są bardzo łatwe w wykorzystaniu.

wtorek, 25 września 2012

PCA, Eigenfaces i rozpoznawanie twarzy

Czym jest PCA?

PCA czyli Principal Component Analysis czyli analiza głównych składowych jest jedną z metod statystycznych służących do analizy zbioru danych. Jeśli kogoś interesuje dokładniejsze omówienie tematu - polecam poszperać choćby po sieci (Wikipedia dla leniwych). Na potrzeby zaś tego wpisu i naszych rozważań uświadomimy sobie jedynie, że PCA umożliwia określenie w jakim stopniu dany klasyfikator jest zgodny z badanym obiektem. Innymi słowy -  jak daleko wartości elementu wzorowego do wartości badanego obiektu.

Czym jest rozpoznawanie twarzy?

Niby oczywistość, ale lepiej ją uściślić teraz, niż potem gubić się w zeznaniach. Z grubsza chodzi o to aby mając zbiór zdjęć z zidentyfikowanymi osobami móc określić czy nowe zdjęcie należy do jakiejś osoby z naszego zbioru (a jeśli tak, to której), czy też jest ono fotografią kogoś zupełnie nowego.

Etapy działania
  1. Przygotować zbiór uczący składający się z obrazów o tych samych wymiarach/rozdzielczości (szerokość i wysokość), w których oczy i usta osób znajdują się na podobnej wysokości.
  2.  Poddać obrazy operacji wyrównywania histogramów (histogram equalization) w celu redukcji wpływu różnicy oświetlenia.
  3. Obliczyć wartości pikseli dla średniego obrazu i odjąć ten obraz od każdego obrazu ze zbioru uczącego.
  4. Utworzyć macierz zawierającą wszystkie obrazy (każdy obraz to jeden wektor).
  5. Obliczyć wektory własne powyższej macierzy.
  6. Obliczyć PCA na podstawie wektorów własnych (jeśli decydujemy się na pominięcie niektórych wektorów to należy odrzucić te z najmniejszymi wartościami własnymi).
  7. Wykonać projekcję z wykorzystaniem PCA dla każdego obrazu ze zbioru uczącego.
  8. Projekcje nowych zdjęć porównywać z projekcjami obiektów już rozpoznanych i wyszukiwać wyników najbliższych.

Accord.NET

Czym jest Accord.NET? Z grubsza jest to framework oferujący bogactwo funkcji matematycznych i statystycznych, w tym m.in. rozkład na wektory własne, analizę PCA, LDA oraz wykrywanie twarzy (które działa trochę topornie, ale działa). Dlaczego wybrałem ten framework do realizacji tego zadania? Cóż popularną alternatywą byłoby użycie biblioteki OpenCV, która dość szeroko jest do tego celu (rozpoznawania twarzy) rekomendowana. Niestety cud ten technologii nie doczekał się sensownego wsparcia pod C# (wrappery działają różnie i generalnie w mojej opinii nie nadają się do użycia w produktach komercyjnych ze względu na moc różnych drobnych i nie tylko problemów z ich działaniem). Natomiast z racji tego, że Accord.NET jest bezproblemowy w użyciu (i ma wiele przykładów i opisów działania różnych zaimplementowanych funkcji) to praca z nim okazała się bardzo przyjemna.

niedziela, 1 lipca 2012

Dithering i C# (PGM)

Niecały rok temu opublikowałem wpis o ditheringu, w którym w zasadzie nic prócz pokazania efektów tego cudu "nowoczesnej" technologii nie pokazałem. Dziś zamierzam to naprawić poprzez:
  • udostępnienie biblioteki, wykonującej obsługującej proces ditheringu (póki co na plikach PGM) przy użyciu metod:
    • Random
    • Threshold
    • Ordered
    • Floyd-Steinberg
    • "fałszywy" Floyd-Steinberg (czasem opisywany jako Floyd-Steinberg - stąd "fałszywy")
    • Jarvis-Judice-Ninke
    • Stucki
    • Burkes
    • Sierra
  • zgrubny opis owych metod wraz z przykładowymi efektami ich działania
  • W najbliższym zaś czasie dorzucę kilka własnych metod będących mniej lub bardziej efektem mych eksperymentów :)

Biblioteka

Obsługa ditheringu została dodana jako statyczna klasa (Transform.Dither) biblioteki Runaurufu.Drawing (do pobrania z chomika) i w obecnym kształcie zawiera metody:
public static class Dither
{
    public static PgmImage Threshold(PgmImage img, UInt16 newMaxColor){...}
    public static PgmImage Random(PgmImage img, UInt16 newMaxColor){...}
    public static PgmImage Ordered(PgmImage img, UInt16[] box, UInt16 newMaxColor){...}
    public static PgmImage Ordered2(PgmImage img, UInt16 newMaxColor){...}
    public static PgmImage Ordered3(PgmImage img, UInt16 newMaxColor){...}
    public static PgmImage Ordered4(PgmImage img, UInt16 newMaxColor){...}
    public static PgmImage Ordered8(PgmImage img, UInt16 newMaxColor){...}
    public static PgmImage Ordered16(PgmImage img, UInt16 newMaxColor){...}
    public static PgmImage FloydSteinberg(PgmImage img, UInt16 newMaxColor){...}
    public static PgmImage FalseFloydSteinberg(PgmImage img, UInt16 newMaxColor){...}
    public static PgmImage JarvisJudiceNinke(PgmImage img, UInt16 newMaxColor){...}
    public static PgmImage Stucki(PgmImage img, UInt16 newMaxColor){...}
    public static PgmImage Burkes(PgmImage img, UInt16 newMaxColor){...}
    public static PgmImage Sierra3(PgmImage img, UInt16 newMaxColor){...}
    public static PgmImage Sierra2(PgmImage img, UInt16 newMaxColor){...}
    public static PgmImage Sierra24A(PgmImage img, UInt16 newMaxColor){...}
}

Metody Ordered2 - Ordered16 wywołują metodę Ordered z wykorzystaniem predefiniowanych tablic kwadratowych o boku równym numerowi znajdującemu się przy nazwie metody (Ordered2 - tablica 2x2).

Metody Threshold i Ordered wykorzystują zrównoleglenie (Parallel.For) - niestety metody z propagacją błędu nie są w stanie z daru wielowątkowości skorzystać.

Opis metod

Oryginalny obraz Oryginalny obraz w odcieniach szarości
Nie będę się tu zagłębiał w to po co powstał dithering, ani czym jest - bo na pierwsze pytanie można napisać rozprawę naukową i dość dobry ogląd sytuacji przedstawia nam choćby Wikipedia, a na pytanie czym jest odpowiedź znaleźć można w poniższym opisie wybranych metod. Dodatkowo dla porównania załączyłem obrazy będące wynikiem działania każdego z algorytmów na obrazie przykładowym złożonym z 4 elementów (w tle gra Civilization V, a z przodu przykładowe pingwiny z Windowsa oraz popularne obrazy - Lena i rzeźba Dawida dłuta Michała Anioła):


Threshold

Threshold dithering
Jedna z prostszych metod ograniczania ilości kolorów obrazu. Polega na rzutowaniu wartości mniejszych od pewnego wybranego progu (ang. threshold - próg, wartość progowa) na jedną zadaną wartość, a wartości większych lub równych na wartość inną (np. 0 i 255). Zazwyczaj wartość progu określa się jako medianę dostępnych wartości (128 = 256/2). Metoda ta ma tą zaletę, że jest szybka i bardzo łatwa do zrównoleglenia. Niestety wiele do życzenia pozostawia efekt końcowy w przypadku zdjęć o zróżnicowanej kolorystyce.






Random

Random dithering
Kolejna prosta metoda, która tym razem wykorzystuje losowo generowaną wartość progową (losowe wartości mogą znajdować się w takim samym zakresie jak wartości dla poszczególnych pikseli). Dla każdego piksela próg jest generowany osobno, co redukuje możliwość powstawania różnego rodzaju powtarzających się wzorów, jednak z tego samego powodu nie ma praktycznej możliwości dwukrotnego wygenerowania takich samych obrazów bez zapisania również wszystkich użytych progów.


Ordered

Ordered dithering ze wzorem 16x16
Metoda uporządkowanego ditheringu jest jedną z metod związanych z używaniem pewnych wzorów, które "nakłada się" na obraz poddawany obróbce. W tym przypadku wartości progowe pochodzą właśnie z owego wzoru, który powtarza się tak horyzontalnie jak i wertykalnie - stąd też biorą się podobnie rozlokowane artefakty. Zaletą algorytmu jest łatwość zrównoleglenia, która w połączeniu z całkiem dobrymi rezultatami wizualnymi tej metody okazuje się idealnym rozwiązaniem w przypadku wielordzeniowych systemów.




Floyd-Steinberg

Floyd-Steinberg
Z metod związanych z propagacją błędu omówię jedynie metodę Floyda-Steinberga. Dlaczego? A z tej prostej przyczyny, że pozostałe (a mówiąc wprost - pochodne) różnią się wyłącznie pozycjami i wartościami ułamku błędu.
Metoda ta opiera się na wykryciu błędu między wartością piksela (konkretnych kolorów w pikselu) oryginalnego obrazu, a wartością w obrazie przetworzonym.
Opis propagacji błędu
w metodzie Floyda-Steinberga
gwiazdką (*) oznaczono
aktualnie przetwarzany piksel
Owa różnica - błąd kwantyzacji - jest następnie rozdzielana wg zdefiniowanej tablicy (zaprezentowanej obok) na sąsiednie nieprzetworzone piksele. Wadą tej metody jest kompletna niezdolność do zrównoleglenia - algorytm musi przejść piksel po pikselu - która jednak jest niejako zacierana przez niesamowite efekty uzyskiwane dzięki tej metodzie. Za konceptem propagacji błędu stoi proste spostrzeżenie - zarówno wartość 0 jak i 120 zostaną zrzutowane na 0, co sprawia, że obraz z szarego staje się czarny. Jeśli jednak mamy 3 kolejne piksele o wartości 120, to wynikiem pracy tego algorytmu nie będzie ciąg {0,0,0}, ale ciąg {0, 255 (120 + 52.5), 0 (120 - 82.5)}, który o wiele lepiej prezentuje szarość.
Stucki dithering
Pochodne metody różnią się jedynie wartościami, liczbą i pozycją ułamków błędu kwantyzacji.

Bibliografia

Czyli co warto przeglądnąć w poszukiwaniu informacji o ditheringu...
  • Wikipedia - czyli dobry wstęp do rozpoczęcia przygody z tą tematyką
  • Praca Lee Daniela Crockera (rozszerzona przez Paula Boulaya oraz Mike Morra) - opisuje dobrze zarówno historię całej idei ditheringu jak i wyżej omawiane algorytmy. (mirror)
  • Joel Yliluoma's arbitrary-palette positional dithering algorithm - strona z przykładami i kodami źródłowymi w PHP. Przydatna do porównania różnych algorytmów - również przy zastosowaniu w filmie.
  • Image Based Artistic Dithering - praca  studentów z PUC w Rio, może nie odkrywcza, ale jeśli ktoś zaczyna przygodę z tą tematyką to może się przydać (choć jak ktoś faktycznie chce ugryźć temat to o wiele lepiej zacząć od pracy Lee Daniela Crockera)

czwartek, 14 czerwca 2012

Obsługa formatu Netpbm przez C#

 Wstęp teoretyczny

Format Netpbm odnosi się tak na prawdę do zestawu formatów plikowych służących do przechowywania obrazów. W skład tego pakietu wchodzą trzy podstawowe formaty:
  • PBM - Portable Bit Map
  • PGM - Portable Gray Map
  • PPM - Portable Pixel Map
 Do tego czasem dorzuca się PAM (Portable Arbitrary Map), którego struktura opiera się na zasadach wręcz identycznych do tych odnoszących się do PBM/PGM/PPM (które od teraz zbiorczo będę określał jako PNM - Portable Any Map).
Jeśli kogoś interesuje więcej na temat tego formatu to zapraszam do czytania:
A jeśli nie chcecie zagłębiać się w te techniczne detale (bo i po co?) to potraktujmy ten wstęp teoretyczny za zakończony i przejdźmy do sedna...

niedziela, 29 kwietnia 2012

WebBrowser i cieknąca pamięć

Jeśli kiedykolwiek używaliście domyślnego WebBrowsera w C# to za pewne zauważyliście, że ilość pamięci zużywanej przez wasz program rośnie wraz z użytkowaniem tej kontrolki w zasadzie w nieskończoność. Dlaczego tak się dzieje? Cóż dokładnie nie wiadomo, wiadomo jednak, że jest to spowodowane głównie cachowaniem zawartości odwiedzonych stron... dodajmy cachowaniem, którego nie można w całości wyłączyć/wyczyścić. Oczywiście można by sądzić, iż problem ten wynika z leciwości tej kontrolki, jednak ku memu wielkiemu zaskoczeniu to samo dzieje się z WebBrowserem dostępnym wewnątrz WPF (obie kontrolki używają tego samego silnika IE).
Ale po co w ogóle używać kontrolkę WebBrowser? Poza oczywistym zastosowaniem - umożliwienia "przeglądania" Internetu (klikania w łącza, oglądania obrazków, czytania tekstów) można również używać go do parsowania ściągniętych plików HTML, z którymi natywne parsery .NET nie dają sobie rady z racji niezgodnego ze standardem użycia tagów.

Oczywiście to do czego chcemy używać WebBrowser jest sprawą drugorzędną, gdyż wpływa jedynie na to czy istnieje jakieś rozwiązanie alternatywne, czy też skazani jesteśmy na WebBrowsera, który pożre przy sprzyjających dla siebie warunkach wszelkie ilości dostępnej pamięci nie dając w zamian absolutnie nic. W tym wpisie przedstawię kilka sposobów na ograniczenie ilości wyciekającej pamięci, więc jeśli ktoś liczy na to, że znajdzie tu sposób na całkowite zażegnanie tego problemu to spotka go rozczarowanie, gdyż takie sposobu nie znam, ani nigdzie nie mogłem znaleźć... Obalę również kilka mitów krążących po sieci związanych z "magicznymi sposobami" na ograniczenie używanej pamięci. Oczywiście zawsze można zaprzestać używania natywnych kontrolek i wykorzystać kontrolki stworzone w pocie czoła innych programistów (dla porównania w dalszej części przedstawię właśnie kontrolkę Awesomium):