1. Wczytywanie obrazka do kontrolki Image:
Zadanie to można wykonać na kilka sposobów: strumień, pobranie z żądanej lokalizacji sieciowej lub ścieżka. Najprostszą i najczęściej stosowaną opcją jest wczytanie ze ścieżki:
Image image = new Image();
BitmapImage bitmapImage = new BitmapImage(new Uri("c:\obraz.png"));
image.Source = bitmapImage;
2. Zapisywanie zawartości kontrolki Image do pliku.
Tutaj niestety bez pomocy strumienia się nie obejdzie.
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "Pliki BMP | *.bmp | Pliki PNG | *.png";
if (saveFileDialog.ShowDialog(this) == true)
{
FileStream saveStream = new FileStream(saveFileDialog.FileName, FileMode.OpenOrCreate);
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(Image.Image));
encoder.Save(saveStream);
saveStream.Close();
}
Kod trochę bardziej skomplikowany od tego znanego w WindowsForms, ale działa na takiej samej zasadzie :) Oczywiście jeżeli chcemy zapisać obrazek w innym formacie, możemy skorzystać z innego enkodera.
3. Dopasowanie rozmiarów obiektu Image do wczytywanego obrazka
Bitmap = new BitmapImage(new Uri(openFileDialog.FileName));
Image.Width = Bitmap.Width;
Image.Height = Bitmap.Height;
4. Konwersja bitmap
W WinForms istniała dziecinnie prosta możliwość zmiany wartości pojedyńczych pixeli na naszym obrazku.
Dla zobrazowania tego co chcemy zrobić przypuśćmy scenariusz w którym chcielibyśmy zmienić skalę kolorów na szare. W WinForms można by to zrobić w taki sposób:
Stopwatch clock = new Stopwatch();
clock.Start();
Bitmap img = new Bitmap(Image.FromFile(@"c:\o.png"));
int rows = img.Width;
int cols = img.Height;
Color c;
int b;
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
c = img.GetPixel(i, j);
b = (c.R + c.G + c.B) / 3;
img.SetPixel(i, j, Color.FromArgb(b, b, b));
}
}
pictureBox1.Image = img;
clock.Stop();
MessageBox.Show(clock.ElapsedMilliseconds.ToString());
Jak widać dołożyłem do kodu Stopwatch aby sprawdzić ile trwa konwersja do skali szarości w tym rozwiązaniu. Podczas konwersji wykorzystuje bitmapę o wielkości 512 x 512:
Jak widać średni czas konwersji takiego imaga zajmuje > 600 ms. Nie jest to zadowalający czas. Należy też wziąć pod uwagę fakt, że zdjęcia w większości przypadków są większej rozdzielczości. Nasz sposób sprawia, że wraz ze zwiększaniem rozdzielczości będzie rósł czas konwersji. Dzieje się tak z powodu powolnego działania funkcji GetPixel i SetPixel. Istnieje kilka rozwiązań tego problemu. Można skorzystać z kodu unsafe lub bardzo rozbudowanego mechanizmu oferowanego przez klasę ColorMatrix.
W WPF niestety (albo na szczęście) nie istnieją metody GetPixel i SetPixel.Aby zmienić wartość pojedynczego pixela należy użyć klasy WriteableBitmap, która posiada dwie metody: CopyPixels i WritePixels. Pierwsza z nich kopiuje wartości pixeli z Bitmapy do tablicy a druga nadpisuje wartości pixeli w Bitmapie.Zwrócić należy tutaj na słowo wartości pixeli. Otóż każdy pixel jest reprezentowany przez 3 składowe RGB i A (alpha - przeźroczystość). Czyli dla przykładu pierwszy pixel będzie zapisany na pierwszych 4 pozycjach naszej tablicy, drugi na kolejnych 4 itd.
Zobaczmy na przykład:
Zobaczmy na przykład:
BitmapImage image = new BitmapImage(new Uri(@"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg"));
width = (int)image.Width;
height = (int)image.Height;
_bitmap = new WriteableBitmap(image);
stride = width * ((this._bitmap.Format.BitsPerPixel + 7) / 8);
int arraySize = stride * height;
byte[] pixels = new byte[arraySize];
_bitmap.CopyPixels(pixels, stride, 0);
int color = 0;
int j = 0;
for (int i = 0; i < pixels.Length / 4; ++i)
{
color = (pixels[j] + pixels[j + 1] + pixels[j + 2]) / 3;
pixels[j] = (byte)color;
pixels[j + 1] = (byte)color;
pixels[j + 2] = (byte)color;
pixels[j + 3] = 255;
j += 4;
}
Int32Rect rect = new Int32Rect(0, 0, width, height);
_bitmap.WritePixels(rect, pixels, stride, 0);
Image.Source = _bitmap;
Kod może trochę bardziej rozbudowany od znanego z WinForms ale za to działający znacznie szybciej. O ile szybciej? O tym przekonamy się podczas tego testu:
Jak widać czas wynosi około 42 ms co jest dużo lepszym rezultatem. Kolejne czasy na poziomie 9 - 13 ms są wynikiem używania przez WPF Intelligent Redrawing (więcej o tym narzędziu można poczytać na stronach msdn).
Jeżeli mowa o zamianie na skalę szarości nie można pominąć tego co sam framework oferuje. Dzięki klasie FormatConvertedBitmap mamy możliwość konwersji obrazka na dowolną skalę. Przykład:
BitmapImage image = new BitmapImage(new Uri(@"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg"));
FormatConvertedBitmap format = new FormatConvertedBitmap();
format.BeginInit();
format.Source = image;
format.DestinationFormat = PixelFormats.Gray32Float;
format.EndInit();
Image.Source = format;
Kod jak widać bardzo prosty, ciekawe jak z wydajnością?:
Wydajność całkiem niezła, czas konwersji zmniejszył się ponad 4 krotnie. A kolejne przemalowania nie zabierają praktycznie żadnego czasu (jedynie kiedy badamy takty procesora można wychwycić jakieś zmiany).
Oczywiście naszą metodę można przyśpieszyć poprzez użycie wskaźników w trybie unsafe.
Brak komentarzy:
Prześlij komentarz