Пошаговое руководство. Создание сети обработки изображений

В этом документе показано, как создавать сеть асинхронных блоков сообщений, выполняющих обработку изображений.

В зависимости от свойств изображения сеть определяет, какие операции с ним необходимо выполнить. В этом примере для перемещения изображений по сети используется модель потока данных. В модели потока данных независимые компоненты программы взаимодействуют друг с другом посредством отправки сообщений. Когда компонент получает сообщение, он может выполнить какое-либо действие и передать результат этого действия другому компоненту. Сравните эту модель с моделью потока управления, в которой приложение управляет действиями программы с помощью структур управления, например условных операторов, циклов и так далее.

Сеть, основанная на потоке данных, создает конвейер задач. Каждый этап конвейера параллельно выполняет часть общей задачи. Это можно сравнить с линией сборки автомобилей. При продвижении автомобиля по линии сборки одна станция собирает раму, другая — устанавливает двигатель и так далее. Так как при этом можно собирать одновременно много автомобилей, линия сборки обеспечивает большую производительность, чем полная сборка автомобилей по одному.

Обязательные компоненты

Прежде чем начать выполнение этого пошагового руководства, необходимо ознакомиться со следующими документами.

Также перед освоением этого пошагового руководства рекомендуется разобраться в основах GDI+. Дополнительные сведения о GDI+ см. в разделе GDI+.

Подразделы

Это пошаговое руководство содержит следующие подразделы.

  • Определение функции обработки изображений

  • Создание сети обработки изображений

  • Полный код примера

Определение функции обработки изображений

В этом разделе демонстрируются необходимые функции, которые сеть обработки изображений использует для работы с изображениями, считываемыми с диска.

Следующие функции, GetRGB и MakeColor, извлекают и объединяют, соответственно, отдельные компоненты данного цвета.

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

Следующая функция, ProcessImage, вызывает данный объект std::function, чтобы преобразовать значение цвета каждого пикселя в объект GDI+ Bitmap. Функция ProcessImage использует алгоритм Concurrency::parallel_for, чтобы параллельно обрабатывать все ряды растрового изображения.

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

Следующие функции, Grayscale, Sepiatone, ColorMask и Darken, вызывают функцию ProcessImage, чтобы преобразовать значение цвета каждого пикселя в объект Bitmap. Все перечисленные функции используют лямбда-выражения, чтобы определить преобразование цвета одного пикселя.

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

Следующая функция, GetColorDominance, также вызывает функцию ProcessImage. При этом она не изменяет значение каждого цвета, а использует объекты Concurrency::combinable, чтобы вычислить, компонент какого цвета преобладает в изображении: красного, зеленого или синего.

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

Следующая функция, GetEncoderClsid, получает идентификатор класса для данного типа MIME кодировщика. Приложение использует эту функцию, чтобы получить кодировщик для растрового изображения.

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

[в начало]

Создание сети обработки изображений

В этом разделе описано, как создавать сеть асинхронных блоков сообщений, выполняющих обработку каждого изображения JPEG (JPG) в данном каталоге. Сеть выполняет следующие операции по обработке изображений.

  1. Все изображения, созданные пользователем Tom, преобразуются в оттенки серого.

  2. Во всех изображениях, в которых преобладает красный цвет, удаляются зеленый и синий компоненты, затем изображение затемняется.

  3. Ко всем остальным изображениям применяется тонирование сепией.

Сеть применяет только первую операцию обработки изображения, соответствующую одному из перечисленных условий. Например, если изображение создано пользователем Tom и в нем преобладает красный цвет, оно будет только преобразовано в оттенки серого.

После выполнения операции обработки изображения сеть сохраняет его на диск в виде файла растрового изображения (BMP).

Ниже показано, как создать функцию, реализующую сеть обработки изображений и применяющую эту сеть ко всем изображениям JPEG в данном каталоге.

Создание сети обработки изображений

  1. Создайте функцию ProcessImages, которая принимает имя каталога на диске.

    void ProcessImages(const wstring& directory)
    {
    }
    
  2. В функции ProcessImages создайте переменную countdown_event. Класс countdown_event показан далее в этом пошаговом руководстве.

    // Holds the number of active image processing operations and 
    // signals to the main thread that processing is complete.
    countdown_event active(0);
    
  3. Создайте объект std::map, связывающий объект Bitmap с исходным именем файла.

    // Maps Bitmap objects to their original file names.
    map<Bitmap*, wstring> bitmap_file_names;
    
  4. Добавьте следующий код, чтобы определить члены сети обработки изображений.

    //
    // 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. Добавьте следующий код, чтобы соединить сеть.

    //
    // 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. Добавьте следующий код, чтобы отправить в начало сети полный путь каждого файла JPEG в каталоге.

    // 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. Подождите, пока переменная countdown_event не достигнет нуля.

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

