Hatena::ブログ(Diary)

Life like a clown このページをアンテナに追加 RSSフィード

2013-12-08

Boost 逆引きリファレンスのサンプルコードをテスト化してみる

この記事は C++ Advent Calendar 2013 の 8 日目の記事になります(あれ、俺 8 日目なのか……)。このネタを思いついたのは、先月に boostjp の ML で見かけた以下の投稿からでした。

Boost 1.55.0 Beta 1 RCが公開されました。
http://lists.boost.org/Archives/boost/2013/10/206792.php
来週頭にはBoost 1.55.0 Beta 1がリリース、今月末のBoost 1.55.0 RCを経て、11月第1週にはBoost 1.55.0のリリースとなるかと思います。
# ちなみにそういったスケジュールは http://www.boost.org/community/ のカレンダーに書いてあります。
1.55.0リリースしてから問題があっても遅いので、今のうちからテストしたりしてもらえると非常に助かります。

https://groups.google.com/forum/#!topic/boostjp/Xub5TE3VCPM

この投稿を見ながら(投稿内容とはちょっとずれていますが)「あぁ、バージョンアップの度に、サンプルコードがきちんと動作をするかどうかもテストした方が良いのか」と思い、「Boost 逆引きリファレンスのサンプルコード、テスト化計画」を密かに始めてみました。

clown/boostjp では、Boost 逆引きリファレンス に掲載されている各種サンプルコードを出来るだけ原型を保ったままテスト化すると言う試みを行っています。例えば、配列 - boostjp の最初のサンプルコード

#include <iostream>
#include <boost/array.hpp>
#include <algorithm>

void disp(int x) { std::cout << x << ' '; }

int main()
{
    boost::array<int, 3> ar = {3, 1, 4};
    std::for_each(ar.begin(), ar.end(), disp);
}

これを以下のようにテスト化します。

#include <boost/test/unit_test.hpp>
#include <boost/array.hpp>
#include <algorithm>
#include <sstream>
#include "ostream_proxy.h"

BOOST_AUTO_TEST_CASE(test_overview) {
    boost::array<int, 3> ar = { 3, 1, 4 };

    std::ostringstream ss;
    std::for_each(ar.begin(), ar.end(), ostream_proxy(ss));
    BOOST_CHECK_EQUAL(ss.str(), "3 1 4 ");
}

「標準出力(std::cout)へ出力」と言うサンプルコードのテスト方法

サンプルコードの特徴として、標準出力(std::cout)に出力して結果を確かめる(掲載する)と言うコードが多くなります。特に、前述した例のように「アルゴリズムで指定した関数の中で std::cout に出力する」と言うサンプルコードの場合、どうやってテスト化すべきか悩んだのですが、最終的には以下のような補助クラスを用いて std::cout の代わりに std::ostringstream に出力させ、最後に出力した文字列で比較すると言う形を取る事にしました。

class ostream_proxy {
public:
    explicit ostream_proxy(std::ostream& stream, char separator = ' ') :
        stream_(stream), separator_(separator) {}

    template <class T>
    void operator()(const T& value) const {
        stream_ << value << separator_;
    }
}

本当はもう少し完成した状態で記事を書く予定だったのですが、予想以上に手間取ってしまったため現時点でひとまず記事として公開します。static assert(コンパイル時にエラーを発生させるサンプルコード)等いくつかはテスト化できそうにないものもありますが、掲載されているサンプルコードはできるだけを網羅してみようかと思います。

Related Pages

2013-10-11

Boost.Test (boost/test/included/unit_test.hpp) の使い方

リンク無しでテスト用のソースファイルが複数ある状況でビルドしようとしたら嵌ったお話。

先日、ユニットテストについての講義をする際に「取りあえず Boost.Test を使ってみましょう」と言うことで、リンク(≒ Boost をビルド)しなくても良い boost/test/included/unit_test.hpp を使おうとしたのですが、以下のようなリンクエラーが出力されました(Visual C++ 2012)。

error LNK2005: "public: __thiscall 
boost::unit_test::ut_detail::auto_test_unit_registrar::auto_test_unit_registrar(class 
boost::unit_test::test_unit_generator const &)" (??
0auto_test_unit_registrar@ut_detail@unit_test@boost@@QAE@ABVtest_unit_generator@23@@Z)
は既に gp2-test.obj で定義されています。

