Panduan: Membuat Jaringan Pemrosesan Gambar
Dokumen ini menunjukkan cara membuat jaringan blok pesan asinkron yang melakukan pemrosesan gambar.
Jaringan menentukan operasi mana yang akan dilakukan berdasarkan citra berdasarkan karakteristiknya. Contoh ini menggunakan model aliran data untuk merutekan gambar melalui jaringan. Dalam model aliran data, komponen independen program berkomunikasi satu sama lain dengan mengirim pesan. Ketika komponen menerima pesan, komponen dapat melakukan beberapa tindakan lalu meneruskan hasil tindakan tersebut ke komponen lain. Bandingkan ini dengan model alur kontrol, di mana aplikasi menggunakan struktur kontrol, misalnya, pernyataan kondisional, perulangan, dan sebagainya, untuk mengontrol urutan operasi dalam program.
Jaringan yang didasarkan pada aliran data membuat alur tugas. Setiap tahap alur secara bersamaan melakukan bagian dari tugas keseluruhan. Analogi untuk ini adalah jalur assembly untuk manufaktur mobil. Ketika setiap kendaraan melewati jalur perakitan, satu stasiun merakitan bingkai, yang lain memasang mesin, dan sebagainya. Dengan memungkinkan beberapa kendaraan untuk dirakit secara bersamaan, jalur perakitan memberikan throughput yang lebih baik daripada merakit kendaraan lengkap satu per satu.
Prasyarat
Baca dokumen berikut sebelum Anda memulai panduan ini:
Kami juga menyarankan agar Anda memahami dasar-dasar GDI+ sebelum memulai panduan ini.
Bagian
Panduan ini berisi bagian berikut:
Menentukan Fungsionalitas Pemrosesan Gambar
Bagian ini menunjukkan fungsi dukungan yang digunakan jaringan pemrosesan gambar untuk bekerja dengan gambar yang dibaca dari disk.
Fungsi berikut, GetRGB
dan MakeColor
, ekstrak dan gabungkan masing-masing komponen dari warna yang diberikan.
// 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);
}
Fungsi berikut, ProcessImage
, memanggil objek std::function yang diberikan untuk mengubah nilai warna setiap piksel dalam objek GDI+ Bitmap. Fungsi ini ProcessImage
menggunakan konkurensi::p arallel_for algoritma untuk memproses setiap baris bitmap secara paralel.
// 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);
}
Fungsi berikut, Grayscale
, , ColorMask
Sepiatone
, dan Darken
, panggil ProcessImage
fungsi untuk mengubah nilai warna setiap piksel dalam Bitmap
objek. Masing-masing fungsi ini menggunakan ekspresi lambda untuk menentukan transformasi warna satu piksel.
// 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;
}
Fungsi berikut, GetColorDominance
, juga memanggil ProcessImage
fungsi . Namun, alih-alih mengubah nilai setiap warna, fungsi ini menggunakan konkurensi::objek yang dapat dikombinasikan untuk menghitung apakah komponen warna merah, hijau, atau biru mendominasi gambar.
// 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;
}
Fungsi berikut, GetEncoderClsid
, mengambil pengidentifikasi kelas untuk jenis MIME yang diberikan dari encoder. Aplikasi menggunakan fungsi ini untuk mengambil encoder untuk 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
}
[Atas]
Membuat Jaringan Pemrosesan Gambar
Bagian ini menjelaskan cara membuat jaringan blok pesan asinkron yang melakukan pemrosesan gambar pada setiap gambar JPEG (.jpg) dalam direktori tertentu. Jaringan melakukan operasi pemrosesan gambar berikut:
Untuk gambar apa pun yang ditulis oleh Tom, konversi ke skala abu-abu.
Untuk gambar apa pun yang memiliki warna merah sebagai dominan, hapus komponen hijau dan biru lalu gelapkan.
Untuk gambar lain, terapkan toning sepia.
Jaringan hanya menerapkan operasi pemrosesan gambar pertama yang cocok dengan salah satu kondisi ini. Misalnya, jika gambar ditulis oleh Tom dan memiliki merah sebagai warna dominannya, gambar hanya dikonversi menjadi skala abu-abu.
Setelah jaringan melakukan setiap operasi pemrosesan gambar, jaringan menyimpan gambar ke disk sebagai file bitmap (.bmp).
Langkah-langkah berikut menunjukkan cara membuat fungsi yang mengimplementasikan jaringan pemrosesan gambar ini dan menerapkan jaringan tersebut ke setiap gambar JPEG dalam direktori tertentu.
Untuk membuat jaringan pemrosesan gambar
Buat fungsi,
ProcessImages
, yang mengambil nama direktori pada disk.void ProcessImages(const wstring& directory) { }
ProcessImages
Dalam fungsi , buatcountdown_event
variabel . Kelascountdown_event
ditampilkan nanti dalam panduan ini.// Holds the number of active image processing operations and // signals to the main thread that processing is complete. countdown_event active(0);
Buat objek std::map yang mengaitkan
Bitmap
objek dengan nama file aslinya.// Maps Bitmap objects to their original file names. map<Bitmap*, wstring> bitmap_file_names;
Tambahkan kode berikut untuk menentukan anggota jaringan pemrosesan gambar.
// // 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(); });
Tambahkan kode berikut untuk menyambungkan jaringan.
// // 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);
Tambahkan kode berikut untuk dikirim ke kepala jaringan jalur lengkap setiap file JPEG di direktori.
// 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);
Tunggu hingga
countdown_event
variabel mencapai nol.// Wait for all operations to finish. active.wait();
Tabel berikut ini menjelaskan anggota jaringan.
Anggota | Deskripsi |
---|---|
load_bitmap |
Objek konkurensi::transformer yang memuat Bitmap objek dari disk dan menambahkan entri ke map objek untuk mengaitkan gambar dengan nama file aslinya. |
loaded_bitmaps |
Objek konkurensi::unbounded_buffer yang mengirim gambar yang dimuat ke filter pemrosesan gambar. |
grayscale |
Objek transformer yang mengonversi gambar yang ditulis oleh Tom menjadi skala abu-abu. Ini menggunakan metadata gambar untuk menentukan penulisnya. |
colormask |
Objek transformer yang menghapus komponen warna hijau dan biru dari gambar yang memiliki warna merah sebagai warna dominan. |
darken |
Objek transformer yang menggelapkan gambar yang memiliki warna merah sebagai warna dominan. |
sepiatone |
Objek transformer yang menerapkan toning sepia ke gambar yang tidak ditulis oleh Tom dan sebagian besar tidak berwarna merah. |
save_bitmap |
Objek transformer yang menyimpan yang diproses image ke disk sebagai bitmap. save_bitmap mengambil nama file asli dari map objek dan mengubah ekstensi nama filenya menjadi .bmp. |
delete_bitmap |
Objek transformer yang membebaskan memori untuk gambar. |
decrement |
Konkurensi ::panggil objek yang bertindak sebagai simpul terminal dalam jaringan. Ini mengurangi countdown_event objek untuk memberi sinyal ke aplikasi utama bahwa gambar telah diproses. |
Buffer loaded_bitmaps
pesan penting karena, sebagai unbounded_buffer
objek, ia menawarkan Bitmap
objek ke beberapa penerima. Ketika blok target menerima Bitmap
objek, objek tidak menawarkan objek tersebut unbounded_buffer
Bitmap
ke target lain. Oleh karena itu, urutan Anda menautkan objek ke unbounded_buffer
objek penting. Pesan grayscale
, colormask
, dan sepiatone
memblokir masing-masing menggunakan filter untuk hanya menerima objek tertentu Bitmap
. Buffer decrement
pesan adalah target penting dari loaded_bitmaps
buffer pesan karena menerima semua Bitmap
objek yang ditolak oleh buffer pesan lainnya. Objek unbounded_buffer
diperlukan untuk menyebarluaskan pesan secara berurutan. Oleh karena itu, unbounded_buffer
objek memblokir hingga blok target baru ditautkan ke objek tersebut dan menerima pesan jika tidak ada blok target saat ini yang menerima pesan tersebut.
Jika aplikasi Anda mengharuskan beberapa blok pesan memproses pesan, alih-alih hanya satu blok pesan yang pertama kali menerima pesan, Anda dapat menggunakan jenis blok pesan lain, seperti overwrite_buffer
. Kelas overwrite_buffer
menyimpan satu pesan pada satu waktu, tetapi menyebarkan pesan tersebut ke setiap targetnya.
Ilustrasi berikut menunjukkan jaringan pemrosesan gambar:
Objek countdown_event
dalam contoh ini memungkinkan jaringan pemrosesan gambar untuk menginformasikan aplikasi utama ketika semua gambar telah diproses. Kelas countdown_event
menggunakan objek konkurensi::event untuk memberi sinyal ketika nilai penghitung mencapai nol. Aplikasi utama menaikkan penghitung setiap kali mengirim nama file ke jaringan. Simpul terminal dari jaringan mengurangi penghitung setelah setiap gambar diproses. Setelah aplikasi utama melintasi direktori yang ditentukan, ia menunggu objek untuk countdown_event
memberi sinyal bahwa penghitungnya telah mencapai nol.
Contoh berikut menunjukkan countdown_event
kelas:
// 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&);
};
[Atas]
Contoh Lengkap
Kode berikut menunjukkan contoh lengkapnya. Fungsi ini wmain
mengelola pustaka GDI+ dan memanggil ProcessImages
fungsi untuk memproses file JPEG di Sample Pictures
direktori.
// 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);
}
Ilustrasi berikut menunjukkan output sampel. Setiap gambar sumber berada di atas gambar yang dimodifikasi yang sesuai.
Lighthouse
ditulis oleh Tom Alphin dan oleh karena itu dikonversi menjadi skala abu-abu. Chrysanthemum
, , Desert
Koala
, dan Tulips
memiliki merah sebagai warna dominan dan oleh karena itu memiliki komponen warna biru dan hijau dihapus dan digelapkan. Hydrangeas
, Jellyfish
, dan Penguins
cocok dengan kriteria default dan oleh karena itu adalah sepia toned.
[Atas]
Mengompilasi Kode
Salin kode contoh dan tempelkan dalam proyek Visual Studio, atau tempelkan dalam file yang diberi nama image-processing-network.cpp
lalu jalankan perintah berikut di jendela Prompt Perintah Visual Studio.
cl.exe /DUNICODE /EHsc image-processing-network.cpp /link gdiplus.lib