Exemplarische Vorgehensweise: Erstellen eines Bildverarbeitungsnetzwerks
In diesem Dokument wird veranschaulicht, wie Sie ein Netzwerk asynchroner Nachrichtenblöcke erstellen, die Bildverarbeitung ausführen.
Das Netzwerk bestimmt, welche Vorgänge auf der Grundlage ihrer Merkmale auf einem Image ausgeführt werden sollen. In diesem Beispiel wird das Datenflussmodell verwendet, um Bilder über das Netzwerk weiterzuleiten. Im Datenflussmodell kommunizieren unabhängige Komponenten eines Programms durch Senden von Nachrichten miteinander. Wenn eine Komponente eine Nachricht empfängt, kann sie eine Aktion ausführen und dann das Ergebnis dieser Aktion an eine andere Komponente übergeben. Vergleichen Sie dies mit dem Steuerungsflussmodell , in dem eine Anwendung Kontrollstrukturen verwendet, z. B. bedingte Anweisungen, Schleifen usw., um die Reihenfolge der Vorgänge in einem Programm zu steuern.
Ein Netzwerk, das auf dem Datenfluss basiert, erstellt eine Pipeline von Aufgaben. Jede Phase der Pipeline führt gleichzeitig einen Teil des Gesamtvorgangs aus. Eine Analogie hierzu ist eine Fertigungsstraße eines Fahrzeugherstellers. Wenn jedes Fahrzeug durch die Montagelinie geht, montiert eine Station den Rahmen, ein anderes installiert den Motor usw. Durch die gleichzeitige Montage mehrerer Fahrzeuge bietet die Montagelinie einen besseren Durchsatz als die Montage kompletter Fahrzeuge gleichzeitig.
Voraussetzungen
Lesen Sie die folgenden Dokumente, bevor Sie mit dieser exemplarischen Vorgehensweise beginnen:
Außerdem wird empfohlen, die Grundlagen von GDI+ zu verstehen, bevor Sie mit dieser exemplarischen Vorgehensweise beginnen.
Abschnitte
Diese exemplarische Vorgehensweise enthält folgende Abschnitte:
Definieren der Bildverarbeitungsfunktionalität
In diesem Abschnitt werden die Unterstützungsfunktionen gezeigt, die das Bildverarbeitungsnetzwerk verwendet, um mit Bildern zu arbeiten, die vom Datenträger gelesen werden.
Die folgenden Funktionen GetRGB
und MakeColor
, extrahieren und kombinieren die einzelnen Komponenten der angegebenen Farbe, bzw. kombinieren.
// 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);
}
Die folgende Funktion ruft das angegebene std::function-Objekt auf, ProcessImage
um den Farbwert jedes Pixels in einem GDI+ Bitmap-Objekt zu transformieren. Die ProcessImage
Funktion verwendet die Parallelität::p arallel_for-Algorithmus , um jede Zeile der Bitmap parallel zu verarbeiten.
// 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);
}
Die folgenden Funktionen, Grayscale
, , Sepiatone
und ColorMask
Darken
, rufen die ProcessImage
Funktion auf, um den Farbwert jedes Pixels in einem Bitmap
Objekt zu transformieren. Jede dieser Funktionen verwendet einen Lambda-Ausdruck, um die Farbtransformation eines Pixels zu definieren.
// 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;
}
Die folgende Funktion GetColorDominance
ruft auch die ProcessImage
Funktion auf. Anstatt jedoch den Wert jeder Farbe zu ändern, verwendet diese Funktion parallele Objekte::kombinationsfähige Objekte, um zu berechnen, ob die Rot-, Grün- oder Blaufarbenkomponente das Bild dominiert.
// 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;
}
Die folgende Funktion GetEncoderClsid
ruft den Klassenbezeichner für den angegebenen MIME-Typ eines Encoders ab. Die Anwendung verwendet diese Funktion, um den Encoder für eine Bitmap abzurufen.
// 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
}
Erstellen des Bildverarbeitungsnetzwerks
In diesem Abschnitt wird beschrieben, wie Sie ein Netzwerk asynchroner Nachrichtenblöcke erstellen, die die Bildverarbeitung für jedes JPEG-Bild (.jpg) in einem bestimmten Verzeichnis durchführen. Das Netzwerk führt die folgenden Bildverarbeitungsvorgänge aus:
Konvertieren Sie für jedes Bild, das von Tom erstellt wurde, in Graustufen.
Entfernen Sie für jedes Bild, das rot als dominante Farbe hat, die grünen und blauen Komponenten, und dunklen sie dann ab.
Wenden Sie für jedes andere Bild sepia toning an.
Das Netzwerk wendet nur den ersten Bildverarbeitungsvorgang an, der einer dieser Bedingungen entspricht. Wenn beispielsweise ein Bild von Tom erstellt wird und rot als dominante Farbe aufweist, wird das Bild nur in Graustufen konvertiert.
Nachdem das Netzwerk die einzelnen Bildverarbeitungsvorgänge ausgeführt hat, speichert es das Bild als Bitmapdatei (.bmp).
Die folgenden Schritte zeigen, wie Sie eine Funktion erstellen, die dieses Bildverarbeitungsnetzwerk implementiert, und wendet dieses Netzwerk auf jedes JPEG-Bild in einem bestimmten Verzeichnis an.
So erstellen Sie das Bildverarbeitungsnetzwerk
Erstellen Sie eine Funktion,
ProcessImages
die den Namen eines Verzeichnisses auf dem Datenträger verwendet.void ProcessImages(const wstring& directory) { }
Erstellen Sie in der
ProcessImages
Funktion einecountdown_event
Variable. Diecountdown_event
Klasse wird später in dieser exemplarischen Vorgehensweise gezeigt.// Holds the number of active image processing operations and // signals to the main thread that processing is complete. countdown_event active(0);
Erstellen Sie ein std::map-Objekt , das einem
Bitmap
Objekt seinen ursprünglichen Dateinamen zuordnet.// Maps Bitmap objects to their original file names. map<Bitmap*, wstring> bitmap_file_names;
Fügen Sie den folgenden Code hinzu, um die Member des Bildverarbeitungsnetzwerks zu definieren.
// // 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(); });
Fügen Sie den folgenden Code hinzu, um das Netzwerk zu verbinden.
// // 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);
Fügen Sie den folgenden Code hinzu, um an den Kopf des Netzwerks den vollständigen Pfad jeder JPEG-Datei im Verzeichnis zu senden.
// 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);
Warten Sie, bis die
countdown_event
Variable null erreicht.// Wait for all operations to finish. active.wait();
In der folgenden Tabelle werden die Member des Netzwerks beschrieben.
Member | Beschreibung |
---|---|
load_bitmap |
Ein Parallelitätsobjekt::Transformer-Objekt , das ein Bitmap Objekt vom Datenträger lädt, und fügt dem Objekt einen Eintrag hinzu map , um das Bild dem ursprünglichen Dateinamen zuzuordnen. |
loaded_bitmaps |
Eine Parallelität::unbounded_buffer Objekt, das die geladenen Bilder an die Bildverarbeitungsfilter sendet. |
grayscale |
Ein transformer Objekt, das Bilder konvertiert, die von Tom erstellt werden, in Graustufen. Es verwendet die Metadaten des Bilds, um seinen Autor zu bestimmen. |
colormask |
Ein transformer Objekt, das die Grün- und Blaufarbenkomponenten aus Bildern entfernt, die rot als dominante Farbe aufweisen. |
darken |
Ein transformer Objekt, das Bilder abdunkelt, die rot als dominante Farbe aufweisen. |
sepiatone |
Ein transformer Objekt, das Sepia-Toning auf Bilder anwendet, die nicht von Tom verfasst wurden und nicht überwiegend rot sind. |
save_bitmap |
Ein transformer Objekt, das den verarbeiteten image Datenträger als Bitmap speichert. save_bitmap ruft den ursprünglichen Dateinamen aus dem map Objekt ab und ändert die Dateinamenerweiterung in .bmp. |
delete_bitmap |
Ein transformer Objekt, das den Speicher für die Bilder freigibt. |
decrement |
Ein Parallelitätsobjekt::call-Objekt , das als Terminalknoten im Netzwerk fungiert. Es erhöht das countdown_event Objekt, um der Standard Anwendung zu signalisieren, dass ein Bild verarbeitet wurde. |
Der loaded_bitmaps
Nachrichtenpuffer ist wichtig, da er als unbounded_buffer
Objekt Objekte für mehrere Empfänger bietet Bitmap
. Wenn ein Zielblock ein Bitmap
Objekt akzeptiert, bietet Bitmap
das Objekt dieses unbounded_buffer
Objekt keinem anderen Ziel an. Daher ist die Reihenfolge, in der Sie Objekte mit einem unbounded_buffer
Objekt verknüpfen, wichtig. Mit den grayscale
Blöcken , colormask
und sepiatone
Nachrichten wird jeweils ein Filter verwendet, um nur bestimmte Bitmap
Objekte zu akzeptieren. Der decrement
Nachrichtenpuffer ist ein wichtiges Ziel des loaded_bitmaps
Nachrichtenpuffers, da er alle Bitmap
Objekte akzeptiert, die von den anderen Nachrichtenpuffern abgelehnt werden. Ein unbounded_buffer
Objekt ist erforderlich, um Nachrichten in der reihenfolge zu verteilen. Daher blockiert ein unbounded_buffer
Objekt, bis ein neuer Zielblock damit verknüpft ist, und akzeptiert die Nachricht, wenn keine aktuelle Zielsperre diese Nachricht akzeptiert.
Wenn Ihre Anwendung erfordert, dass mehrere Nachrichtenblöcke die Nachricht verarbeiten, anstatt nur den einen Nachrichtenblock, der die Nachricht zuerst akzeptiert, können Sie einen anderen Nachrichtenblocktyp verwenden, z overwrite_buffer
. B. . . Die overwrite_buffer
Klasse enthält jeweils eine Nachricht, aber sie verteilt diese Nachricht an jedes seiner Ziele.
Die folgende Abbildung zeigt das Bildverarbeitungsnetzwerk:
Das countdown_event
Objekt in diesem Beispiel ermöglicht es dem Bildverarbeitungsnetzwerk, die Standard Anwendung zu informieren, wenn alle Bilder verarbeitet wurden. Die countdown_event
Klasse verwendet ein Parallelitätsobjekt::event , um zu signalisieren, wenn ein Zählerwert null erreicht. Die Standard Anwendung erhöht den Zähler jedes Mal, wenn er einen Dateinamen an das Netzwerk sendet. Der Terminalknoten des Netzwerks erhöht den Zähler, nachdem jedes Bild verarbeitet wurde. Nachdem die Standard Anwendung das angegebene Verzeichnis durchlaufen hat, wartet sie, bis das countdown_event
Objekt signalisiert, dass der Zähler null erreicht hat.
Das folgende Beispiel zeigt die countdown_event
Klasse:
// 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&);
};
Vollständiges Beispiel
Der folgende Code veranschaulicht das vollständige Beispiel. Die wmain
Funktion verwaltet die GDI+-Bibliothek und ruft die ProcessImages
Funktion auf, um die JPEG-Dateien im Sample Pictures
Verzeichnis zu verarbeiten.
// 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);
}
Die folgende Abbildung zeigt die Beispielausgabe. Jedes Quellbild befindet sich über dem entsprechenden geänderten Bild.
Lighthouse
wird von Tom Alphin verfasst und daher in Graustufen konvertiert. Chrysanthemum
, Desert
, Koala
und Tulips
haben Rot als dominante Farbe und daher werden die blauen und grünen Farbkomponenten entfernt und abgedunkelt. Hydrangeas
, Jellyfish
und Penguins
entsprechen sie den Standardkriterien und sind daher sepia toned.
Kompilieren des Codes
Kopieren Sie den Beispielcode, fügen Sie ihn in ein Visual Studio-Projekt ein, oder fügen Sie ihn in eine Datei ein, die benannt image-processing-network.cpp
ist, und führen Sie dann den folgenden Befehl in einem Visual Studio-Eingabeaufforderungsfenster aus.
cl.exe /DUNICODE /EHsc image-processing-network.cpp /link gdiplus.lib