チュートリアル: イメージ処理ネットワークの作成

更新 : 2010 年 6 月

ここでは、イメージ処理を実行する非同期メッセージ ブロックのネットワークを作成する方法について説明します。

このネットワークでは、イメージに対して実行する操作がそのイメージの特性に基づいて決定されます。 この例では、データ フロー モデルを使用して、ネットワークでイメージをルーティングします。 データ フロー モデルでは、プログラムの独立したコンポーネントどうしがメッセージを送信することによって通信します。 メッセージを受信したコンポーネントは、あるアクションを実行し、その結果を別のコンポーネントに渡すことができます。 このモデルと制御フロー モデルを比較してください。制御フロー モデルでは、アプリケーションは制御構造 (条件付きステートメントやループなど) を使用してプログラムでの操作順序を制御します。

データ フローに基づくネットワークでは、タスクのパイプラインが作成されます。 タスク全体がいくつかの部分に分けられ、パイプラインの各ステージによって同時に実行されます。 これは、自動車製造の組み立てラインに似ています。 各車両が組み立てラインを通るときに、あるステーションではフレームが組み立てられ、別のステーションではエンジンが取り付けられます。 複数の車両を同時に組み立てることができれば、1 台ずつ車両を完成させていくよりも組み立てラインのスループットが向上します。

必須コンポーネント

このチュートリアルを開始する前に、次のドキュメントを参照してください。

また、このチュートリアルを開始する前に、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);
}

GrayscaleSepiatoneColorMask、および Darken の各関数は、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. 他のイメージについては、セピア調を適用します。

ネットワークでは、これらの条件のうち最初に一致した 1 つのイメージ処理操作だけが適用されます。 たとえば、イメージが 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 変数がゼロになるまで待ちます。

    // 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_bitmap では、map オブジェクトから元のファイル名を取得し、そのファイル名拡張子を .bmp に変更します。

delete_bitmap

イメージ用のメモリを解放する transformer オブジェクト。

decrement

ネットワークのターミナル ノードとして機能する Concurrency::call オブジェクト。 countdown_event オブジェクトをデクリメントし、イメージが処理されたことをメイン アプリケーションに通知します。

loaded_bitmaps メッセージ バッファーは、unbounded_buffer オブジェクトとして Bitmap オブジェクトを複数の受信側に提供するため重要です。 ターゲット ブロックが Bitmap オブジェクトを受け入れると、unbounded_buffer オブジェクトはその Bitmap オブジェクトを他のターゲットには提供しなくなります。 そのため、オブジェクトを unbounded_buffer オブジェクトにリンクする順序が重要になります。 grayscalecolormask、および sepiatone の各メッセージ ブロックは、それぞれフィルターを使用して特定の Bitmap オブジェクトだけを受け入れます。 decrement メッセージ バッファーは、他のメッセージ バッファーによって拒否されたすべての Bitmap オブジェクトを受け入れるため、loaded_bitmaps メッセージ バッファーの重要なターゲットになります。 unbounded_buffer オブジェクトは、メッセージを順番に伝達するために必要です。 したがって、unbounded_buffer オブジェクトは、新しいターゲット ブロックがリンクされるまでブロックし、現在のターゲット ブロックがメッセージを受け入れない場合はそのメッセージを受け入れます。

メッセージを最初に受け入れた 1 つのメッセージ ブロックだけでなく、複数のメッセージ ブロックでメッセージを処理することがアプリケーションで要求される場合は、overwrite_buffer など、別の型のメッセージ ブロックを使用できます。 overwrite_buffer クラスには、一度に 1 つのメッセージが保持され、そのメッセージは各ターゲットに伝達されます。

次の図は、イメージ処理ネットワークを示しています。

画像処理ネットワーク

この例では、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 関数を呼び出して 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);
}

次の図は、サンプル出力を示しています。 各ソース イメージの下に、対応する変更後のイメージが示されています。

例のサンプル出力

Tom Alphin によって作成された Lighthouse は、グレースケールに変換されています。 最も優勢な色が赤である 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 月

図の誤りを訂正。

カスタマー フィードバック