다음을 통해 공유


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

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

네트워크는 특성에 따라 이미지에서 수행할 작업을 결정합니다. 이 예제에서는 데이터 흐름 모델을 사용하여 네트워크를 통해 이미지를 라우팅합니다. 데이터 흐름 모델에서는, 프로그램의 개별 구성 요소가 메시지를 전달하여 서로 통신합니다. 구성 요소가 메시지를 받으면 일부 작업을 수행한 다음 해당 작업의 결과를 다른 구성 요소에 전달할 수 있습니다. 이를 애플리케이션이 제어 구조(예: 조건문, 루프 등)를 사용하여 프로그램의 작업 순서를 제어하는 제어 흐름 모델과 비교합니다.

데이터 흐름을 기반으로 하는 네트워크는 작업 파이프라인만듭니다. 파이프라인의 각 단계는 전체 작업의 일부를 동시에 수행합니다. 이는 자동차 제조 조립 라인에 비유될 수 있습니다. 각 차량이 어셈블리 라인을 통과할 때 한 스테이션은 프레임을 조립하고 다른 스테이션은 엔진을 설치합니다. 여러 차량을 동시에 조립할 수 있게 함으로써 어셈블리 라인은 전체 차량을 한 번에 하나씩 조립하는 것보다 더 나은 처리량을 제공합니다.

필수 조건

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

또한 이 연습을 시작하기 전에 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+ 비트맵 개체에서 각 픽셀의 색 값을 변환합니다. 이 함수는 ProcessImage concurrency::p arallel_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, SepiatoneColorMaskDarken함수는 개체에서 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 . 그러나 이 함수는 각 색의 값을 변경하는 대신 동시성::결합 가능한 개체를 사용하여 빨강, 녹색 또는 파란색 구성 요소가 이미지를 지배하는지 여부를 계산합니다.

// 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. 변수가 0에 countdown_event 도달할 때까지 기다립니다.

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

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

멤버 설명
load_bitmap 디스크에서 개체를 로드 Bitmap 하고 개체에 항목을 map 추가하여 이미지를 원래 파일 이름과 연결하는 동시성::transformer 개체입니다.
loaded_bitmaps 로드된 이미지를 이미지 처리 필터로 보내는 동시성::unbounded_buffer 개체입니다.
grayscale transformer Tom이 작성한 이미지를 회색조로 변환하는 개체입니다. 이미지의 메타데이터를 사용하여 작성자를 확인합니다.
colormask transformer 빨간색이 주 색인 이미지에서 녹색 및 파란색 구성 요소를 제거하는 개체입니다.
darken transformer 빨간색이 주색인 이미지를 어둡게 하는 개체입니다.
sepiatone transformer Tom에서 작성하지 않고 주로 빨간색이 아닌 이미지에 세피아 토닝을 적용하는 개체입니다.
save_bitmap transformer 처리된 image 디스크를 비트맵으로 저장하는 개체입니다. save_bitmap 는 개체에서 map 원래 파일 이름을 검색하고 파일 이름 확장명을 .bmp로 변경합니다.
delete_bitmap transformer 이미지의 메모리를 해제하는 개체입니다.
decrement 네트워크에서 터미널 노드 역할을 하는 동시성::call 개체입니다. 개체가 countdown_event 감소하여 이미지가 처리되었음을 기본 애플리케이션에 알릴 수 있습니다.

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

애플리케이션에서 메시지를 먼저 수락하는 하나의 메시지 블록 대신 여러 메시지 블록이 메시지를 처리하도록 요구하는 경우 다음과 같은 overwrite_buffer다른 메시지 블록 유형을 사용할 수 있습니다. 클래스는 overwrite_buffer 한 번에 하나의 메시지를 보유하지만 해당 메시지를 각 대상에 전파합니다.

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

Image processing network.

countdown_event 이 예제의 개체를 사용하면 모든 이미지가 처리되었을 때 이미지 처리 네트워크에서 기본 애플리케이션에 알릴 수 있습니다. 클래스는 countdown_event 동시성::event 개체를 사용하여 카운터 값이 0에 도달하면 신호를 표시합니다. 기본 애플리케이션은 네트워크로 파일 이름을 보낼 때마다 카운터를 증가합니다. 네트워크의 터미널 노드는 각 이미지가 처리된 후 카운터를 감소합니다. 기본 애플리케이션이 지정된 디렉터리를 트래버스한 후 개체가 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 호출하여 디렉터리의 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);
}

다음 그림에서는 샘플 출력을 보여 줍니다. 각 원본 이미지는 해당 수정된 이미지 위에 있습니다.

Sample output for the example.

Lighthouse 는 Tom Alphin이 작성하므로 회색조로 변환됩니다. Chrysanthemum, Desert, KoalaTulips 빨강을 주된 색으로 가지므로 파란색 및 녹색 구성 요소가 제거되고 어둡게 됩니다. Hydrangeas, Jellyfish및 기본 조건과 Penguins 일치하므로 세피아 톤입니다.

[맨 위로 이동]

코드 컴파일

예제 코드를 복사하여 Visual Studio 프로젝트에 붙여넣거나 이름이 지정된 image-processing-network.cpp 파일에 붙여넣은 다음 Visual Studio 명령 프롬프트 창에서 다음 명령을 실행합니다.

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

참고 항목

동시성 런타임 연습