…(後略)…

ビルドを通すのにやや苦労しましたが、結論としては「リンク無しで Boost.Test フレームワークを複数のソースファイルが存在する状況で使用する場合」は以下の点を注意する必要があるようです。

  1. #include <boost/test/included/unit_test.hpp> を記述するのは 1 箇所のみ(2 箇所以上でインクルードすると、前述したリンクエラーが発生)。main.cpp 的なファイルを作成して、そこでインクルードするのが良いか。
  2. それ以外のファイル(各テストを記述したファイル)については、リンクしない場合であっても
    #include <boost/test/unit_test.hpp> と記述する。
  3. このままだと Boost.Test 用のライブラリをリンクしようとしてエラーになるので、コンパイル時に BOOST_TEST_NO_LIB または BOOST_ALL_NO_LIB を定義してリンクしないようにする。

ソースファイルの構成例

1. main.cpp

#define BOOST_TEST_MAIN // or #define BOOST_TEST_MODULE test_module_name
#include <boost/test/included/unit_test.hpp>

2. test1.cpp

#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_CASE(test_case1) {
    BOOST_CHECK(true);
}

3. test2.cpp(以下、必要なファイル数だけ同様に)

#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_CASE(test_case2) {
    BOOST_CHECK(true);
}

コンパイル時に BOOST_TEST_NO_LIB 指定を忘れずに。気を付けないと嵌りますが、リンク有無に関わらずメインファイル以外のインクルード記述は変わらないので、どちらにも移行しやすいと言うのは利点かもしれません。

2012-02-14

C++ のテストフレームワークを選ぶポイント

Boost.勉強会 #8 大阪 に参加してきました。発表者、運営者の皆様お疲れ様でした。今回もいろいろと黒魔術的な発表があり「これが変態かー」と感慨深く見ていたのですが、それはそれとして、個人的に気になっていたテーマとして「C++ でのテスト」に関連する発表が 2 件ほどあったので、楽しみにしていました。

C++ のテストフレームワークも数多く公開されており、好みや用途によって選択肢が変わって来るのですが、まぁ今だと選択肢として挙がるのは「Boost.Testgoogletest か」になってくるのかなと思います。どちら(もしくは、上記以外を含めたどれか)を選択すべきか、と言う問いに対しては、残念ながら現状でも決め手となるものに欠けているのですが、自分が利用する上で選択する上でのポイントを少し検討してみます。

手軽に利用できる

発表中でもありましたが、Boost.Test には「ヘッダファイルのインクルードのみで利用可能なテストフレームワーク」と言うものが用意されており、比較的小さなライブラリ等のプログラムをテストする際には便利でよく利用しています(参考:Boost minimal test - Faith and Brave - C++で遊ぼう)。

ビルドしてリンクする位どうって事ないと感じたりもするのですが、特に、Windows (Visual C++) 上で開発している時には /MT、/MD の違いや x86/x64 の違いで予想外にリンクに手こずる事もあり、リンク作業を敬遠しがちになったりもします。そう言った事もあって「ヘッダファイルのインクルードのみで利用可能」と言うのは魅力的に感じる時も多々あり、boost/test/minimal.hpp にはよくお世話になっています。

部分的にテスト可能

C++ Unit Test Frameworks - ACCU を読んでいて思い出したのですが、「部分的にテスト可能かどうか」と言う点も選択肢を検討する上でポイントになったりします。例えば、Microsoft .NET Framework 系?のプログラムのテストフレームワークである NUnit だと下記のように、テストしたい部分のみを選択して実行できるようになっています。

f:id:tt_clown:20120214143846p:image:w600

部分的にテストを行える機能があると、ネットワーク通信用のライブラリ等の「時間のかかる」テストも「テーストハーネス内に記述はするが、通常時(何度も実行する時)は実行されないようにしておく」のような選択肢を取る事もできます。

単体テストは速く走る。速く走らないとしたら、それは単体テストではない。他の種類のテストが単体テストの仮面をかぶっていることもよくある。次に当てはまるものは単体テストではない。

  1. データベースとやり取りをする
  2. ネットワークを介した通信をする
  3. ファイルシステムにアクセスする
  4. 実行するために特別な環境設定を必要とする(環境設定ファイルの編集など)

