Procédure pas à pas : création d'un réseau de traitement d'image
Ce document montre comment créer un réseau de blocs de messages asynchrones qui effectuent un traitement d’image.
Le réseau détermine les opérations à effectuer sur une image en fonction de ses caractéristiques. Cet exemple utilise le modèle de flux de données pour router des images via le réseau. Dans le modèle de flux de données, les composants indépendants d’un programme communiquent entre eux en envoyant des messages. Lorsqu’un composant reçoit un message, il peut effectuer une action, puis transmettre le résultat de cette action à un autre composant. Comparez cela au modèle de flux de contrôle, dans lequel une application utilise des structures de contrôle, par exemple des instructions conditionnelles, des boucles, et ainsi de suite, pour contrôler l’ordre des opérations dans un programme.
Un réseau basé sur le flux de données crée un pipeline de tâches. Chaque étape du pipeline effectue simultanément une partie de la tâche globale. Ce processus s'apparente à une chaîne de montage en construction automobile. À mesure que chaque véhicule passe par la ligne d’assemblage, une station assemble le cadre, une autre installe le moteur, et ainsi de suite. En permettant à plusieurs véhicules d’être assemblés simultanément, la ligne d’assemblage offre un meilleur débit que l’assemblage de véhicules complets un par un.
Prérequis
Lisez les documents suivants avant de commencer cette procédure pas à pas :
Nous vous recommandons également de comprendre les principes de base de GDI+ avant de commencer cette procédure pas à pas.
Sections
Cette procédure pas à pas contient les sections suivantes :
Définition de la fonctionnalité de traitement d’images
Cette section présente les fonctions de prise en charge utilisées par le réseau de traitement d’images pour travailler avec des images lues à partir du disque.
Les fonctions suivantes etMakeColor
, respectivement, GetRGB
extraient et combinent les composants individuels de la couleur donnée.
// 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 fonction suivante appelle ProcessImage
l’objet std ::function donné pour transformer la valeur de couleur de chaque pixel dans un objet Bitmap GDI+. La ProcessImage
fonction utilise l’algorithme concurrency ::p arallel_for pour traiter chaque ligne de la bitmap en parallèle.
// 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);
}
Les fonctions suivantes, , Grayscale
, ColorMask
Sepiatone
, et , appellent Darken
la ProcessImage
fonction pour transformer la valeur de couleur de chaque pixel dans un Bitmap
objet. Chacune de ces fonctions utilise une expression lambda pour définir la transformation de couleur d’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 fonction suivante, GetColorDominance
appelle également la ProcessImage
fonction. Toutefois, au lieu de modifier la valeur de chaque couleur, cette fonction utilise des objets concurrency ::combinables pour calculer si le composant rouge, vert ou bleu domine l’image.
// 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 fonction suivante récupère GetEncoderClsid
l’identificateur de classe pour le type MIME donné d’un encodeur. L’application utilise cette fonction pour récupérer l’encodeur d’une 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
}
[Haut]
Création du réseau de traitement d’images
Cette section explique comment créer un réseau de blocs de messages asynchrones qui effectuent le traitement d’images sur chaque image JPEG (.jpg) dans un répertoire donné. Le réseau effectue les opérations de traitement d’images suivantes :
Pour toute image créée par Tom, convertissez en nuances de gris.
Pour toute image qui a le rouge comme couleur dominante, supprimez les composants verts et bleus, puis assombrissez-le.
Pour toute autre image, appliquez la sépia.
Le réseau applique uniquement la première opération de traitement d’images qui correspond à l’une de ces conditions. Par exemple, si une image est créée par Tom et qu’elle a rouge comme couleur dominante, l’image est convertie uniquement en nuances de gris.
Une fois que le réseau effectue chaque opération de traitement d’image, il enregistre l’image sur le disque sous la forme d’un fichier bitmap (.bmp).
Les étapes suivantes montrent comment créer une fonction qui implémente ce réseau de traitement d’images et applique ce réseau à chaque image JPEG dans un répertoire donné.
Pour créer le réseau de traitement d’images
Créez une fonction,
ProcessImages
qui prend le nom d’un répertoire sur le disque.void ProcessImages(const wstring& directory) { }
Dans la
ProcessImages
fonction, créez unecountdown_event
variable. Lacountdown_event
classe est présentée plus loin dans cette procédure pas à pas.// Holds the number of active image processing operations and // signals to the main thread that processing is complete. countdown_event active(0);
Créez un objet std ::map qui associe un
Bitmap
objet à son nom de fichier d’origine.// Maps Bitmap objects to their original file names. map<Bitmap*, wstring> bitmap_file_names;
Ajoutez le code suivant pour définir les membres du réseau de traitement d’images.
// // 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(); });
Ajoutez le code suivant pour connecter le réseau.
// // 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);
Ajoutez le code suivant pour envoyer à la tête du réseau le chemin d’accès complet de chaque fichier JPEG dans le répertoire.
// 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);
Attendez que la
countdown_event
variable atteigne zéro.// Wait for all operations to finish. active.wait();
Le tableau ci-dessous décrit les membres du réseau.
Membre | Description |
---|---|
load_bitmap |
Objet concurrency ::transformer qui charge un Bitmap objet à partir du disque et ajoute une entrée à l’objet map pour associer l’image à son nom de fichier d’origine. |
loaded_bitmaps |
Objet concurrency ::unbounded_buffer qui envoie les images chargées aux filtres de traitement d’images. |
grayscale |
Objet transformer qui convertit les images créées par Tom en nuances de gris. Il utilise les métadonnées de l’image pour déterminer son auteur. |
colormask |
Objet transformer qui supprime les composants de couleur verte et bleue des images qui ont le rouge comme couleur dominante. |
darken |
Objet transformer qui assombrit les images qui ont rouge comme couleur dominante. |
sepiatone |
Objet transformer qui applique la sépia à des images qui ne sont pas créées par Tom et qui ne sont pas principalement rouges. |
save_bitmap |
Objet transformer qui enregistre le traitement image sur le disque sous forme de bitmap. save_bitmap récupère le nom de fichier d’origine de l’objet map et modifie son extension de nom de fichier en .bmp. |
delete_bitmap |
Objet transformer qui libère la mémoire des images. |
decrement |
Objet concurrency ::call qui agit comme nœud de terminal dans le réseau. Il décrémente l’objet countdown_event pour signaler à l’application principale qu’une image a été traitée. |
La mémoire tampon de loaded_bitmaps
message est importante, car, en tant qu’objet unbounded_buffer
, elle offre des Bitmap
objets à plusieurs récepteurs. Lorsqu’un bloc cible accepte un Bitmap
objet, l’objet unbounded_buffer
n’offre pas cet Bitmap
objet à d’autres cibles. Par conséquent, l’ordre dans lequel vous liez des objets à un unbounded_buffer
objet est important. Les grayscale
blocs , colormask
et sepiatone
les messages utilisent chacun un filtre pour accepter uniquement certains Bitmap
objets. La decrement
mémoire tampon de message est une cible importante de la loaded_bitmaps
mémoire tampon de message, car elle accepte tous les Bitmap
objets rejetés par les autres mémoires tampons de message. Un unbounded_buffer
objet est requis pour propager les messages dans l’ordre. Par conséquent, un unbounded_buffer
objet bloque jusqu’à ce qu’un nouveau bloc cible soit lié et accepte le message si aucun bloc cible actuel n’accepte ce message.
Si votre application nécessite que plusieurs blocs de messages traitent le message, au lieu d’un seul bloc de message qui accepte d’abord le message, vous pouvez utiliser un autre type de bloc de message, tel que overwrite_buffer
. La overwrite_buffer
classe contient un message à la fois, mais elle propage ce message à chacune de ses cibles.
L’illustration suivante montre le réseau de traitement des images :
L’objet countdown_event
de cet exemple permet au réseau de traitement d’images d’informer l’application principale lorsque toutes les images ont été traitées. La countdown_event
classe utilise un objet concurrency ::event pour signaler lorsqu’une valeur de compteur atteint zéro. L’application principale incrémente le compteur chaque fois qu’il envoie un nom de fichier au réseau. Le nœud terminal du réseau décrémente le compteur une fois que chaque image a été traitée. Une fois que l’application principale traverse le répertoire spécifié, elle attend que l’objet countdown_event
indique que son compteur a atteint zéro.
L’exemple suivant montre 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&);
};
[Haut]
Exemple complet
L'exemple de code suivant illustre l'exemple complet. La wmain
fonction gère la bibliothèque GDI+ et appelle la ProcessImages
fonction pour traiter les fichiers JPEG dans le Sample Pictures
répertoire.
// 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);
}
L’illustration suivante montre l’exemple de sortie. Chaque image source est au-dessus de son image modifiée correspondante.
Lighthouse
est créé par Tom Alphin et est donc converti en nuances de gris. Chrysanthemum
, , Desert
Koala
et Tulips
ont rouge comme couleur dominante et ont donc les composants de couleur bleu et vert supprimés et sont foncés. Hydrangeas
, Jellyfish
et Penguins
correspondent aux critères par défaut et sont donc sépia toned.
[Haut]
Compilation du code
Copiez l’exemple de code et collez-le dans un projet Visual Studio, ou collez-le dans un fichier nommé image-processing-network.cpp
, puis exécutez la commande suivante dans une fenêtre d’invite de commandes Visual Studio.
cl.exe /DUNICODE /EHsc image-processing-network.cpp /link gdiplus.lib
Voir aussi
Procédures pas à pas relatives au runtime d’accès concurrentiel
Commentaires
https://aka.ms/ContentUserFeedback.
Prochainement : Tout au long de l'année 2024, nous supprimerons progressivement les GitHub Issues en tant que mécanisme de retour d'information pour le contenu et nous les remplacerons par un nouveau système de retour d'information. Pour plus d’informations, voir:Soumettre et afficher des commentaires pour