Exemplarische Vorgehensweise: Erstellen eines Bildverarbeitungsnetzwerks

In diesem Dokument wird das Erstellen eines Netzwerks asynchroner Nachrichtenblöcke für die Bildverarbeitung veranschaulicht.

Das Netzwerk bestimmt anhand der Eigenschaften, welche Vorgänge für ein Bild ausgeführt werden. In diesem Beispiel wird das Datenflussmodell verwendet, um Bilder durch das Netzwerk zu leiten. Im Datenflussmodell kommunizieren unabhängige Komponenten eines Programms durch den Austausch von Nachrichten. Wenn eine Komponente eine Nachricht empfängt, kann sie eine Aktion ausführen und das Ergebnis an eine andere Komponente übergeben. Vergleichen Sie dies mit dem Ablaufsteuerungsmodell, in dem die Reihenfolge der Vorgänge in einem Programm von der Anwendung mithilfe von Steuerungsstrukturen wie Bedingungsanweisungen oder Schleifen kontrolliert wird.

In einem Netzwerk, das auf Datenfluss basiert, wird eine Pipeline von Aufgaben erstellt. Die Teile der Gesamtaufgabe werden in den einzelnen Pipelinephasen nebenläufig ausgeführt. Diese Vorgehensweise ist vergleichbar mit einem Fließband in der Automobilfertigung. Jedes Fahrzeug durchläuft eine Reihe von Arbeitsstationen, an einer wird die Karosserie zusammengebaut, an einer anderen der Motor eingesetzt usw. Wenn mehrere Fahrzeuge gleichzeitig zusammengebaut werden können, ist die Produktivität des Fließbands höher, als wenn nur jeweils ein Fahrzeug fertig gestellt werden kann.

Vorbereitungsmaßnahmen

Lesen Sie die folgenden Dokumente, bevor Sie mit dieser exemplarischen Vorgehensweise beginnen:

Es wird außerdem empfohlen, sich vor der Verwendung dieser exemplarischen Vorgehensweise ggf. mit den Grundlagen von GDI+ vertraut zu machen. Weitere Informationen zu GDI+ finden Sie unter GDI+.

Abschnitte

Diese exemplarische Vorgehensweise enthält folgende Abschnitte:

  • Definieren der Bildverarbeitungsfunktionen

  • Erstellen des Bildverarbeitungsnetzwerks

  • Vollständiges Beispiel

Definieren der Bildverarbeitungsfunktionen

In diesem Abschnitt werden die Unterstützungsfunktionen veranschaulicht, die vom Bildverarbeitungsnetzwerk zur Verarbeitung von Bildern von einem Datenträger verwendet werden.

Mit der GetRGB-Funktion und der MakeColor-Funktion werden die einzelnen Anteile der angegebenen Farbe extrahiert bzw. kombiniert.

// 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);
}

Mit der ProcessImage-Funktion wird das angegebene std::function-Objekt aufgerufen, um den Farbwert jedes Pixels in einem GDI+ Bitmap-Objekt umzuwandeln. Die ProcessImage-Funktion verarbeitet parallel die einzelnen Zeilen des Bitmaps mit dem Concurrency::parallel_for-Algorithmus.

// 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);
}

Mit den Funktionen Grayscale, Sepiatone, ColorMask und Darken wird die ProcessImage-Funktion aufgerufen, um den Farbwert jedes Pixels in ein Bitmap-Objekt umzuwandeln. Jede dieser Funktionen verwendet einen Lambda-Ausdruck zum Definieren der Farbtransformation eines Pixels.

// 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 ProcessImage-Funktion wird ebenfalls von der GetColorDominance-Funktion aufgerufen. Anstatt jedoch den Wert der einzelnen Farben zu ändern, berechnet diese Funktion jedoch mithilfe von Concurrency::combinable-Objekten, wann das Bild von einem roten, grünen oder blauen Farbanteil dominiert wird.

// 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;
}

Mit der GetEncoderClsid-Funktion wird der Klassenbezeichner für den angegebenen MIME-Typ eines Encoders abgerufen. Diese Funktion wird von der Anwendung verwendet, um den Encoder für ein 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
}

[Nach oben]

Erstellen des Bildverarbeitungsnetzwerks

