Procedura dettagliata: creazione di una rete per l'elaborazione di immagini
Questo documento illustra come creare una rete di blocchi di messaggi asincroni che eseguono l'elaborazione delle immagini.
La rete determina le operazioni da eseguire su un'immagine sulla base delle relative caratteristiche. In questo esempio viene usato il modello di flusso di dati per instradare le immagini attraverso la rete. Nel modello del flusso di dati, i componenti indipendenti di un programma di comunicano tra loro mediante l'invio di messaggi. Quando un componente riceve un messaggio, può eseguire un'azione e quindi passare il risultato di tale azione a un altro componente. Confrontarlo con il modello di flusso di controllo, in cui un'applicazione usa strutture di controllo, ad esempio istruzioni condizionali, cicli e così via, per controllare l'ordine delle operazioni in un programma.
Una rete basata sul flusso di dati crea una pipeline di attività. Ogni fase della pipeline esegue simultaneamente parte dell'attività complessiva. Un'analogia a questo è data da una catena di montaggio per la produzione di automobili. Quando ogni veicolo passa attraverso la linea di montaggio, una stazione assembla il telaio, un altro installa il motore e così via. Consentendo l'assemblaggio simultaneo di più veicoli, la linea di montaggio offre una velocità effettiva migliore rispetto all'assemblaggio di veicoli completi uno alla volta.
Prerequisiti
Leggere i documenti seguenti prima di iniziare questa procedura dettagliata:
È anche consigliabile comprendere le nozioni di base di GDI+ prima di iniziare questa procedura dettagliata.
Sezioni
Questa procedura dettagliata contiene le sezioni seguenti:
Definizione della funzionalità di elaborazione delle immagini
Questa sezione illustra le funzioni di supporto usate dalla rete di elaborazione delle immagini per lavorare con le immagini lette dal disco.
Le funzioni seguenti, GetRGB
e MakeColor
, estraggono e combinano rispettivamente i singoli componenti del colore specificato.
// Retrieves the red, green, and blue components from the given
// color value.
void GetRGB(DWORD color, BYTE& r, BYTE& g, BYTE& b)
{
r = static_cast<BYTE>((color & 0x00ff0000) >> 16);
g = static_cast<BYTE>((color & 0x0000ff00) >> 8);
b = static_cast<BYTE>((color & 0x000000ff));
}
// Creates a single color value from the provided red, green,
// and blue components.
DWORD MakeColor(BYTE r, BYTE g, BYTE b)
{
return (r<<16) | (g<<8) | (b);
}
La funzione seguente, ProcessImage
, chiama l'oggetto std::function specificato per trasformare il valore di colore di ogni pixel in un oggetto GDI+ Bitmap. La ProcessImage
funzione usa l'algoritmo concurrency::p arallel_for per elaborare ogni riga della bitmap in parallelo.
// Calls the provided function for each pixel in a Bitmap object.
void ProcessImage(Bitmap* bmp, const function<void (DWORD&)>& f)
{
int width = bmp->GetWidth();
int height = bmp->GetHeight();
// Lock the bitmap.
BitmapData bitmapData;
Rect rect(0, 0, bmp->GetWidth(), bmp->GetHeight());
bmp->LockBits(&rect, ImageLockModeWrite, PixelFormat32bppRGB, &bitmapData);
// Get a pointer to the bitmap data.
DWORD* image_bits = (DWORD*)bitmapData.Scan0;
// Call the function for each pixel in the image.
parallel_for (0, height, [&, width](int y)
{
for (int x = 0; x < width; ++x)
{
// Get the current pixel value.
DWORD* curr_pixel = image_bits + (y * width) + x;
// Call the function.
f(*curr_pixel);
}
});
// Unlock the bitmap.
bmp->UnlockBits(&bitmapData);
}
Le funzioni seguenti, , Sepiatone
Grayscale
ColorMask
, e Darken
, chiamano la ProcessImage
funzione per trasformare il valore di colore di ogni pixel in un Bitmap
oggetto . Ognuna di queste funzioni usa un'espressione lambda per definire la trasformazione del colore di un pixel.
// Converts the given image to grayscale.
Bitmap* Grayscale(Bitmap* bmp)
{
ProcessImage(bmp,
[](DWORD& color) {
BYTE r, g, b;
GetRGB(color, r, g, b);
// Set each color component to the average of
// the original components.
BYTE c = (static_cast<WORD>(r) + g + b) / 3;
color = MakeColor(c, c, c);
}
);
return bmp;
}
// Applies sepia toning to the provided image.
Bitmap* Sepiatone(Bitmap* bmp)
{
ProcessImage(bmp,
[](DWORD& color) {
BYTE r0, g0, b0;
GetRGB(color, r0, g0, b0);
WORD r1 = static_cast<WORD>((r0 * .393) + (g0 *.769) + (b0 * .189));
WORD g1 = static_cast<WORD>((r0 * .349) + (g0 *.686) + (b0 * .168));
WORD b1 = static_cast<WORD>((r0 * .272) + (g0 *.534) + (b0 * .131));
color = MakeColor(min(0xff, r1), min(0xff, g1), min(0xff, b1));
}
);
return bmp;
}
// Applies the given color mask to each pixel in the provided image.
Bitmap* ColorMask(Bitmap* bmp, DWORD mask)
{
ProcessImage(bmp,
[mask](DWORD& color) {
color = color & mask;
}
);
return bmp;
}
// Darkens the provided image by the given amount.
Bitmap* Darken(Bitmap* bmp, unsigned int percent)
{
if (percent > 100)
throw invalid_argument("Darken: percent must less than 100.");
double factor = percent / 100.0;
ProcessImage(bmp,
[factor](DWORD& color) {
BYTE r, g, b;
GetRGB(color, r, g, b);
r = static_cast<BYTE>(factor*r);
g = static_cast<BYTE>(factor*g);
b = static_cast<BYTE>(factor*b);
color = MakeColor(r, g, b);
}
);
return bmp;
}
La funzione seguente, GetColorDominance
, chiama anche la ProcessImage
funzione . Tuttavia, invece di modificare il valore di ogni colore, questa funzione usa oggetti concurrency::combinable per calcolare se il componente colore rosso, verde o blu domina l'immagine.
// Determines which color component (red, green, or blue) is most dominant
// in the given image and returns a corresponding color mask.
DWORD GetColorDominance(Bitmap* bmp)
{
// The ProcessImage function processes the image in parallel.
// The following combinable objects enable the callback function
// to increment the color counts without using a lock.
combinable<unsigned int> reds;
combinable<unsigned int> greens;
combinable<unsigned int> blues;
ProcessImage(bmp,
[&](DWORD& color) {
BYTE r, g, b;
GetRGB(color, r, g, b);
if (r >= g && r >= b)
reds.local()++;
else if (g >= r && g >= b)
greens.local()++;
else
blues.local()++;
}
);
// Determine which color is dominant and return the corresponding
// color mask.
unsigned int r = reds.combine(plus<unsigned int>());
unsigned int g = greens.combine(plus<unsigned int>());
unsigned int b = blues.combine(plus<unsigned int>());
if (r + r >= g + b)
return 0x00ff0000;
else if (g + g >= r + b)
return 0x0000ff00;
else
return 0x000000ff;
}
La funzione seguente, GetEncoderClsid
, recupera l'identificatore di classe per il tipo MIME specificato di un codificatore. L'applicazione usa questa funzione per recuperare il codificatore per una bitmap.
// Retrieves the class identifier for the given MIME type of an encoder.
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = nullptr;
GetImageEncodersSize(&num, &size);
if(size == 0)
return -1; // Failure
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if(pImageCodecInfo == nullptr)
return -1; // Failure
GetImageEncoders(num, size, pImageCodecInfo);
for(UINT j = 0; j < num; ++j)
{
if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}
free(pImageCodecInfo);
return -1; // Failure
}
Creazione della rete di elaborazione immagini
Questa sezione descrive come creare una rete di blocchi di messaggi asincroni che eseguono l'elaborazione delle immagini in ogni immagine JPEG (.jpg) in una determinata directory. La rete esegue le operazioni di elaborazione delle immagini seguenti:
Per qualsiasi immagine creata da Tom, convertirla in scala di grigi.
Per qualsiasi immagine con colore rosso come colore dominante, rimuovere i componenti verdi e blu e quindi scurirlo.
Per qualsiasi altra immagine, applicare la topia.
La rete applica solo la prima operazione di elaborazione delle immagini che corrisponde a una di queste condizioni. Ad esempio, se un'immagine viene creata da Tom e ha il rosso come colore dominante, l'immagine viene convertita solo in scala di grigi.
Dopo che la rete esegue ogni operazione di elaborazione delle immagini, salva l'immagine su disco come file bitmap (.bmp).
I passaggi seguenti illustrano come creare una funzione che implementa questa rete di elaborazione immagini e la applica a ogni immagine JPEG in una determinata directory.
Per creare la rete di elaborazione delle immagini
Creare una funzione ,
ProcessImages
, che accetta il nome di una directory su disco.void ProcessImages(const wstring& directory) { }
ProcessImages
Nella funzione creare unacountdown_event
variabile. Lacountdown_event
classe viene illustrata più avanti in questa procedura dettagliata.// Holds the number of active image processing operations and // signals to the main thread that processing is complete. countdown_event active(0);
Creare un oggetto std::map che associa un
Bitmap
oggetto al nome file originale.// Maps Bitmap objects to their original file names. map<Bitmap*, wstring> bitmap_file_names;
Aggiungere il codice seguente per definire i membri della rete di elaborazione delle immagini.
// // Create the nodes of the network. // // Loads Bitmap objects from disk. transformer<wstring, Bitmap*> load_bitmap( [&](wstring file_name) -> Bitmap* { Bitmap* bmp = new Bitmap(file_name.c_str()); if (bmp != nullptr) bitmap_file_names.insert(make_pair(bmp, file_name)); return bmp; } ); // Holds loaded Bitmap objects. unbounded_buffer<Bitmap*> loaded_bitmaps; // Converts images that are authored by Tom to grayscale. transformer<Bitmap*, Bitmap*> grayscale( [](Bitmap* bmp) { return Grayscale(bmp); }, nullptr, [](Bitmap* bmp) -> bool { if (bmp == nullptr) return false; // Retrieve the artist name from metadata. UINT size = bmp->GetPropertyItemSize(PropertyTagArtist); if (size == 0) // Image does not have the Artist property. return false; PropertyItem* artistProperty = (PropertyItem*) malloc(size); bmp->GetPropertyItem(PropertyTagArtist, size, artistProperty); string artist(reinterpret_cast<char*>(artistProperty->value)); free(artistProperty); return (artist.find("Tom ") == 0); } ); // Removes the green and blue color components from images that have red as // their dominant color. transformer<Bitmap*, Bitmap*> colormask( [](Bitmap* bmp) { return ColorMask(bmp, 0x00ff0000); }, nullptr, [](Bitmap* bmp) -> bool { if (bmp == nullptr) return false; return (GetColorDominance(bmp) == 0x00ff0000); } ); // Darkens the color of the provided Bitmap object. transformer<Bitmap*, Bitmap*> darken([](Bitmap* bmp) { return Darken(bmp, 50); }); // Applies sepia toning to the remaining images. transformer<Bitmap*, Bitmap*> sepiatone( [](Bitmap* bmp) { return Sepiatone(bmp); }, nullptr, [](Bitmap* bmp) -> bool { return bmp != nullptr; } ); // Saves Bitmap objects to disk. transformer<Bitmap*, Bitmap*> save_bitmap([&](Bitmap* bmp) -> Bitmap* { // Replace the file extension with .bmp. wstring file_name = bitmap_file_names[bmp]; file_name.replace(file_name.rfind(L'.') + 1, 3, L"bmp"); // Save the processed image. CLSID bmpClsid; GetEncoderClsid(L"image/bmp", &bmpClsid); bmp->Save(file_name.c_str(), &bmpClsid); return bmp; }); // Deletes Bitmap objects. transformer<Bitmap*, Bitmap*> delete_bitmap([](Bitmap* bmp) -> Bitmap* { delete bmp; return nullptr; }); // Decrements the event counter. call<Bitmap*> decrement([&](Bitmap* _) { active.signal(); });
Aggiungere il codice seguente per connettere la rete.
// // Connect the network. // load_bitmap.link_target(&loaded_bitmaps); loaded_bitmaps.link_target(&grayscale); loaded_bitmaps.link_target(&colormask); colormask.link_target(&darken); loaded_bitmaps.link_target(&sepiatone); loaded_bitmaps.link_target(&decrement); grayscale.link_target(&save_bitmap); darken.link_target(&save_bitmap); sepiatone.link_target(&save_bitmap); save_bitmap.link_target(&delete_bitmap); delete_bitmap.link_target(&decrement);
Aggiungere il codice seguente per inviare all'inizio della rete il percorso completo di ogni file JPEG nella directory.
// Traverse all files in the directory. wstring searchPattern = directory; searchPattern.append(L"\\*"); WIN32_FIND_DATA fileFindData; HANDLE hFind = FindFirstFile(searchPattern.c_str(), &fileFindData); if (hFind == INVALID_HANDLE_VALUE) return; do { if (!(fileFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { wstring file = fileFindData.cFileName; // Process only JPEG files. if (file.rfind(L".jpg") == file.length() - 4) { // Form the full path to the file. wstring full_path(directory); full_path.append(L"\\"); full_path.append(file); // Increment the count of work items. active.add_count(); // Send the path name to the network. send(load_bitmap, full_path); } } } while (FindNextFile(hFind, &fileFindData) != 0); FindClose(hFind);
Attendere che la
countdown_event
variabile raggiunga zero.// Wait for all operations to finish. active.wait();
Nella tabella seguente vengono descritti i membri della rete.
Membro | Descrizione |
---|---|
load_bitmap |
Oggetto concurrency::transformer che carica un Bitmap oggetto dal disco e aggiunge una voce all'oggetto map per associare l'immagine al nome file originale. |
loaded_bitmaps |
Oggetto concurrency::unbounded_buffer che invia le immagini caricate ai filtri di elaborazione delle immagini. |
grayscale |
Oggetto transformer che converte le immagini create da Tom in scala di grigi. Usa i metadati dell'immagine per determinarne l'autore. |
colormask |
Oggetto transformer che rimuove i componenti di colore verde e blu dalle immagini con il colore rosso come colore dominante. |
darken |
Oggetto transformer che scurisce le immagini con colore rosso come colore dominante. |
sepiatone |
Oggetto transformer che applica la sepia alle immagini che non sono create da Tom e non sono prevalentemente rosse. |
save_bitmap |
Oggetto transformer che salva l'oggetto elaborato image su disco come bitmap. save_bitmap recupera il nome file originale dall'oggetto e ne modifica l'estensione map in .bmp. |
delete_bitmap |
Oggetto transformer che libera la memoria per le immagini. |
decrement |
Oggetto concurrency::call che funge da nodo terminale nella rete. Decrementa l'oggetto countdown_event per segnalare all'applicazione principale che un'immagine è stata elaborata. |
Il loaded_bitmaps
buffer dei messaggi è importante perché, come unbounded_buffer
oggetto, offre Bitmap
oggetti a più ricevitori. Quando un blocco di destinazione accetta un Bitmap
oggetto , l'oggetto unbounded_buffer
non offre tale Bitmap
oggetto ad altre destinazioni. Pertanto, l'ordine in cui si collegano oggetti a un unbounded_buffer
oggetto è importante. I grayscale
blocchi di messaggi , colormask
e sepiatone
usano ogni filtro per accettare solo determinati Bitmap
oggetti. Il decrement
buffer dei messaggi è una destinazione importante del buffer dei loaded_bitmaps
messaggi perché accetta tutti gli Bitmap
oggetti rifiutati dagli altri buffer di messaggi. Per unbounded_buffer
propagare i messaggi in ordine, è necessario un oggetto . Pertanto, un unbounded_buffer
oggetto si blocca finché non viene collegato un nuovo blocco di destinazione e accetta il messaggio se nessun blocco di destinazione corrente accetta tale messaggio.
Se l'applicazione richiede che più blocchi di messaggi elaborino il messaggio, anziché solo il blocco di messaggi che prima accetta il messaggio, è possibile usare un altro tipo di blocco di messaggi, ad esempio overwrite_buffer
. La overwrite_buffer
classe contiene un messaggio alla volta, ma propaga tale messaggio a ognuna delle destinazioni.
Nella figura seguente viene illustrata la rete di elaborazione delle immagini:
L'oggetto countdown_event
in questo esempio consente alla rete di elaborazione delle immagini di informare l'applicazione principale quando tutte le immagini sono state elaborate. La countdown_event
classe usa un oggetto concurrency::event per segnalare quando un valore del contatore raggiunge zero. L'applicazione principale incrementa il contatore ogni volta che invia un nome file alla rete. Il nodo terminale della rete decrementa il contatore dopo l'elaborazione di ogni immagine. Dopo che l'applicazione principale attraversa la directory specificata, attende che l'oggetto segnali che il countdown_event
contatore ha raggiunto zero.
L'esempio seguente illustra la countdown_event
classe :
// A synchronization primitive that is signaled when its
// count reaches zero.
class countdown_event
{
public:
countdown_event(unsigned int count = 0)
: _current(static_cast<long>(count))
{
// Set the event if the initial count is zero.
if (_current == 0L)
_event.set();
}
// Decrements the event counter.
void signal() {
if(InterlockedDecrement(&_current) == 0L) {
_event.set();
}
}
// Increments the event counter.
void add_count() {
if(InterlockedIncrement(&_current) == 1L) {
_event.reset();
}
}
// Blocks the current context until the event is set.
void wait() {
_event.wait();
}
private:
// The current count.
volatile long _current;
// The event that is set when the counter reaches zero.
event _event;
// Disable copy constructor.
countdown_event(const countdown_event&);
// Disable assignment.
countdown_event const & operator=(countdown_event const&);
};
Esempio completo
Nel codice seguente viene illustrato l'esempio completo. La wmain
funzione gestisce la libreria GDI+ e chiama la ProcessImages
funzione per elaborare i file JPEG nella Sample Pictures
directory.
// image-processing-network.cpp
// compile with: /DUNICODE /EHsc image-processing-network.cpp /link gdiplus.lib
#include <windows.h>
#include <gdiplus.h>
#include <iostream>
#include <map>
#include <agents.h>
#include <ppl.h>
using namespace concurrency;
using namespace Gdiplus;
using namespace std;
// Retrieves the red, green, and blue components from the given
// color value.
void GetRGB(DWORD color, BYTE& r, BYTE& g, BYTE& b)
{
r = static_cast<BYTE>((color & 0x00ff0000) >> 16);
g = static_cast<BYTE>((color & 0x0000ff00) >> 8);
b = static_cast<BYTE>((color & 0x000000ff));
}
// Creates a single color value from the provided red, green,
// and blue components.
DWORD MakeColor(BYTE r, BYTE g, BYTE b)
{
return (r<<16) | (g<<8) | (b);
}
// Calls the provided function for each pixel in a Bitmap object.
void ProcessImage(Bitmap* bmp, const function<void (DWORD&)>& f)
{
int width = bmp->GetWidth();
int height = bmp->GetHeight();
// Lock the bitmap.
BitmapData bitmapData;
Rect rect(0, 0, bmp->GetWidth(), bmp->GetHeight());
bmp->LockBits(&rect, ImageLockModeWrite, PixelFormat32bppRGB, &bitmapData);
// Get a pointer to the bitmap data.
DWORD* image_bits = (DWORD*)bitmapData.Scan0;
// Call the function for each pixel in the image.
parallel_for (0, height, [&, width](int y)
{
for (int x = 0; x < width; ++x)
{
// Get the current pixel value.
DWORD* curr_pixel = image_bits + (y * width) + x;
// Call the function.
f(*curr_pixel);
}
});
// Unlock the bitmap.
bmp->UnlockBits(&bitmapData);
}
// Converts the given image to grayscale.
Bitmap* Grayscale(Bitmap* bmp)
{
ProcessImage(bmp,
[](DWORD& color) {
BYTE r, g, b;
GetRGB(color, r, g, b);
// Set each color component to the average of
// the original components.
BYTE c = (static_cast<WORD>(r) + g + b) / 3;
color = MakeColor(c, c, c);
}
);
return bmp;
}
// Applies sepia toning to the provided image.
Bitmap* Sepiatone(Bitmap* bmp)
{
ProcessImage(bmp,
[](DWORD& color) {
BYTE r0, g0, b0;
GetRGB(color, r0, g0, b0);
WORD r1 = static_cast<WORD>((r0 * .393) + (g0 *.769) + (b0 * .189));
WORD g1 = static_cast<WORD>((r0 * .349) + (g0 *.686) + (b0 * .168));
WORD b1 = static_cast<WORD>((r0 * .272) + (g0 *.534) + (b0 * .131));
color = MakeColor(min(0xff, r1), min(0xff, g1), min(0xff, b1));
}
);
return bmp;
}
// Applies the given color mask to each pixel in the provided image.
Bitmap* ColorMask(Bitmap* bmp, DWORD mask)
{
ProcessImage(bmp,
[mask](DWORD& color) {
color = color & mask;
}
);
return bmp;
}
// Darkens the provided image by the given amount.
Bitmap* Darken(Bitmap* bmp, unsigned int percent)
{
if (percent > 100)
throw invalid_argument("Darken: percent must less than 100.");
double factor = percent / 100.0;
ProcessImage(bmp,
[factor](DWORD& color) {
BYTE r, g, b;
GetRGB(color, r, g, b);
r = static_cast<BYTE>(factor*r);
g = static_cast<BYTE>(factor*g);
b = static_cast<BYTE>(factor*b);
color = MakeColor(r, g, b);
}
);
return bmp;
}
// Determines which color component (red, green, or blue) is most dominant
// in the given image and returns a corresponding color mask.
DWORD GetColorDominance(Bitmap* bmp)
{
// The ProcessImage function processes the image in parallel.
// The following combinable objects enable the callback function
// to increment the color counts without using a lock.
combinable<unsigned int> reds;
combinable<unsigned int> greens;
combinable<unsigned int> blues;
ProcessImage(bmp,
[&](DWORD& color) {
BYTE r, g, b;
GetRGB(color, r, g, b);
if (r >= g && r >= b)
reds.local()++;
else if (g >= r && g >= b)
greens.local()++;
else
blues.local()++;
}
);
// Determine which color is dominant and return the corresponding
// color mask.
unsigned int r = reds.combine(plus<unsigned int>());
unsigned int g = greens.combine(plus<unsigned int>());
unsigned int b = blues.combine(plus<unsigned int>());
if (r + r >= g + b)
return 0x00ff0000;
else if (g + g >= r + b)
return 0x0000ff00;
else
return 0x000000ff;
}
// Retrieves the class identifier for the given MIME type of an encoder.
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = nullptr;
GetImageEncodersSize(&num, &size);
if(size == 0)
return -1; // Failure
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if(pImageCodecInfo == nullptr)
return -1; // Failure
GetImageEncoders(num, size, pImageCodecInfo);
for(UINT j = 0; j < num; ++j)
{
if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}
free(pImageCodecInfo);
return -1; // Failure
}
// A synchronization primitive that is signaled when its
// count reaches zero.
class countdown_event
{
public:
countdown_event(unsigned int count = 0)
: _current(static_cast<long>(count))
{
// Set the event if the initial count is zero.
if (_current == 0L)
_event.set();
}
// Decrements the event counter.
void signal() {
if(InterlockedDecrement(&_current) == 0L) {
_event.set();
}
}
// Increments the event counter.
void add_count() {
if(InterlockedIncrement(&_current) == 1L) {
_event.reset();
}
}
// Blocks the current context until the event is set.
void wait() {
_event.wait();
}
private:
// The current count.
volatile long _current;
// The event that is set when the counter reaches zero.
event _event;
// Disable copy constructor.
countdown_event(const countdown_event&);
// Disable assignment.
countdown_event const & operator=(countdown_event const&);
};
// Demonstrates how to set up a message network that performs a series of
// image processing operations on each JPEG image in the given directory and
// saves each altered image as a Windows bitmap.
void ProcessImages(const wstring& directory)
{
// Holds the number of active image processing operations and
// signals to the main thread that processing is complete.
countdown_event active(0);
// Maps Bitmap objects to their original file names.
map<Bitmap*, wstring> bitmap_file_names;
//
// Create the nodes of the network.
//
// Loads Bitmap objects from disk.
transformer<wstring, Bitmap*> load_bitmap(
[&](wstring file_name) -> Bitmap* {
Bitmap* bmp = new Bitmap(file_name.c_str());
if (bmp != nullptr)
bitmap_file_names.insert(make_pair(bmp, file_name));
return bmp;
}
);
// Holds loaded Bitmap objects.
unbounded_buffer<Bitmap*> loaded_bitmaps;
// Converts images that are authored by Tom to grayscale.
transformer<Bitmap*, Bitmap*> grayscale(
[](Bitmap* bmp) {
return Grayscale(bmp);
},
nullptr,
[](Bitmap* bmp) -> bool {
if (bmp == nullptr)
return false;
// Retrieve the artist name from metadata.
UINT size = bmp->GetPropertyItemSize(PropertyTagArtist);
if (size == 0)
// Image does not have the Artist property.
return false;
PropertyItem* artistProperty = (PropertyItem*) malloc(size);
bmp->GetPropertyItem(PropertyTagArtist, size, artistProperty);
string artist(reinterpret_cast<char*>(artistProperty->value));
free(artistProperty);
return (artist.find("Tom ") == 0);
}
);
// Removes the green and blue color components from images that have red as
// their dominant color.
transformer<Bitmap*, Bitmap*> colormask(
[](Bitmap* bmp) {
return ColorMask(bmp, 0x00ff0000);
},
nullptr,
[](Bitmap* bmp) -> bool {
if (bmp == nullptr)
return false;
return (GetColorDominance(bmp) == 0x00ff0000);
}
);
// Darkens the color of the provided Bitmap object.
transformer<Bitmap*, Bitmap*> darken([](Bitmap* bmp) {
return Darken(bmp, 50);
});
// Applies sepia toning to the remaining images.
transformer<Bitmap*, Bitmap*> sepiatone(
[](Bitmap* bmp) {
return Sepiatone(bmp);
},
nullptr,
[](Bitmap* bmp) -> bool { return bmp != nullptr; }
);
// Saves Bitmap objects to disk.
transformer<Bitmap*, Bitmap*> save_bitmap([&](Bitmap* bmp) -> Bitmap* {
// Replace the file extension with .bmp.
wstring file_name = bitmap_file_names[bmp];
file_name.replace(file_name.rfind(L'.') + 1, 3, L"bmp");
// Save the processed image.
CLSID bmpClsid;
GetEncoderClsid(L"image/bmp", &bmpClsid);
bmp->Save(file_name.c_str(), &bmpClsid);
return bmp;
});
// Deletes Bitmap objects.
transformer<Bitmap*, Bitmap*> delete_bitmap([](Bitmap* bmp) -> Bitmap* {
delete bmp;
return nullptr;
});
// Decrements the event counter.
call<Bitmap*> decrement([&](Bitmap* _) {
active.signal();
});
//
// Connect the network.
//
load_bitmap.link_target(&loaded_bitmaps);
loaded_bitmaps.link_target(&grayscale);
loaded_bitmaps.link_target(&colormask);
colormask.link_target(&darken);
loaded_bitmaps.link_target(&sepiatone);
loaded_bitmaps.link_target(&decrement);
grayscale.link_target(&save_bitmap);
darken.link_target(&save_bitmap);
sepiatone.link_target(&save_bitmap);
save_bitmap.link_target(&delete_bitmap);
delete_bitmap.link_target(&decrement);
// Traverse all files in the directory.
wstring searchPattern = directory;
searchPattern.append(L"\\*");
WIN32_FIND_DATA fileFindData;
HANDLE hFind = FindFirstFile(searchPattern.c_str(), &fileFindData);
if (hFind == INVALID_HANDLE_VALUE)
return;
do
{
if (!(fileFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
wstring file = fileFindData.cFileName;
// Process only JPEG files.
if (file.rfind(L".jpg") == file.length() - 4)
{
// Form the full path to the file.
wstring full_path(directory);
full_path.append(L"\\");
full_path.append(file);
// Increment the count of work items.
active.add_count();
// Send the path name to the network.
send(load_bitmap, full_path);
}
}
}
while (FindNextFile(hFind, &fileFindData) != 0);
FindClose(hFind);
// Wait for all operations to finish.
active.wait();
}
int wmain()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
// Initialize GDI+.
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
// Perform image processing.
// TODO: Change this path if necessary.
ProcessImages(L"C:\\Users\\Public\\Pictures\\Sample Pictures");
// Shutdown GDI+.
GdiplusShutdown(gdiplusToken);
}
La figura seguente mostra l'output di esempio. Ogni immagine di origine è sopra l'immagine modificata corrispondente.
Lighthouse
è creato da Tom Alphin e quindi viene convertito in scala di grigi. Chrysanthemum
, Desert
, Koala
e Tulips
hanno il rosso come colore dominante e quindi hanno i componenti di colore blu e verde rimossi e sono scuriti. Hydrangeas
, Jellyfish
e Penguins
corrispondono ai criteri predefiniti e pertanto sono sepia toned.
Compilazione del codice
Copiare il codice di esempio e incollarlo in un progetto di Visual Studio oppure incollarlo in un file denominato image-processing-network.cpp
e quindi eseguire il comando seguente in una finestra del prompt dei comandi di Visual Studio.
cl.exe /DUNICODE /EHsc image-processing-network.cpp /link gdiplus.lib