上記に該当するテストが悪いというわけではない。多くの場合において、そのようなテストを書く価値はあり、しばしばテストハーネス内に記述される。しかし単体テストは、そのようなテストと切り分けて、変更を行うたびに高速で実行できるように保ち続けることが重要である。

レガシーコード改善ガイド(p.17)

部分的にテストを行う機能については、ざっと見たところ Boost.Test、googletest ともに可能なようです。

ディフォルトでは,Google Testはユーザが定義したすべてのテストを実行します.しかし,ときにはテストの一部だけを実行したい場合もあるでしょう(たとえばデバッグや変更の速やかな確認のために).GTEST_FILTER環境変数か,--gtest_filterフラグをセットすることで,このフィルタに一致するテストだけを実行することが可能となります(形式は,TestCaseName.TestNameです)

GoogleTestAdvacnedGuide(翻訳)

これをひとまとめにしてテストされるプログラムも参照可能にしてコンパイルすると、すべてのテストを実行してくれるテストプログラムができる。WARNの表示や一部のテストだけを実行したいときは、コマンドラインで引数を指定すればよい。

(例)
    --log_level=warning (エラーレベルがWARNのところも表示)
    --run_test=suite名,suite名/test名 (特定のテストだけ実行)
Boost Testメモ

CppUnit と CppUnitLite

最後に蛇足的な話題。C++ のテストフレームワークと言うと CppUnit もよく名前が挙がるのですが、もう少し簡単に記述できるテストフレームワークとして CppUnitLite と言うものがあるそうです。

最初に CppUnit を開発した時、私はできるだけ JUnit に近いものにしようと考えました。そのほうが xUnit のアーキテクチャを見たことがある人にとって使いやすいだろうと考えたからです。しかしすぐさま、C++ ではきれいに実装することが難しい、あるいは不可能な事柄が次々に出てきました。これは C++ と Java の機能の違いによるものです。一番の問題は C++ にリフレクションの機能がないことでした。Java では、派生クラスのメソッドに対する参照を持って、実行時にメソッドを検索するといったことが可能です。しかし C++ では、実行時にアクセスすべきメソッドを登録するコードは人間が書いてやる必要があります。その結果、CppUnit は少し使いにくく理解づらいものになりました。

・・・(中略)・・・

CppUnit も CppUnitLite もテストハーネスとして利用可能です。CppUnitLite を使って書いたテストのほうが少し短いため、本書の C++ の例では CppUnitLite を使用しています。

レガシーコード改善ガイド(p.57-59)

2011-12-22

Web 上での C++11 関連記事まとめ

これは,C++11 Advent Calendar 2011 参加記事です.現在,開催されている Advent Calendar の一覧は Advent Calendar 2011 (jp) 開催予定リスト - Life like a clown を参照下さい.

C++11 (C++0x) に関しての情報は 本の虫Faith and Brave - C++で遊ぼう でかなり詳細な記述がありますので,情報を探す場合は site:cpplover.blogspot.comsite:d.hatena.ne.jp/faith_and_brave 辺りを指定してググってみると良いかもしれません.この記事でも,該当ブログの記事をメインにリンク集を作成しています.

尚,自分の理解が追い付いてないものや見逃したもの等があるので,結構な抜けがあるかと思います.

概要

ざっと概要を知りたい場合は,Wikipedia@wraith13 さんの発表資料 辺りが良いかと思います.

型推論・型指定子 (auto, decltype)

auto は右辺値の型を推論してくれる機能,decltype は式の演算結果の型を取得する機能となります.

初期化構文 (initializer_list)

std::vector 等の STL コンテナの初期化が,一つずつ push_back などのようにしなくとも {} (波括弧) で一括して行えるようになる機能です.これに合わせて,全ての初期化が {} (波括弧) で統一できるようにもなるようです.尚,リスト初期化は同じ型しか渡せないという制限があるようで,任意の数の違う型の引数を指定する場合は Variadic Templates を代わりに使う事になるようです.

Range-based for 文

他プログラミング言語によくある foreach 文に相当するものです.

ラムダ式

匿名関数(無名関数)と呼ばれるもの.昔,一番最初に聞いた時は「は?何それ?」と言う感想を抱いたのですが,他プログラミング言語を見ても随分とポピュラーな機能になってきた感があります.

rvalue reference, および Move Semantics