В следующей таблице описаны члены сети.

Элемент

Описание

load_bitmap

Объект Concurrency::transformer, загружающий объект Bitmap с диска и добавляющий запись в объект map, связывающую изображение с исходным именем файла.

loaded_bitmaps

Объект Concurrency::unbounded_buffer, отправляющий загруженные изображения фильтрам обработки изображений.

grayscale

Объект transformer, преобразующий в оттенки серого изображения, созданные пользователем Tom. Он использует метаданные изображения, чтобы определить создавшего его пользователя.

colormask

Объект transformer, удаляющий компоненты зеленого и синего цветов из изображений, в которых преобладает красный цвет.

darken

Объект transformer, затемняющий изображения, в которых преобладает красный цвет.

sepiatone

Объект transformer, применяющий тонирование сепией к изображениям, не созданным пользователем Tom и в которых не преобладает красный цвет.

save_bitmap

Объект transformer, сохраняющий обработанный объект image на диск в качестве растрового изображения. save_bitmap получает исходное имя файла из объекта map и изменяет расширение имени файла на BMP.

delete_bitmap

Объект transformer, освобождающий память для изображений.

decrement

Объект Concurrency::call, действующий как конечный узел сети. Он уменьшает объект countdown_event, чтобы сообщить главному приложению о том, что изображение обработано.

Буфер сообщений loaded_bitmaps важен, так как будучи объектом unbounded_buffer, он предлагает объекты Bitmap нескольким получателям. Когда целевой блок принимает объект Bitmap, объект unbounded_buffer не предлагает этот объект Bitmap другим целевым блокам. Поэтому важен порядок связывания объектов с объектом unbounded_buffer. Блоки сообщений grayscale, colormask и sepiatone используют фильтры, чтобы принимать только определенные объекты Bitmap. Буфер сообщений decrement является важным целевым объектом буфера сообщений loaded_bitmaps, так как он принимает все объекты Bitmap, отклоненные другими буферами сообщений. Объект unbounded_buffer необходим для распространения сообщений в определенном порядке. Поэтому объект unbounded_buffer блокируется до тех пор, пока к нему не будет привязан новый целевой блок, и принимает сообщение, не принятое ни одним текущим целевым блоком.

Если в приложении необходимо, чтобы сообщение обрабатывалось несколькими блоками сообщений, а не одним блоком, первым принявшим сообщение, можно использовать другой тип блока сообщений, например overwrite_buffer. Класс overwrite_buffer может содержать только одно сообщение, но он распространяет его всем целевым объектам.

На следующем рисунке показана сеть обработки изображений.

Сеть обработки изображений

Объект countdown_event в этом примере позволяет сети обработки изображений сообщать главному приложению о том, что все изображения обработаны. Класс countdown_event использует объект Concurrency::event, чтобы сообщить, что значение счетчика достигло нуля. Главное приложение увеличивает счетчик при каждой отправке имени файла в сеть. Конечный узел сети уменьшает счетчик после обработки каждого изображения. Когда главное приложение завершает проход заданного каталога, оно ожидает, пока объект countdown_event не сообщит, что его счетчик достиг нуля.

В следующем примере показан класс countdown_event:

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

[в начало]

Полный код примера

Ниже приведен полный пример кода. Функция wmain управляет библиотекой GDI+ и вызывает функцию ProcessImages, чтобы обработать файлы JPEG в каталоге Sample Pictures.

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

На следующем рисунке показан пример результатов. Исходные изображения расположены над соответствующими измененными изображениями.

Пример выходных данных для данного примера

Изображение Lighthouse создано пользователем Tom Alphin, поэтому оно преобразовано в оттенки серого. В изображениях Chrysanthemum, Desert, Koala и Tulips преобладает красный цвет, поэтому из них удалены компоненты синего и зеленого цветов, а затем они затемнены. Изображения Hydrangeas, Jellyfish и Penguins соответствуют критериям по умолчанию, поэтому тонированы сепией.

[в начало]

Компиляция кода

Скопируйте код примера и вставьте его в проект Visual Studio или в файл с именем image-processing-network.cpp, затем выполните в окне командной строки Visual Studio 2010 следующую команду.

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

См. также

Основные понятия

Пошаговые руководства по среде выполнения с параллелизмом

Журнал изменений

Дата

Журнал

Причина

Июнь 2010

Исправлена ошибка в рисунке.

Обратная связь от клиента.