Lo scopo di questo breve tutorial è quello di mostrare come con poche righe di codice .Net 3.5 e Windows Presentation Foundation (WPF) ci permettono di aprire, modificare il colore di un’immagine e salvare il risultato finale. Per quanto riguarda la costruzione della GUI in XAML rimandiamo direttamente al progetto in allegato.
Come anticipato, il nostro scopo è quello di caricare un’immagine (per semplicità gestiremo immagini BMP, GIF e JPEG) in un oggetto del tipo System.Windows.Controls.Image, applicare un filtro all’immagine caricata (per semplicità convertiremo l’immagine in bianco e nero, ovvero in un’immagine con 1 bit di colore), visualizzeremo nel medesimo controllo il risultato e salveremo la stessa in un file (anch’esso del tipo BMP, GIF o JPEG).
Premettiamo che sfortunatamente WPF non ha ancora a disposizione dei controllo di apertura/salvataggio di un file, per cui saremo costretti ad includere nel nostro progetto anche la libreria System.Windows.Forms.
OpenFileDialog openFile = new OpenFileDialog();
openFile.Filter = "BMP files (*.bmp)|*.bmp|GIF files (*.gif)|*.gif|JPEG files (*.jpg)|*.jpg";
openFile.ShowDialog();
fileToProcess = openFile.FileName;
if (fileToProcess != String.Empty || fileToProcess != "")
{
Uri myUri = new Uri(fileToProcess, UriKind.RelativeOrAbsolute);
switch (openFile.FilterIndex)
{
case 0:
BmpBitmapDecoder decoder = new BmpBitmapDecoder(myUri, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource bitmapSource = decoder.Frames[0];
mainImage.Source = bitmapSource;
break;
case 1:
GifBitmapDecoder gifDecoder = new GifBitmapDecoder(myUri, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource gifBitmapSource = gifDecoder.Frames[0];
mainImage.Source = gifBitmapSource;
break;
case 3:
JpegBitmapDecoder jpgDecoder = new JpegBitmapDecoder(myUri, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource jpgBitmapSource = jpgDecoder.Frames[0];
mainImage.Source = jpgBitmapSource;
break;
default:
break;
}
}
Passo 1: caricamento dell’immagine nel controllo Image (mainImage).
OpenFileDialog openFile = new OpenFileDialog();
openFile.Filter = "BMP files (*.bmp)|*.bmp|GIF files (*.gif)|*.gif|JPEG files (*.jpg)|*.jpg";
openFile.ShowDialog();
fileToProcess = openFile.FileName;
if (fileToProcess != String.Empty || fileToProcess != "")
{
Uri myUri = new Uri(fileToProcess, UriKind.RelativeOrAbsolute);
switch (openFile.FilterIndex)
{
case 0:
BmpBitmapDecoder decoder = new BmpBitmapDecoder(myUri, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource bitmapSource = decoder.Frames[0];
mainImage.Source = bitmapSource;
break;
case 1:
GifBitmapDecoder gifDecoder = new GifBitmapDecoder(myUri, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource gifBitmapSource = gifDecoder.Frames[0];
mainImage.Source = gifBitmapSource;
break;
case 3:
JpegBitmapDecoder jpgDecoder = new JpegBitmapDecoder(myUri, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource jpgBitmapSource = jpgDecoder.Frames[0];
mainImage.Source = jpgBitmapSource;
break;
default:
break;
}
}
Il codice precedente non ha bisogno di molte spiegazioni: tramite l’usuale controllo OpenFileDialog otteniamo il nome del file da caricare che andremo a “decodificare” tramite un’istanza della classe BmpBitmapDecodre/GifBitmapDecoder/JpgBitmapDecoder. A sua volta quest’oggetto restituirà un frame che contiene il “sorgente”, ovvero un’istanza di ImageSource che è il formato di “input” del controllo System.Windows.Controls.Image, ovvero l’oggetto che mostra a video l’immagine caricata dal file.
Passo 2: conversione in bianco e nero dell’immagine caricata.
FormatConvertedBitmap newFormatedBitmapSource = new FormatConvertedBitmap();
newFormatedBitmapSource.BeginInit();
newFormatedBitmapSource.Source = (BitmapSource)mainImage.Source;
newFormatedBitmapSource.DestinationFormat = PixelFormats.BlackWhite;
newFormatedBitmapSource.EndInit();
Image myImage = new Image();
myImage.Source = newFormatedBitmapSource;
mainImage.Source = myImage.Source;
Anche in questo caso il codice è molto semplice: un oggetto FormatConvertedBitmap prenderà come sorgente l’immagine caricata nel controllo Image e tramite la proprietà DestinationFormat applicherà il filtro che abbiamo scelto, in questo caso il formato di pixel con 1 bit di colore.
Successivamente provvederemo a creare un’oggetto Image a partire dalla conversione appena operata che sarà la nuova “sorgente” del controllo Image che di conseguenza mostrerà l’immagine modificata.
Passo 3: Salvataggio su disco dell’immagine modificata.
SaveFileDialog saveFile = new SaveFileDialog();
saveFile.Filter = "BMP files (*.bmp)|*.bmp|GIF files (*.gif)|*.gif|JPEG files (*.jpg)|*.jpg";
saveFile.ShowDialog();
fileToSave = saveFile.FileName;
if (fileToSave != String.Empty || fileToSave != "")
{
double actualHeight = mainImage.RenderSize.Height;
double actualWidth = mainImage.RenderSize.Width;
double renderHeight = actualHeight * 1;
double renderWidth = actualWidth * 1;
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)renderWidth, (int)renderHeight, 96, 96, PixelFormats.Pbgra32);
VisualBrush sourceBrush = new VisualBrush(mainImage);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.PushTransform(new ScaleTransform(1, 1));
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));
}
renderTarget.Render(drawingVisual);
switch (saveFile.FilterIndex)
{
case 1:
BmpBitmapEncoder bmpEncoder = new BmpBitmapEncoder();
bmpEncoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (FileStream fileStream = new FileStream(fileToSave, FileMode.Create))
{
bmpEncoder.Save(fileStream);
fileStream.Flush();
fileStream.Close();
}
break;
case 2:
GifBitmapEncoder gifEncoder = new GifBitmapEncoder();
gifEncoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (FileStream fileStream = new FileStream(fileToSave, FileMode.Create))
{
gifEncoder.Save(fileStream);
fileStream.Flush();
fileStream.Close();
}
break;
case 3:
JpegBitmapEncoder jpgEncoder = new JpegBitmapEncoder();
jpgEncoder.QualityLevel = 100;
jpgEncoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (FileStream fileStream = new FileStream(fileToSave, FileMode.Create))
{
jpgEncoder.Save(fileStream);
fileStream.Flush();
fileStream.Close();
}
break;
default:
break;
}
}
Questo metodo è un po’ più lungo dei precedenti ma alla fine non è molto complicato. Tramite l’usuale oggetto SaveFileDialog otteniamo il nome del file su cui salvare l’immagine che abbiamo precedentemente modificato. Calcoliamo altezza e larghezza dell’immagine mostrata a video e le stesse informazioni per l’immagine che andremo a salvare. Notiamo che in questo caso avviene una moltiplicazione per 1: stiamo semplicemente usando la scala 1:1.
Gli oggetti del tipo VisualBrush, DrawingVisual e DrawingContext ci permettono di “selezionare” l’immagine contenuta nel controllo Image a partire dal punto inizale di coordinate (0,0) al punto finale (actualWidth, actualHeight). Il risultato della selezione verrà “registrato” in un oggetto RenderTargetBitmap che non fa altro che convertire un oggetto “visual” appunto in una bitmap.
Lo switch successivo è molto semplice: a seconda del tipo di output scelto (bmp, gif o jpg) creeremo un oggetto “encoder” cui verrà aggiunto un frame contenente la bitmap precedentemente creata. Questo encoder possiede un metodo “save” che permette di salvare il contenuto dell’encodere in uno stream creato ad hoc. In questo caso lo stream su cui andremo a salvare è uno stream su file.