다음을 통해 공유


연습: 이미지 처리 네트워크 만들기

업데이트: 2010년 6월

이 문서에서는 이미지 처리를 수행하는 비동기 메시지 블록의 네트워크를 만드는 방법을 보여 줍니다.

네트워크는 특징을 기준으로 이미지에 대해 수행할 작업을 결정합니다. 이 예제에서는 데이터 흐름 모델을 사용하여 네트워크를 통해 이미지 경로를 지정합니다. 데이터 모델에서 프로그램의 독립 구성 요소는 메시지를 보내 서로 통신합니다. 구성 요소가 메시지를 받으면 임의의 동작을 수행한 다음 해당 동작의 결과를 다른 구성 요소에 전달합니다. 데이터 흐름 모델을 제어 흐름 모델과 비교해 볼 수 있습니다. 제어 흐름 모델의 경우 응용 프로그램에서 조건문, 루프 등의 제어 구조를 사용하여 프로그램의 작업 순서를 제어합니다.

데이터 흐름을 기반으로 하는 네트워크에서는 작업의 파이프라인을 만듭니다. 파이프라인의 각 단계는 전체 작업의 각 부분을 동시에 수행합니다. 자동차 제조를 위한 조립과 비슷하다고 생각하면 됩니다. 각 자동차를 조립 라인에 전달하면 한 작업장에서는 프레임을 조립하고 다른 작업장에서는 엔진을 장착하는 식입니다. 여러 대의 자동차를 동시에 조립할 수 있으면 한 번에 자동차 한 대 전체를 조립할 때보다 조립 라인의 처리량이 향상됩니다.

사전 요구 사항

이 연습을 시작하기 전에 다음 문서를 읽어 보십시오.

또한 이 연습을 시작하기 전에 GDI+의 기본 사항을 이해하는 것이 좋습니다. GDI+에 대한 자세한 내용은 GDI+를 참조하십시오.

단원

이 연습에는 다음 단원이 포함되어 있습니다.

  • 이미지 처리 기능 정의

  • 이미지 처리 네트워크 만들기

  • 전체 예제

이미지 처리 기능 정의

이 단원에서는 이미지 처리 네트워크가 디스크에서 읽은 이미지에 대한 작업 시 사용하는 지원 기능을 보여 줍니다.

GetRGBMakeColor 함수는 각각 지정된 색의 개별 구성 요소를 추출하고 결합합니다.

// 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, ColorMaskDarken 함수는 ProcessImage 함수를 호출하여 Bitmap 개체에 있는 각 픽셀의 색상 값을 변환합니다. 이러한 각각의 함수는 람다 식을 사용하여 1픽셀의 색 변환을 정의합니다.

// 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. Bitmap 개체를 원본 파일 이름과 연결하는 std::map 개체를 만듭니다.

    // 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 변수가 0이 될 때까지 기다립니다.

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

다음 표에서는 네트워크 멤버에 대해 설명합니다.

멤버

설명

load_bitmap

디스크에서 Bitmap 개체를 로드하고 이미지를 원본 파일 이름에 연결하도록 map 개체에 항목을 추가하는 Concurrency::transformer 개체입니다.

loaded_bitmaps

로드된 이미지를 이미지 처리 필터에 보내는 Concurrency::unbounded_buffer 개체입니다.

grayscale

Tom이 작성한 이미지를 회색조로 변환하는 transformer 개체입니다. 이 개체는 이미지의 메타데이터를 사용하여 작성자를 확인합니다.

colormask

주요 색으로 빨간색을 포함하고 있는 이미지에서 녹색 및 파란색 구성 요소를 제거하는 transformer 개체입니다.

darken

주요 색으로 빨간색을 포함하고 있는 이미지를 어둡게 만드는 transformer 개체입니다.

sepiatone

Tom이 작성하지 않고 주요 색이 빨간색이 아닌 이미지에 세피아 톤을 적용하는 transformer 개체입니다.

save_bitmap

처리된 image를 디스크에 비트맵으로 저장하는 transformer 개체입니다. save_bitmapmap 개체에서 원본 파일 이름을 검색하고 해당 파일 확장명을 .bmp로 변경합니다.

delete_bitmap

이미지에 사용하도록 메모리 공간을 확보하는 transformer 개체입니다.

decrement

네트워크에서 터미널 노드 역할을 하는 Concurrency::call 개체입니다. 이 개체는 countdown_event 개체를 줄여 이미지가 처리된 기본 응용 프로그램에 신호를 보냅니다.

loaded_bitmaps 메시지 버퍼는 unbounded_buffer 개체로서 Bitmap 개체를 여러 수신자에게 제공하므로 중요합니다. 대상 블록이 Bitmap 개체를 수락하면 unbounded_buffer 개체는 해당 Bitmap 개체를 다른 대상에 제공하지 않습니다. 따라서 개체를 unbounded_buffer 개체에 연결하는 순서가 중요합니다. grayscale, colormasksepiatone 메시지 블록은 각각 필터를 사용하여 특정 Bitmap 개체만 수락합니다. decrement 메시지 버퍼는 다른 메시지 버퍼에 의해 거부된 모든 Bitmap 개체를 수락하므로 loaded_bitmaps 메시지 버퍼의 중요한 대상입니다. unbounded_buffer 개체는 메시지를 순서대로 전파하는 데 필요합니다. 따라서 unbounded_buffer 개체는 새 대상 블록이 이 개체에 연결될 때까지 차단되며 현재 대상 블록에서 해당 메시지를 수락하지 않을 경우 메시지를 수락합니다.

응용 프로그램에서 먼저 메시지를 수락하는 하나의 메시지 블록 대신 여러 메시지 블록이 메시지를 처리하도록 요구할 경우 overwrite_buffer와 같은 다른 메시지 블록 형식을 사용할 수 있습니다. overwrite_buffer 클래스는 한 번에 하나의 메시지를 포함하지만 해당 메시지를 각 대상으로 전파합니다.

다음 그림에서는 이미지 처리 네트워크를 보여 줍니다.

이미지 처리 네트워크

이 예제의 countdown_event 개체를 사용하면 모든 이미지가 처리되었을 때 이미지 처리 네트워크를 통해 기본 응용 프로그램에 알릴 수 있습니다. countdown_event 클래스는 카운터 값이 0이 되면 Concurrency::event 개체를 사용하여 신호를 보낼 수 있습니다. 기본 응용 프로그램은 파일 이름을 네트워크에 보낼 때마다 카운터를 증가시킵니다. 네트워크의 터미널 노드는 각 이미지가 처리된 후 카운터를 감소시킵니다. 기본 응용 프로그램은 지정된 디렉터리를 트래버스한 후 countdown_event 개체에서 카운터가 0에 도달했다는 신호를 보낼 때까지 기다립니다.

다음 예제에서는 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 함수를 호출하여 Sample Pictures 디렉터리의 JPEG 파일을 처리합니다.

// 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년 6월

그림에서 오류를 수정했습니다.

고객 의견