In diesem Abschnitt wird beschrieben, wie ein Netzwerk von asynchronen Nachrichtenblöcken für die Verarbeitung aller Bilder vom Typ JPEG (JPG) in einem bestimmten Verzeichnis erstellt wird. Folgende Bildverarbeitungsvorgänge werden im Netzwerk ausgeführt:

  1. Jedes Bild, das von Tom erstellt wurde, wird in Graustufen konvertiert.

  2. In jedem Bild, in dem die Farbe Rot dominiert, werden die grünen und blauen Farbanteile entfernt, und das Bild wird dunkler gemacht.

  3. Auf alle anderen Bilder wird eine Sepiatönung angewendet.

Nur der erste Bildverarbeitungsvorgang, der einer dieser Bedingungen entspricht, wird vom Netzwerk ausgeführt. Ein Bild, das von Tom erstellt wurde und in dem die Farbe Rot dominiert, wird beispielsweise nur in Graustufen konvertiert.

Nachdem die einzelnen Bildverarbeitungsvorgänge vom Netzwerk ausgeführt wurden, werden die Bilder als Bitmap-Dateien (.bmp) auf dem Datenträger gespeichert.

In den folgenden Schritten wird veranschaulicht, wie eine Funktion zur Implementierung dieses Bildverarbeitungsnetzwerks erstellt und das Netzwerk auf alle Bilder vom Typ JPEG in einem angegebenen Verzeichnis angewendet werden kann.

So erstellen Sie das Bildverarbeitungsnetzwerk

  1. Erstellen Sie die ProcessImages-Funktion, die den Namen eines Verzeichnisses auf dem Datenträger übernimmt.

    void ProcessImages(const wstring& directory)
    {
    }
    
  2. Erstellen Sie in der in der ProcessImages-Funktion eine countdown_event-Variable. Die countdown_event-Klasse wird später in dieser exemplarischen Vorgehensweise beschrieben.

    // Holds the number of active image processing operations and 
    // signals to the main thread that processing is complete.
    countdown_event active(0);
    
  3. Erstellen Sie ein std::map-Objekt, um ein Bitmap-Objekt mit dem ursprünglichen Dateinamen zu verknüpfen.

    // Maps Bitmap objects to their original file names.
    map<Bitmap*, wstring> bitmap_file_names;
    
  4. 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();
    });
    
  5. Fügen Sie den folgenden Code hinzu, um eine Verbindung zum Netzwerk herzustellen.

    //
    // 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);
    
  6. Fügen Sie den folgenden Code hinzu, um den vollständigen Pfad jeder JPEG-Datei im Verzeichnis ab den Anfang des Netzwerks 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);
    
  7. Warten Sie, bis die Variable countdown_event den Wert 0 (null) erreicht hat.

    // Wait for all operations to finish.
    active.wait();
    

In der folgenden Tabelle werden die Member des Netzwerks beschrieben.

Member

Beschreibung

load_bitmap

Mit einem Concurrency::transformer-Objekt wird ein Bitmap-Objekt vom Datenträger geladen, und dem map-Objekt wird ein Eintrag hinzugefügt, um das Bild mit dem ursprünglichen Dateinamen zu verknüpfen.

loaded_bitmaps

Mit einem Concurrency::unbounded_buffer-Objekt werden die geladenen Bilder an die Bildverarbeitungsfilter gesendet.

grayscale

Mit einem transformer-Objekt werden die Bilder, die von Tom erstellt wurden, in Graustufen konvertiert werden. Der Autor des Bildes wird anhand der zugehörigen Metadaten ermittelt.

colormask

Mit einem transformer-Objekt werden die grünen und blauen Farbanteile auf Bildern entfernt, in denen die Farbe Rot dominiert.

darken

Mit einem transformer-Objekt werden die Bilder dunkler gemacht, in denen die Farbe Rot dominiert.

sepiatone

Mit einem transformer-Objekt wird eine Sepiatönung auf alle Bilder angewendet, die nicht von Tom erstellt wurden und in denen Rot nicht die dominierende Farbe ist.

save_bitmap

Mit einem transformer-Objekt wird das verarbeitete image als Bitmap auf einem Datenträger gespeichert. Mit save_bitmap wird der ursprüngliche Dateiname vom map-Objekt abgerufen, und die Dateinamenerweiterung wird in BMP geändert.

delete_bitmap

Mit einem transformer-Objekt wird der Speicher für die Bilder freigegeben.

decrement