C++03 での問題点の一つとしてよく挙げられていた「コピーされすぎ(コピーコンストラクタが実行されすぎ)」を緩和する事を目的の一つとして導入された機能です.名称等の問題もあって馴染みにくい機能ですが,第 3 者にライブラリを提供する立場にある開発者であれば,これから必須となって来る機能だろうと思います.

定数式 (constexpr)

コンパイル時に実行される関数を定義できるようになると言うもの.constexpr 指定のものであれば swtich 文の case にも使用できるようになりので,これまでマクロで行われていた部分が代替可能になる可能性があります.ただし,記述する関数本体に厳しい制限があるので,実装するのが大変になる場合があるかもしれません.

可変長引数テンプレート (Variadic Templates)

Boost.PP さん死んでしまうん?

ライブラリ

C++11 で新規に導入されるライブラリ群に関するリンク.C++11 で新規に導入されたライブラリの多くは Boost C++ Libraries から採用されたものであるため,Boost C++ Libraries の該当ライブラリの説明も参考になります.また,いくつかのものは TR1 (Technical Report 1) として,コンパイラによっては既に実装されている事もあります.

STL アルゴリズムの追加

all_of(), any_of(), none_of(), is_permutation(), move() が追加されました.

文字列・数値の相互変換

boost::lexical_cast() を用いて行っていた文字列・数値の相互変換関数群.

スマートポインタ

unique_ptr, shared_ptr, weak_ptr

正規表現
スレッド
タプル型

std::pair の拡張版.std::tuple<int, char, double> のように 3項目以上指定できます.

固定長配列
ハッシュテーブル
単方向リスト
日付・時間

C++0x から Boost へ逆輸入された珍しい例.そのため,Boost には Boost.DateTime と Boost.Chrono と言う 2 種類の日付・時間を扱うライブラリが存在しています.

乱数
可変引数 minmax
参照ラッパ
型特性 (type_traits)
汎用関数オブジェクト
束縛子 (bind)
関数の戻り値の型 (result_of)
メンバ関数アダプタ (mem_fn)
エラー情報のサポート (system_error)

その他

単体の章を設けるかその他のサブ項目にするかについては,完全に私の主観で行ってますので,いろいろあるかとは思いますがまぁ・・・反応を見ながら移した方がいいかなと判断した場合は変更するかもしれません.

NULL ポインタ (nullptr)

NULL が意図せず int 型に推論されないようにするための修正.

文字列リテラル

UTF-16,UTF-32,UTF-8,raw 文字および文字列のリテラルにそれぞれ,u,U,u8,R プリフィックスを付ける事で明示できるようになりました.

ユーザ定義リテラル
final, および override 指定
テンプレートの別名付け (Template Aliases)

template クラスの typedef.実際には typedef ではなく using と言うキーワードを使うようです.

例外を送出しないことの明示 (noexcept)

C++03 までは,デストラクタ等によく throw() と書いていた部分に相当するものです.C++11 で例外指定が deprecated になったので,その代わりと言う感じでしょうか.

コンパイル時 assert (static_assert)

assert() のコンパイル時版.BOOST_STATIC_ASSERT() と言うマクロがありましたが,これが言語機能としてサポートされたと言う感じでしょうか.

Enum 拡張
関数の戻り値の後置

decltype との併用で便利な場面が出てくると言った感じでしょうか.

コンパイラが生成する関数への default/delete 指定

コンパイラによって自動生成されるコンストラクタ,デストラクタ,代入演算子等の各種関数がある程度制御できるようになります.

明示的な型変換

キャスト演算子とも言われていますが,これに explicit キーワードを付けられるようになるようです.explicit キーワードの付いた型変換用演算子を使用する場合は,(Type)x のように変換後の型を明示的に記述する必要があります.

委譲コンストラクタ

あるコンストラクタから,同じクラスの別のコンストラクタが利用できるようになるようです.

メンバ変数の初期値の指定

C# 辺りで馴染みのあるメンバ変数の定義部分での初期化が可能になるようです.

2011-12-08

Boost.GIL で HSV 変換を行う

これは,Boost Advent Calendar 2011 参加記事です.現在,開催されている Advent Calendar の一覧は Advent Calendar 2011 (jp) 開催予定リスト - Life like a clown を参照下さい.

