Instruções passo a passo: criando uma rede de processamento de imagem

Este documento demonstra como criar uma rede de blocos de mensagens assíncronas que executam o processamento de imagens.

A rede determina quais operações executar em uma imagem com base em suas características. Esse exemplo usa o modelo de fluxo de dados para encaminhar imagens por meio da rede. No modelo de fluxo de dados, componentes independentes de um programa se comunicam uns com os outros enviando mensagens. Quando um componente recebe uma mensagem, ele pode executar uma ação e, depois, passar o resultado dessa ação para outro componente. Compare isso com o modelo de fluxo de controle, em que um aplicativo usa estruturas de controle, por exemplo, instruções condicionais, loops e assim por diante, para controlar a ordem das operações em um programa.

Uma rede baseada no fluxo de dados cria um pipeline de tarefas. Cada estágio do pipeline executa simultaneamente parte da tarefa geral. Como analogia, podemos usar uma linha de montagem de automóveis. À medida que os veículos passam por ela, uma estação monta a carroceria, outra instala o motor e assim por diante. Ao permitir que vários veículos sejam montados ao mesmo tempo, a linha de produção oferece taxa de transferência superior se comparada com a montagem de um veículo completo por vez.

Pré-requisitos

Leia os seguintes documentos antes de iniciar este passo a passo:

Também recomendamos que você entenda as noções básicas de GDI+ antes de iniciar este passo a passo.

Seções

Este passo a passo contém as seguintes seções:

Definir a funcionalidade de processamento de imagem

Esta seção mostra as funções de suporte que a rede de processamento de imagens usa para trabalhar com imagens lidas do disco.

As funções a seguir, GetRGB e MakeColor, extraem e combinam os componentes individuais da cor fornecida, respectivamente.

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

A função a seguir, ProcessImage, chama o objeto std::function fornecido para transformar o valor de cor de cada pixel em um objeto Bitmap GDI+. A ProcessImage função usa o algoritmo concurrency::p arallel_for para processar cada linha do bitmap em paralelo.

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

As funções a seguir, Grayscale, Sepiatonee ColorMaskDarken, chamam a função ProcessImage para transformar o valor de cor de cada pixel em um objeto Bitmap. Cada uma dessas funções usa uma expressão lambda para definir a transformação de cores de um 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;
}

A função a seguir,GetColorDominance, também chama a função ProcessImage. No entanto, em vez de alterar o valor de cada cor, essa função usa objetos simultâneos::combináveis para calcular se o componente de cor vermelho, verde ou azul domina a imagem.

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

A função a seguir,GetEncoderClsid, recupera o identificador de classe para o tipo MIME fornecido de um codificador. O aplicativo usa essa função para recuperar o codificador para um 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
}

[Parte superior]

Criar a rede de processamento de imagem

Esta seção descreve como criar uma rede de blocos de mensagem assíncronas que executam o processamento de imagens em cada imagem JPEG (.jpg) em um determinado diretório. A rede executa as seguintes operações de processamento de imagem:

  1. Para qualquer imagem que seja criada por Tom, converta em escala de cinza.

  2. Para qualquer imagem que tenha vermelho como a cor dominante, remova os componentes verde e azul e, em seguida, escureça-a.

  3. Para qualquer outra imagem, aplique o tom sépia.

A rede aplica apenas a primeira operação de processamento de imagem que corresponde a uma dessas condições. Por exemplo, se uma imagem for criada por Tom e tiver vermelho como cor dominante, a imagem será convertida apenas em escala de cinza.

Depois que a rede executa cada operação de processamento de imagens, ela salva a imagem no disco como um arquivo bitmap (.bmp).

As etapas a seguir mostram como criar uma função que implementa essa rede de processamento de imagens e aplica essa rede a cada imagem JPEG em um determinado diretório.

Para criar a rede de processamento de imagem

  1. Crie uma função, ProcessImages, que leva o nome de um diretório no disco.

    void ProcessImages(const wstring& directory)
    {
    }
    
  2. Na função ProcessImages, crie uma variável countdown_event. A classe countdown_event é exibida posteriormente neste passo a passo.

    // Holds the number of active image processing operations and 
    // signals to the main thread that processing is complete.
    countdown_event active(0);
    
  3. Crie um objeto std::map que associe um objeto Bitmap ao nome de arquivo original.

    // Maps Bitmap objects to their original file names.
    map<Bitmap*, wstring> bitmap_file_names;
    
  4. Adicione o código a seguir para definir os membros da rede de processamento de imagem.

     //
     // 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. Adicione o seguinte código para se conectar à rede.

    //
    // 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. Adicione seguinte código para enviar ao cabeçalho da rede o caminho completo de cada arquivo JPEG no diretório.

    // 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. Aguarde até que a variável countdown_event atinja zero.

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