Ein Concurrency::call-Objekt fungiert als Terminalknoten im Netzwerk. Es verringert den Wert des countdown_event-Objekts, um gegenüber der Hauptanwendung anzugeben, dass ein Bild verarbeitet wurde.

Der Nachrichtenpuffer loaded_bitmaps ist wichtig, da er als unbounded_buffer-Objekt Bitmap-Objekte für mehrere Empfänger bereitstellt. Wenn ein Bitmap-Objekt einen Zielblock akzeptiert, wird das Bitmap-Objekt vom unbounded_buffer-Objekt nicht mehr für andere Ziele bereitgestellt. Aus diesem Grund ist die Reihenfolge, in der die Objekte mit einem unbounded_buffer-Objekt verknüpft werden, von Bedeutung. Die Nachrichtenblöcke grayscale, colormask und sepiatone verwenden jeweils einen Filter, um nur bestimmte Bitmap-Objekte zu akzeptieren. Der Meldungspuffer decrement ist ein wichtiges Ziel des Nachrichtenpuffers loaded_bitmaps, da er alle Bitmap-Objekte akzeptiert, die von den anderen Nachrichtenpuffer abgelehnt wurden. Ein unbounded_buffer-Objekt ist erforderlich, um Nachrichten der Reihenfolge nach zu verteilen. Ein unbounded_buffer-Objekt wird daher blockiert, bis ein neuer Zielblock damit verknüpft wird und die Nachricht akzeptiert, wenn diese nicht von einem aktuellen Zielblock akzeptiert wird.

Wenn die Nachricht in Ihrer Anwendung von mehreren Nachrichtenblöcken verarbeitet werden muss und nicht nur von dem Block, der sie zuerst akzeptiert hat, können Sie einen anderen Nachrichtenblocktyp wie overwrite_buffer verwenden. Die overwrite_buffer-Klasse enthält jeweils nur eine Nachricht, gibt diese jedoch an alle entsprechenden Ziele weiter.

In der folgenden Abbildung wird das Bildverarbeitungsnetzwerk veranschaulicht:

Bildverarbeitungsnetzwerk

Das countdown_event-Objekt in diesem Beispiel ermöglicht dem Bildverarbeitungsnetzwerk, die Hauptanwendung darüber zu informieren, dass alle Bilder verarbeitet wurden. Die countdown_event-Klasse gibt mit einem Concurrency::event-Objekt an, wenn der Zähler den Wert 0 (null) erreicht hat. Jedes Mal, wenn ein Dateiname an das Netzwerk gesendet wird, wird der Zähler von der Hauptanwendung inkrementiert. Nachdem das Bild verarbeitet wurde, wird der Zähler vom Terminalknoten des Netzwerks dekrementiert. Sobald das angegebene Verzeichnis von der Hauptanwendung durchlaufen wurde, wartet diese auf ein Signal des countdown_event-Objekt, dass der Zähler den Wert 0 (null) erreicht hat.

Im folgenden Beispiel ist die countdown_event-Klasse dargestellt:

// 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&);
};

[Nach oben]

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 Verzeichnis Beispielbilder 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);
}

In der folgenden Abbildung wird eine Beispielausgabe veranschaulicht. Jedes Quellbild befindet sich über dem entsprechenden geänderten Bild.

Beispielausgabe

Lighthouse wurde von Tom Alphin erstellt und wird daher in Graustufen konvertiert. Bei Chrysanthemum, Desert, Koala und Tulips dominiert die Farbe Rot. Daher werden die blauen und grünen Farbanteile entfernt, und die Bilder werden dunkler gemacht. Die Bilder Hydrangeas, Jellyfish und Penguins entsprechen den Standardkriterien und werden daher mit einer Sepiatönung versehen.

[Nach oben]

Kompilieren des Codes

Kopieren Sie den Beispielcode, und fügen Sie ihn in ein Visual Studio-Projekt ein, oder fügen Sie ihn in eine Datei mit dem Namen image-processing-network.cpp ein, und führen Sie dann den folgenden Befehl in einem Visual Studio 2010-Eingabeaufforderungsfenster aus.

cl.exe /DUNICODE /EHsc image-processing-network.cpp /link gdiplus.lib

Siehe auch

Konzepte

Exemplarische Vorgehensweisen für die Concurrency Runtime

Änderungsprotokoll

Datum

Versionsgeschichte

Grund

Juni 2010

Fehler in der Abbildung korrigiert.

Kundenfeedback.