当初,Boost.GIL の(初歩的な)逆引きリファレンス的な記事にしようかと思ったのですが,Boost.勉強会の Boost.GIL 画像処理入門 (PDF) に大体の事が書かれてあってやる事がなかったので,後半に書く予定だった「画像を明るくする」,「画像を鮮やかにする」等の処理を行うための HSV 変換についてをメインに書くことにします.

HSV 変換のアルゴリズムについては以下を参照下さい.

これを Boost.GIL で実現する方法ですが,boost::gil::transform_pixel と言う関数を使うと良いようです.

boost::gil::transform_pixel(const View& src, const View& dst, F functor)

対象のピクセルを受け取って別のピクセルを返す関数を、全ピクセルに対して適用します。この関数は、画素値と出力値が1対1対応である場合に用います(e.g. 明度、コントラスト, レベル補正, トーンカーブ, etc...)。

boost::gil::transform_pixel_positions(const View& src, const View& dst, F functor)

周囲のピクセルの値を元に結果となるピクセルを返す関数を、全ピクセルに対して適用します。transform_pixelとは、受け取る引数がロケータであるという点が異なっており、周囲の画素値を元に実際の出力値を求める場合に有効です(e.g. Blur, Sovel, Laplacian, etc...)。

映像奮闘記: boost.GILで組む、ジェネリックな画像処理のアルゴリズム

尚,第 3 引数に指定する関数オブジェクトは以下のようになります.

class transform_functor {
public:
    template<typename PixelT>
    PixelT operator()(PixelT src);
};

HSV 色空間内で処理を行うための hsvfilter

今回は,この形に沿った関数オブジェクトで「RGB 値を HSV 値に変換してユーザに何らかの処理を行ってもらった後,再び RGB 値に戻す」と言う機能を持つ hsvfilter を実装してみます.尚,現時点では各ピクセルが RGB 値のみを持つ画像でないとエラーになります.それ以外のピクセル型については,そのうち調べて実装していこうかと思います.

#ifndef HSVFILTER_H
#define HSVFILTER_H

#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_io.hpp>
#include <boost/function.hpp>
#include <boost/gil/gil_all.hpp>
#include <cassert>

/* ------------------------------------------------------------------------- */
/*
 *  hsvfilter
 *
 *  各ピクセルを HSV 値に変換した上で何らかの処理を行うためのフィルタ.
 */
/* ------------------------------------------------------------------------- */
class hsvfilter {
public:
    typedef boost::function<void (int&, int&, int&)> predicate_type;
private:
    typedef boost::tuple<int, int, int> rgb_type;
    typedef boost::tuple<int, int, int> hsv_type;
public:
    /* --------------------------------------------------------------------- */
    //  constructor
    /* --------------------------------------------------------------------- */
    explicit hsvfilter(predicate_type pred) : pred_(pred) {}
    
    /* --------------------------------------------------------------------- */
    //  operator()
    /* --------------------------------------------------------------------- */
    template <class PixelT>
    PixelT operator()(PixelT pixel) {
        rgb_type src = this->to_rgb(pixel);
        hsv_type hsv = this->rgb2hsv(src);
        pred_(hsv.get<0>(), hsv.get<1>(), hsv.get<2>());
        rgb_type dest = this->hsv2rgb(hsv);
        return this->from_rgb<PixelT>(dest);
    }

private:
    /* --------------------------------------------------------------------- */
    /*
     *  to_rgb
     *
     *  Boost.GIL の各種ピクセル型から RGB へ変換する.
     *  TODO: 現在は RGB の値のみが格納されてあるものしかサポートされて
     *  いない.その他のピクセル型を調査して対応する.
     */
    /* --------------------------------------------------------------------- */
    template <class PixelT>
    rgb_type to_rgb(const PixelT& pixel) {
        assert(boost::gil::num_channels<PixelT>::value == 3);
        return boost::make_tuple(pixel[0], pixel[1], pixel[2]);
    }
    
    /* --------------------------------------------------------------------- */
    /*
     *  from_rgb
     *
     *  RGB から Boost.GIL の各種ピクセル型へ変換する.
     *  TODO: 現在は RGB の値のみが格納されてあるものしかサポートされて
     *  いない.その他のピクセル型を調査して対応する.
     */
    /* --------------------------------------------------------------------- */
    template <class PixelT>
    PixelT from_rgb(const rgb_type& rgb) {
        assert(boost::gil::num_channels<PixelT>::value == 3);
        
        PixelT dest;
        dest[0] = rgb.get<0>();
        dest[1] = rgb.get<1>();
        dest[2] = rgb.get<2>();
        return dest;
    }
    