A tabela a seguir descreve os membros da rede.

Membro Descrição
load_bitmap Um objeto concurrency::transformer que carrega um objeto Bitmap do disco e adiciona uma entrada ao objeto map para associar a imagem ao nome de arquivo original.
loaded_bitmaps Um objeto concurrency::unbounded_buffer que envia as imagens carregadas para os filtros de processamento de imagem.
grayscale Um objeto transformer que converte imagens criadas por Tom em escala de cinza. Ele usa os metadados da imagem para determinar seu autor.
colormask Um objeto transformer que remove os componentes de cor verde e azul de imagens que têm vermelho como a cor dominante.
darken Um objeto transformer que escurece imagens que têm vermelho como a cor dominante.
sepiatone Um objeto transformer que aplica o tom sépia a imagens que não são criadas por Tom e não são predominantemente vermelhas.
save_bitmap Um objeto transformer que salva o image processado no disco como um bitmap. save_bitmap recupera o nome do arquivo original do objeto map e altera sua extensão de nome de arquivo para .bmp.
delete_bitmap Um objeto transformer que libera a memória das imagens.
decrement Um objeto concurrency::call que atua como o nó de terminal na rede. Ele decrementa o objeto countdown_event para sinalizar para o aplicativo principal que uma imagem foi processada.

O buffer de mensagem loaded_bitmaps é importante porque, como um objeto unbounded_buffer, ele oferece objetos Bitmap para vários receptores. Quando um bloco de destino aceita um objeto Bitmap, o objeto unbounded_buffer não oferece esse objeto Bitmap a nenhum outro destino. Portanto, a ordem na qual você vincula objetos a um objeto unbounded_buffer é importante. Cada um dos blocos de mensagem grayscale, colormask e sepiatone usa um filtro para aceitar apenas determinados objetos Bitmap. O buffer de mensagem decrement é um destino importante do buffer de mensagem loaded_bitmaps porque aceita todos os objetos Bitmap rejeitados pelos outros buffers de mensagem. Um objeto unbounded_buffer é necessário para propagar mensagens em ordem. Portanto, um objeto unbounded_buffer é bloqueado até que um novo bloco de destino seja vinculado a ele e aceite a mensagem se nenhum bloco de destino atual aceitar essa mensagem.

Se o aplicativo exigir que vários blocos de mensagem processem a mensagem, em vez de apenas um bloco que aceita primeiro a mensagem, você pode usar outro tipo de bloco, como overwrite_buffer. A classe overwrite_buffer contém uma mensagem por vez, mas propaga essa mensagem para cada um de seus destinos.

A seguinte ilustração mostra a rede de processamento de imagem:

Image processing network.

O objeto countdown_event neste exemplo permite que a rede de processamento de imagens informe o aplicativo principal quando todas as imagens foram processadas. A classe countdown_event usa um objeto concurrency::event para sinalizar quando um valor de contador atinge zero. O aplicativo principal incrementa o contador sempre que envia um nome de arquivo à rede. O nó de terminal da rede diminui o contador depois que cada imagem é processada. Depois que o aplicativo principal atravessa o diretório especificado, ele aguarda que o objeto countdown_event sinalize que seu contador atingiu zero.

O exemplo a seguir mostra a classe 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&);
};

[Parte superior]

O Exemplo Completo

O código a seguir mostra um exemplo completo. A função wmain gerencia a biblioteca GDI+ e chama a função ProcessImages para processar os arquivos JPEG no diretório 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);
}

A seguinte ilustração contém os resultados da amostra. Cada imagem de origem está acima da imagem modificada correspondente.

Sample output for the example.

Lighthouse é criado por Tom Alphin e, portanto, é convertido em escala de cinza. Chrysanthemum, Desert e KoalaTulips têm vermelho como a cor dominante e, portanto, têm os componentes de cor azul e verde removidos e escurecidos. Hydrangeas, Jellyfish e Penguins correspondem aos critérios padrão e, portanto, têm tom sépia.

[Parte superior]

Compilando o código

Copie o código de exemplo e cole-o em um projeto do Visual Studio, ou cole-o em um arquivo chamado image-processing-network.cpp e execute o comando a seguir em uma janela do Prompt de comando do Visual Studio.

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

Confira também

Instruções passo a passo do runtime de simultaneidade