    /* --------------------------------------------------------------------- */
    /*
     *  rgb2hsv
     *
     *  RGB 値を HSV 値に変換する.
     */
    /* --------------------------------------------------------------------- */
    hsv_type rgb2hsv(const rgb_type& rgb) {
        const int R = std::min(rgb.get<0>(), 255);
        const int G = std::min(rgb.get<1>(), 255);
        const int B = std::min(rgb.get<2>(), 255);
        
        const int maximum = std::max(std::max(R, G), B);
        const int minimum = std::min(std::min(R, G), B);
        
        const double V = maximum;
        const double S = (maximum != 0) ? 255.0 * (maximum - minimum) / static_cast<double>(maximum) : 0.0;
        
        double H = 0.0;
        if (S > 0.0 && maximum != minimum) {
            if (maximum == R) H = 60.0 * (G - B) / static_cast<double>(maximum - minimum);
            else if (maximum == G) H = 60.0 * (B - R) / static_cast<double>(maximum - minimum) + 120.0;
            else H = 60.0 * (R - G) / static_cast<double>(maximum - minimum) + 240.0;
            if (H < 0.0) H += 360.0;
        }
        
        return boost::make_tuple(static_cast<int>(H), static_cast<int>(S), static_cast<int>(V));
    }
    
    /* --------------------------------------------------------------------- */
    /*
     *  hsv2rgb
     *
     *  HSV 値を RGB 値に変換する.
     */
    /* --------------------------------------------------------------------- */
    rgb_type hsv2rgb(const hsv_type& hsv) {
        const int H = hsv.get<0>();
        const int S = hsv.get<1>();
        const int V = hsv.get<2>();
        
        if (S == 0) return boost::make_tuple(V, V, V);
        
        const int Hi = H / 60;
        const double F = H / 60.0 - Hi;
        const double M = (1.0 - S / 255.0) * V;
        const double N = (1.0 - S / 255.0 * F) * V;
        const double K = (1.0 - S / 255.0 * (1.0 - F)) * V;
        
        switch (Hi) {
            case 0: return boost::make_tuple(V, K, M);
            case 1: return boost::make_tuple(N, V, M);
            case 2: return boost::make_tuple(M, V, K);
            case 3: return boost::make_tuple(M, N, V);
            case 4: return boost::make_tuple(K, M, V);
            case 5: return boost::make_tuple(V, M, N);
            default: break;
        }
        
        return boost::make_tuple(-1, -1, -1);
    }
    
private:
    predicate_type pred_;
};

#endif // HSVFILTER_H

サンプルプログラム

この hsvfilter を使用して,画像を明るくするサンプルプログラムは以下になります.ここでは画像の明るさを調整していますが,これが例えば,画像の鮮やかさを調整する場合は,value の代わりに saturation の値を弄る事になります.

/* ------------------------------------------------------------------------- */
/*
 *  brightener
 *
 *  各ピクセルの明るさを一律に明るくしていくクラス.
 */
/* ------------------------------------------------------------------------- */
class brightener {
public:
    explicit brightener(int brightness) :
        brightness_(brightness) {}
    
    void operator()(int& hue, int& saturation, int& value) {
        value += brightness_;
        if (value > 255) value = 255;
    }
    
private:
    int brightness_;
};

/* ------------------------------------------------------------------------- */
//  main
/* ------------------------------------------------------------------------- */
int main(int argc, char* argv[]) {
    if (argc < 3) return -1;
    
    std::string src_path = argv[1];
    std::string dest_path = argv[2];
    
    boost::gil::rgb8_image_t src_image;
    boost::gil::jpeg_read_image(src_path, src_image);
    boost::gil::rgb8_image_t dest_image(src_image.dimensions());
    
    // メイン処理
    int brightness = 50;
    boost::gil::transform_pixels(
        boost::gil::const_view(src_image),
        boost::gil::view(dest_image),
        hsvfilter(brightener(brightness))
    );
    
    boost::gil::jpeg_write_view(dest_path, boost::gil::view(dest_image));
    
    return 0;
}

f:id:tt_clown:20111208124931j:image:w670

Related Pages