なんだか雲行きの怪しい雑記帖

ぐだぐだ日記とメモと,あと不定期更新

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

色んなhas_xxx


has_xxxは古い古いと言いつつ,なんだかんだで色々使ってしまうので亜種とかに遭遇しつつ
正直この機能はめちゃくちゃ汎用性が高い(というよりむしろ無駄に汎用性を上げすぎている)ライブラリとか作ってるときにしか使わないんじゃないかなと思いつつ


色んなhas_xxxとそれの実装例というか,まともじゃないC++というか,まあそういうのです

概要

has_xxxと言っても実は色々あります
  • 「型名T::xxx(依存名)があるかどうかを調べる」has_xxx

  • 「テンプレートT::xxxがあるかどうかを調べる」has_xxx

  • 「メンバ変数T::xxxがあるかどうかを調べる」has_xxx

  • 「メンバ関数T::xxxがあるかどうかを調べる」has_xxx




ということで,こういう実装だとこういうクラスが受かる(valueが1となる)についてまとめようと思いました
それとevilなC++コード

テストパターン一覧は次(対象とする依存名はxxxで固定です)
struct nothing{
};

struct type_int_xxx{
typedef int xxx;
};

struct struct_xxx{
struct xxx{};
};

struct template_xxx{
template< typename T > struct xxx{};
};

struct field_int_xxx{
int xxx;
};

struct member_func_xxx{
void xxx();
};

struct static_member_func_xxx{
static void xxx();
};

struct overloaded_member_func_xxx{
void xxx();
void xxx( int );
};

struct template_member_func_xxx{
template< typename T > void xxx();
};

追記:あとから気付いたんですが,C++だとフィールド(field)ではなくメンバ変数(member variable)という方が正しいですね,失敗.メンバ関数(member function)の方はあってるっぽいが…

一応,テスト用の実行コードは次
    // ~~~
// has_xxxはこの辺で定義される
// ~~~

#include <iostream>
int main()
{
using namespace std;

#define TEST( classname ) \
{ cout << #classname << " : " << has_xxx<classname>::value << endl; }

TEST(nothing);
TEST(type_int_xxx);
TEST(struct_xxx);
TEST(template_xxx);
TEST(field_int_xxx);
TEST(member_func_xxx);
TEST(static_member_func_xxx);
TEST(overloaded_member_func_xxx);
TEST(template_member_func_xxx);

return 0;
}


どのhas_xxxの実装でも必要になってくる共通の定義は次
typedef struct{ char c; } true_t;
typedef struct{ char c[2]; } false_t;


has_trait_xxx

has_xxxと言ったら普通はこれだと思う
template< typename T >
struct has_trait_xxx{
template< typename U > static true_t check( U*, typename U::xxx* =0 );
static false_t check( ... );
enum{ value = ( sizeof(check((T*)0)) == sizeof(true_t) ) };
};

template< typename T >
using has_xxx = has_trait_xxx<T>;

「型名T::xxx(依存名)があるかどうかを調べる」has_xxxです

上と次のは同じ内容で同じ結果,好みで
template< typename T >
struct has_trait_xxx_b{
template< typename U > static true_t check( typename U::xxx* );
template< typename U > static false_t check( ... );
enum{ value = ( sizeof(check<T>(0)) == sizeof(true_t) ) };
};

template< typename T >
using has_xxx = has_trait_xxx_b<T>;


ところが上記2つはMSVCとGCCとで動作が違う(GCCとClangは一緒)









テストケースMSVC2013g++-4.8.1clang++-3.3
nothing000
type_int_xxx111
struct_xxx111
template_xxx100
field_int_xxx000
member_func_xxx000
static_member_func_xxx000
overloaded_member_func_xxx000
template_member_func_xxx000


どこをどうすると「template_xxx」にヒットするのかは分からないが(というかどの型でマッチしてるのか分からないんだが),MSVCだと通る


これを避けるためにBoostではMSVCの時にだけ次のようなテンプレートで分岐をかけるようなコードを使う
template< typename T >
struct has_trait_xxx_sfinae{
template< typename U > struct sfinae_helper{ typedef void type; };
template< typename U, typename V =void > struct check{ enum{ value =false }; };
template< typename U > struct check< U, typename sfinae_helper<typename U::xxx>::type >{ enum{ value =true }; };
enum{ value = ( check<T>::value ) };
};

template< typename T >
using has_xxx = has_trait_xxx_sfinae<T>;


こうすると見事,結果が一致する,MSVC不思議










テストケースMSVC2013g++-4.8.1clang++-3.3
nothing000
type_int_xxx111
struct_xxx111
template_xxx000
field_int_xxx000
member_func_xxx000
static_member_func_xxx000
overloaded_member_func_xxx000
template_member_func_xxx000


最初の2つが「オーバーロードによる解決」で,最後のが「テンプレートとSFINAEによる解決」らしく,上記例のように微妙に結果が異なったりする時があるようです
次から説明していくやつとかは基本的に「オーバーロードによる解決」の方を使っていきます,書くの楽だし

has_template_xxx

依存名「T::xxx」がtemplateの場合にマッチするようなhas_xxxです
template< typename T >
struct has_template_xxx{
template< template< typename ...Args > class U > struct substitute_any{};
template< typename U > static true_t check( substitute_any<U::template xxx>* );
template< typename U > static false_t check( ... );
enum{ value = ( sizeof(check<T>(0)) == sizeof(true_t) ) };
};

template< typename T >
using has_xxx = has_template_xxx<T>;


カッチョいいのでVariadicTemplatesを使いました
 (書いてませんでしたがTemplateAliasesも使っているので,g++は4.7以降じゃないとコンパイル通らんすよね)

依存名がtemplateの場合に必要になる「template」キーワードの位置が初見だと気持ち悪いですよね,「U::template xxx」ですから









テストケースMSVC2013g++-4.8.1clang++-3.3
nothing000
type_int_xxx000
struct_xxx000
template_xxx111
field_int_xxx000
member_func_xxx000
static_member_func_xxx000
overloaded_member_func_xxx000
template_member_func_xxx000


ちなみにBoostさんの「BOOST_MPL_HAS_XXX_TEMPLATE_DEF」と「BOOST_MPL_HAS_XXX_TEMPLATE_NAMED_DEF」は,VariadicTemplatesを使わない環境でも動くように書いていて,どう対処しているのかというと
template< typename T >
struct has_template_xxx_no_variadic{
template< template< typename A0 > class U > struct substitute1{};
template< template< typename A0, typename A1 > class U > struct substitute2{};
template< template< typename A0, typename A1, typename A2 > class U > struct substitute3{};
// ~~以下もっと続く~~
template< typename U > static true_t check( substitute1<U::template xxx>* );
template< typename U > static true_t check( substitute2<U::template xxx>* );
template< typename U > static true_t check( substitute3<U::template xxx>* );
// ~~以下もっと続く~~
template< typename U > static false_t check( ... );
enum{ value = ( sizeof(check<T>(0)) == sizeof(true_t) ) };
};

template< typename T >
using has_xxx = has_template_xxx_no_variadic<T>;

のような感じです
なので,めちゃくちゃテンプレート引数が多いのにはマッチしないので,例えば
struct template4_xxx{
template< typename T0, typename T1, typename T2, typename T3 > struct xxx{};
};
struct template10_xxx{
template<
typename T0, typename T1, typename T2,
typename T3, typename T4, typename T5,
typename T6, typename T7, typename T8, typename T9
>
struct xxx{};
};

#include <boost/version.hpp>
#include <boost/mpl/has_xxx.hpp>
BOOST_MPL_HAS_XXX_TEMPLATE_NAMED_DEF( has_template_xxx_boost, xxx, false );

#include <iostream>
int main()
{
using namespace std;
cout << "boost_version : " << BOOST_LIB_VERSION << endl;
cout << "template4_xxx : " << has_template_xxx_boost<template4_xxx>::value << endl;
cout << "template10_xxx : " << has_template_xxx_boost<template10_xxx>::value << endl;
return 0;
}

とすると,「template4_xxx」の方はマッチするけど「template10_xxx」だとマッチしないなんていうことがある
 (ちなみに,検証に使用したBoostのバージョンは「1_46_1」と「1_55:revision86344」です)

has_member_xxx

メンバ変数とメンバ関数いずれかにマッチするようなhas_xxx
ちなみにこれが今回試した各コンパイラで一番結果がバラバラになりやすいです
template< typename T >
struct has_member_xxx{
template< typename U > static true_t check( decltype( &U::xxx )* );
template< typename U > static false_t check( ... );
enum{ value = ( sizeof(check<T>(0)) == sizeof(true_t) ) };
};

template< typename T >
using has_xxx = has_member_xxx<T>;

のーまるに書けばおそらくこれが順当だと思うんですが,メンバ関数までとれるのはちょっと意外ですね
それはさておき,実はこれMSVC2013及びMSVC2012ではコンパイル時にコンパイラが落ちるっていう挙動されてチェックができないです;;










テストケースMSVC2013g++-4.8.1clang++-3.3
nothing-00
type_int_xxx-00
struct_xxx-00
template_xxx-00
field_int_xxx-11
member_func_xxx-11
static_member_func_xxx-11
overloaded_member_func_xxx-00
template_member_func_xxx-00

いやぁVC駄目ですね,MSVCが落ちなければこれあってると思うんですけどね

ちなみにSFINAEによる解決方法をとってもMSVCはやらかしてくれます
template< typename T >
struct has_member_xxx_sfinae{
template< typename U > struct sfinae_helper{ typedef void type; };
template< typename U, typename V =void > struct check{ enum{ value =false }; };
template< typename U > struct check< U, typename sfinae_helper<decltype(&U::xxx)>::type >{ enum{ value =true }; };
enum{ value = ( check<T>::value ) };
};

template< typename T >
using has_xxx = has_member_xxx_sfinae<T>;

コンパイルは通ります,ただ全部falseになります










テストケースMSVC2013g++-4.8.1clang++-3.3
nothing000
type_int_xxx000
struct_xxx000
template_xxx000
field_int_xxx011
member_func_xxx011
static_member_func_xxx011
overloaded_member_func_xxx000
template_member_func_xxx000

いやぁVC駄目ですね(再度

MSVCは依存名「T::xxx」を直接記述するとどうやら落ちるっぽいのでこういう風に書きなおしたりしてみます
template< typename T >
struct has_member_xxx_b{
template< typename U > static U&& declval();
template< typename U > static true_t check( decltype( declval<U>().xxx )* );
template< typename U > static false_t check( ... );
enum{ value = ( sizeof(check<T>(0)) == sizeof(true_t) ) };
};

template< typename T >
using has_xxx = has_member_xxx_b<T>;

ちなみにこれ試したコンパイラ全てで結果が違うパターンです










テストケースMSVC2013g++-4.8.1clang++-3.3
nothing000
type_int_xxx000
struct_xxx000
template_xxx000
field_int_xxx111
member_func_xxx100
static_member_func_xxx110
overloaded_member_func_xxx000
template_member_func_xxx000

わあびみょう
しかしg++とclang++が違うのはなかなかないケースだと思うんですがね

ここで,なぜかこう書くとうまくいったりするんですね
template< typename T >
struct has_member_xxx_c{
template< typename U > static U&& declval();
template< typename U > static true_t check( decltype( declval<U>().xxx() )* );
template< typename U > static false_t check( ... );
enum{ value = ( sizeof(check<T>(0)) == sizeof(true_t) ) };
};

template< typename T >
using has_xxx = has_member_xxx_c<T>;











テストケースMSVC2013g++-4.8.1clang++-3.3
nothing000
type_int_xxx000
struct_xxx000
template_xxx000
field_int_xxx000
member_func_xxx111
static_member_func_xxx111
overloaded_member_func_xxx111
template_member_func_xxx000

僕らが見たかったのはこの表だ…!!!!

喜ぶのも束の間,実はこれもちょっとトリックがありまして,よく見てください
全てのコンパイラの結果は一致していますが,メンバ変数は「false」です,代わりにオーバーロードしているメンバ関数が「true」です

これは要するに「declval<T>.xxx()」が評価可能な式であるかどうかを見ているだけなので,メンバ変数はそこで落ちますし,全てのメンバ関数を網羅しているわけではないんですよね
事実,一引数以上をとるような関数(引数省略がないと仮定して)の場合はマッチしません
/*
has_member_xxx_cの定義とかは省略
*/

struct member_func_xxx{
void xxx();
};

struct member_func_p1_xxx{
void xxx( int );
};

struct static_member_func_xxx{
static void xxx();
};

struct static_member_func_p1_xxx{
static void xxx( int );
};

#include <iostream>
int main()
{
using namespace std;
cout << "member_func_xxx : " << has_member_xxx_c<member_func_xxx>::value << endl;
cout << "member_func_p1_xxx : " << has_member_xxx_c<member_func_p1_xxx>::value << endl;
cout << "static_member_func_xxx : " << has_member_xxx_c<static_member_func_xxx>::value << endl;
cout << "static_member_func_p1_xxx : " << has_member_xxx_c<static_member_func_p1_xxx>::value << endl;
/*
結果:
member_func_xxx : 1
member_func_p1_xxx : 0
static_member_func_xxx : 1
static_member_func_p1_xxx : 0
*/
return 0;
}

とても惜しい線をいってる気がする…

ところで,依存名の型「T::xxx」がマッチしない理由は,「typename」がついてないからだったりします
C++よくできてるな


なお,TrailingReturnTypeを使うとちょっと格好良くなります
template< typename T >
struct has_member_xxx_d{
template< typename U > static U&& declval();
template< typename U > static auto check( U v ) -> decltype( v.xxx(), true_t() );
static false_t check( ... );
enum{ value = ( sizeof(check(declval<T>())) == sizeof(true_t) ) };
};

template< typename T >
using has_xxx = has_member_xxx_d<T>;


has_overloaded_member_func_xxx

前節で見た内容ではメンバ関数だけど受かってないケースがあったと思います
そう,オーバーロードされたメンバ関数の場合「overloaded_member_func_xxx」ですね
なぜこれがfalseなのかというと,
”メンバ関数がある…けれど関数シグネチャが分からない→関数の実体が特定できない→戻り値も分からない→\コンパイルエラー/”
って流れになってしまうようです

そこで,「has_xxx時にテンプレートで引数の型を指定すればよいのでは…」という話になるとこうできると思いました
template< typename T, typename ...Args >
struct has_overloaded_member_func_xxx{
template< typename U > static U&& declval();
template< typename U > static true_t check( decltype( declval<U>().xxx( declval<Args>()... ) )* );
template< typename U > static false_t check( ... );
enum{ value = ( sizeof(check<T>(0)) == sizeof(true_t) ) };
};

template< typename T >
using has_xxx = has_overloaded_member_func_xxx<T,int>;

最後のTemplateAliasesで地味にint指定しているのに注意してください
これで,「<any> T::xxx( int )」な関数シグネチャの関数にマッチします










テストケースMSVC2013g++-4.8.1clang++-3.3
nothing000
type_int_xxx000
struct_xxx000
template_xxx000
field_int_xxx000
member_func_xxx000
static_member_func_xxx000
overloaded_member_func_xxx111
template_member_func_xxx000

マッチするようです(確信)
ただ,「declval()」で引数を作っているため,暗黙的変換の影響は受けますので,厳密な関数シグネチャのチェックにはならないですね

おわりに

と,「そんなのBoost.TTIにあるやん!」と言われても何もおかしくないような記事でした
ってかBoost.TTIには
  • メンバ関数があるかどうかのチェック

  • メンバ変数があるかどうかのチェック

  • 指定した型を持つメンバ変数があるかどうかのチェック

とかあるし,明らかに高性能なのは調べたら分かったんですが,何分なぜかMSVCだとコンパイル通らなくて追うのは色々断念しました,ふみゅう
全ては「&U:xxx」が通らないせいだと思うんですが,まじでこれはどうすればいいんですかね…

一応今回あげたのを組み合わせていけばある程度「メンバ変数はあるか」とかまで限定できるように思えるんですが,直接それを推定する記述の仕方がないので書いてないです
悔しい…

…それはそうと…比較内容多くて,地味に疲れたにゃす
それではまた…
スポンサーサイト

週アレ(4) JITアセンブラ短歌

週に一回アレしてアレしたい記事:四回目


この前充填分の記事書いたんだから,先週分の記事がないじゃないということに気付くまでに若干遅延した,何やってるんだオレはorz
某御人のブログで「アセンブラ短歌」なる世界(?)があることを知ってちょっと興味を持ったのでやってみた系の記事


さて本題

タイトルから不穏ですが,中身も若干不穏な気がします

JITがついてますが,元は「アセンブラ短歌」と呼ばれるもので,なんと機械語で5-7-5-7-7(単位:バイト)のように実際に動作するコードを詠む(組み立てる)ことを指すそうです

>アセンブラ短歌
http://kozos.jp/asm-tanka/

「アセンブラ短歌」は五・七・五・七・七の三十一バイト(みそひとバイト)から成る 機械語コードでプログラムを書いてみるという近未来の文化的趣味であり,近年, 国内のハッカー間で密かなブームが起きています.


しかもなんと!! この「アセンブラ短歌」,コンテストっぽいものもあるらしい

>アセンブラ短歌 募集要項
http://2013.seccon.jp/secconnagano-tanka.html


さて,このアセンブラ短歌を,「ユーザーが文字列を入力し,それをJITアセンブルすることでアセンブラ短歌にしてして実行する」ことをやってみようと思いました
つまり,「自動アセンブラ短歌生成器」のようなものですね.

ところで,”短歌”だし本来的には「決められた文字数の中に表現したいことを詰め込む」訳で,つまり「詠む人が表現したいことを詰め込む」ことに他ならない訳です.
実際,上記サイトの導入部分とか作品集には「incで押韻」とか「クワイン」と詠み人技巧的な趣向が織り込まれていて,確かに非常に芸術的な面を帯びています.

ということは,私がやろうとしてることには,「コード書く側(表現者)がユーザ(鑑賞者)の言うことに従う(ユーザの入力を用いる)とは何事だ!」というツッコミが成立しうるのですが….
…まあ…今回はそこは置いといて….


ただし,次のことはさすがにアセンブラ短歌として,コード内で完結していないのでやりませんでした.
 ・ユーザーからの入力文字列を受け取ったバッファのポインタを使う
  (puts等の関数に流し込むだけで,事実上いかなる文字列も出力可能になってしまう)



雛形

使った環境とかは以下

  • Windows/x86

  • C++(コンパイラ:VS2012EE)

  • JITアセンブラ:Xbyak

  •   x86,x64マシン語生成用のC++ライブラリ
      ちょっとしたJITコードを書く時は最強のライブラリだと信じています
      今回使ったバージョンはver4.21,今日(2013/10/29)における最新



正直なところ私はそんなにアセンブラには詳しくないので,まず関数コールとかどうやるんだっけとかから始まりました

呼び出し規約は「cdecl」を使いました,デフォルトだとこの設定ですよね確か
Wikipediaさんによると「cdecl」は

  • 引数は右から左に順にスタックにpushする

  • 戻り値はEAXレジスタ

  • calleeではEAX,ECX,EDXレジスタは破壊的に用いて良い

  • スタックポインタの処理はcallerの責任(pushした引数はcallerが解放する)



これを使って,「ユーザーが入力した文字列をputsを用いて表示するだけのコード」をJITアセンブルする例が次
#include <stdio.h>
#include <iostream>
#include <string>

#include <xbyak/xbyak.h>

class codegen : public Xbyak::CodeGenerator
{
public :
explicit codegen( const std::string& s )
{
// 引数をpush:ただし,sの寿命には気をつける
push( (unsigned int)( s.c_str() ) );
// puts関数のコール
call( &puts );
// 引数は自分でpop(cdecl)
pop( eax );
// return
ret();
}
};

int main()
{
using namespace std;

std::string s;
for( ; ; )
{
cout << "文字列を入力してください(終了:q):";
s.clear();
getline( cin, s );
if ( s == "q" ) break;

codegen cg( s );
const auto code = reinterpret_cast<void(*)()>( cg.getCode() );
// invoke
code();
}

return 0;
}


ここまでくるのに微妙に四苦八苦.
上で「ユーザーからの入力文字列を受け取ったバッファのポインタを使う」なんて言ってますが,ここではとりあえず動くひな形を考えているためその設定は一旦忘れてください

しかしここまでくるとあとは組み立てるだけなので簡単だと思っていました,予想に反してというかなんというか,ここから次のセクションまでに割と苦労してますω



四文字以下の文字列表示

最初に「アセンブラ短歌」を知ったとき,「これを使って”neko”と表示しよう」としか考えていませんでした(雑
しかし,「アセンブラ短歌」が思ったより奥が深かったので(押韻とかのテクニックとかね),「JIT使ってユーザの入力を表示するコードを生成しよう」に方向転換しようと思い,このとき「ユーザの入力が何もなかったら”neko”でいいか」ということにしました
さて,「アセンブラ短歌」はかなり機械語的にナイーブです,出力する文字数に応じて平気で機械語の構成が変わりそうです
そこで今回はとりあえずデフォで表示しようと思った”neko”が四文字なので,ユーザの入力も四文字(またはそれ以下)にすることにしました
文字数が多い分には,必要なところはNULL文字で埋めればいいわけですから,問題はなさそうですからね

#include <stdio.h>
#include <iostream>
#include <string>
#include <xbyak/xbyak.h>

class codegen : public Xbyak::CodeGenerator
{
public :
explicit codegen( const std::string& _s )
{
// 4バイト足りない所はNULL文字うめる
// 負値のバイトは化けるのでガード
int s[4] ={ 0, 0, 0, 0 };
for( std::size_t i=0; i<_s.size(); ++i )
{
s[i] = _s[i] & 0xff;
}

// 右の数字は機械語のバイト数
mov( eax, (s[3]<<8)|s[2] );// 5

mov( ecx, (s[1]<<8)|s[0] );// 5
xor( edx, edx );// 2

shl( eax, 16 );// 3
or( eax, ecx );// 2

push( edx );// 1
push( eax );// 1
mov( edx, (unsigned int)&puts );// 5

push( esp );// 1
call( edx );// 2
pop( eax );// 1
pop( eax );// 1
pop( eax );// 1
ret();// 1
}
};

int main()
{
using namespace std;

std::string s;
for( ; ; )
{
cout << "4バイト以下の文字列を入力してください(終了:q):";
s.clear();
getline( cin, s );
if ( s == "q" ) break;
if ( s.empty() ) s ="neko";
if ( s.size() > 4 ) { cout << "4バイト超えてるよ" << endl; continue; }

codegen cg( s );
const auto code = reinterpret_cast<void(*)()>( cg.getCode() );
// dump code
cout << "--------code--------" << endl;
int sep[] = { 5, 7, 5, 7, 7, -1 };
int* psep = sep;
for( std::size_t i=0; i<cg.getSize(); ++i )
{
printf( "%02x ", reinterpret_cast<unsigned char*>( code )[i] );
if ( !(--(*psep)) ) { cout << endl; ++psep; }
}
cout << endl;
// invoke
cout << "--------result--------" << endl;
code();
}

return 0;
}


やってみて思ったんですが,JIT使うと実行時にアセンブリ見れて便利ですね,Xbyak様々です
ただこのままだとどのバイトが命令区切りなのか分からないので,実際コーディングする時は自分で適当に「getSize()」しながらチェックしてました,ハヒー

大変だったのは,いくつか交換可能な命令に分割し,それを組み合わせる形で31バイトになるように調整するところですかね…

あと元ページだとLinuxのシステムコールで「int 0x80」で割り込みして渡すっていう手法なんですが,今回はナイーブに「puts」の関数アドレスとってきて渡してます
Windowsだとこの方法しかないと勝手に思い込んでいたんですが,このコード書き終えてから調べたところ,Windowsでも「int 0x21」でシステムコールに割り込みして渡せるらしいですね…あんなに「この関数コール分の5バイトがクソ」だと考えていたのは何だったのか(´・ω・`)
今更だから直さないけど

なお,一応これ手法的には,「入力文字列の上位16ビットと下位16ビットをビットORしてるだけ」なので,理論的にも「四文字以下の文字列全て」を扱える形であることがわかりますね
まあ…そもそも「入力文字列の上位16ビットと下位16ビットをビットORしてるだけ」ら辺が無駄なことをしているとかいうツッコミをされてしまうと辛いんですがω


実行結果

jit_tanka_res.jpg
ねこー




おわりに

アセンブラ全く詳しくなかったので色々面白かった,「オメーそんなにバイトくうのかよ!」とかありましたし,少しは機械語に対する理解が深まったかな
また,正直最初は「たった31バイトに趣向なんて詰め込めるんだろうか…」って思ってたんですが,やってみると31バイトって”アセンブラ上”では何とか色々できる範囲にあるのかなと思いました
「cdecl」呼び出し規約なので一部レジスタ全て破壊してもいいですし,好き勝手に荒らしてとりあえず動くように処理書けるのは素晴らしいですね

アセンブラ短歌,31バイトという非常にキツい制限ではありますが,短いからこそ突き詰める部分とかありますし,特に目的が非常に単純なので,アセンブラ始めようと思っている人などにはちょうどいい練習問題なのかもしれない
面白いものができたらできたで楽しいですしね
そんな感じで

週アレ(3) コンパイラ最適化についてちょっと見てみる

週に一回アレしてアレしたかった記事:三回目

言い訳させていただくと,会議とミーティングと会議とで先週更新できませんでした
なので充填分の記事です,内容的に大分軽めな話にする予定ですが


さて,コンパイラ最適化について

コンパイラの出力最適化のことで,コンパイラが吐き出す機械語やバイトコードといったものを,実行時間やメモリ使用量などの観点から効率よくしていくことを目的とした手法を指します

具体的には”最適化”なので,ある明確な目的に沿った指標を設定し,その指標で最も良いとされる出力を得るような処理になります

ただ,コンパイラ最適化で結構問題になるのは,解析範囲の大きさです
最適化問題の際対象となる範囲が広くしすぎれば現実的な時間で処理できなくなりますし,かと言って範囲を狭めてしまうと十分な最適化が行えない可能性があります
ということで,ここでは解析範囲の大きさにフォーカスしていくつか最適化手法を見ていこうかなーと

ところで,コンパイラ最適化は非常に多種多様で,言語により制約が増やせたり増やせなかったりと非常に一概に述べることが難しいので,かなり一般的なお話になることはお許しください



覗き穴的最適化

”覗き穴的”という名前からなんとなく察することができるように,直近の処理を見た場合に可能な最適化を指します

・定数畳み込み
コンパイル時に計算できるものはコンパイル時に計算してしまって,実行時の負担を減らそうという考え方に沿った最適化です

具体的な例では
var v = 1+3


var v = 4

とかでしょう

整数や浮動小数点などはもちろん,文字列なども場合によっては定数畳み込みが可能でしょう
結局話としては,「コンパイル時に定まる値と演算」ならば定数畳み込みが可能である,というところでしょうか(C++のconstexprはその最たる例だと思います)

整数と浮動小数点に限った最適化でも,最終的にコンピュータ内の計算がそこに行き着くので,そこそこ性能があがるんじゃないかなぁとか思ってます


・演算子強度低減
計算コストが高い演算を,同じ結果が得られる計算コストの低い演算に置き換えてしまうような最適化で,最も有名なのは恐らく次の例でしょう

v /= 2


v >>= 2

となったりします

これはCPU内では整数が2進数によって表現されていることを利用しているわけですが,場合によってはよりCPUに依存した最適化もかかったりします


・定数伝搬
話は定数畳み込みと同じですが,ちょっとだけスコープが広いです
基本的には定数畳み込みと同じく,コンパイル時に出来る演算は先にしてしまおうというものです
その中で,計算できるものがある場合はそれらが連鎖的に計算されていくことを指すようです

var v = 10
var u = v+10
var x = u+20


var v = 10
var u = 20
var x = 40

のように置き換わったりします


・共通式削除
共通する計算式を抜き出し,一回計算を行って結果を再利用することで最適化しようとする処理です

var v = (x+y)*2
var u = (x+y)*5


var z = x+y
var v = z*2
var u = z*3

となったりします

こういうのはプログラマの方で処理したりしますが,コンパイラがやってくれると嬉しいですね


局所最適化

さっきまでのも”局所”と言えば”局所”なんですが,この場合の”局所”はブロックやプロシージャ単位での範囲を指すことが多いように思います

ということで,ブロック単位での解析が主になります


・デッドコード削除
実行されない命令は削除することで,無駄を無くそうとする最適化です
最適化と言うと大層なものの感じがしますが,やってることは単純ですよね

var v = 10
if ( v > 10 ) then
~~何かしらの処理~~
end


var v = 10

に置き換わったりします

他には,プロシージャ単位では使用されない変数の削除なども含まれるでしょう


・末尾再帰最適化
再帰関数の最後に行われる処理が再帰的呼び出しの場合,その再帰呼び出しをループ(ジャンプなど)コストが低い計算に置き換えた最適化を指します
有名な例は階乗計算でしょうか

function fact n
if ( n < 2 ) then
return 1
end
return n * fact(n-1)

をループに書きなおすと,
function fact n
if ( n < 2 ) then
return 1
end
var r = n
while n>0
r *= n
n -= 1
end
return r

のような感じになったりします




なんかとってもライトな内容になってしまった,ちょっと反省(´・ω・`)

週アレ(2) 計算式からみる描画モード(後編)

週に一回アレしてアレする記事:二回目

前回「週アレ(1) 計算式からみる描画モード(前編)」に引き続き他の描画モードについても見ていきましょう
前回の記事を参照してない方は,前回から見た方がこの後分かりやすいと思うので戻ってみること推奨



ソフトライト

blend_mode_softlight.jpg

ソフトライトは実はアプリケーション毎に微妙に計算式(実装)が異なるらしいそうで,次に示すのはフォトショップでのソフトライトの計算式



基本的には名前から想像できるように,「ハードライトの効果を弱めたものである」描画モードです
そういった面から見る効果からは裏腹に,計算式からはガンマ補正との関係があったりと,深入りしてもまだまだ奥が深そうな描画モードだったりするようです

例えば適当に基本色,合成色,結果色の関係を見れば
 ・合成色が0のとき,γ=2のときのガンマ補正と一致する(但し係数1)
 ・合成色が0.5のとき,γ=1のときのガンマ補正と一致する(但し係数1):基本色と同じになる
 ・合成色が1のとき,γ=0.5のときのガンマ補正と一致する(但し係数1)
となります

ガンマ補正の観点から言えば,
 ・合成色が0~0.5のときは,γ=2のガンマ補正とγ=1のガンマ補正を,合成色によって内分する
 ・合成色が0.5~1のときは,γ=1のガンマ補正とγ=0.5のガンマ補正を,合成色によって内分する
と見ると計算式的に直観的だったりします

内分の係数を抜き取って,Baseに注目しやすくするとこういうことですね
blend_softlight_alpha2.jpg

 (最初からこの形に見えた人は相当だと思います…)

さて,ガンマ補正はγが大きいほど暗く,γが小さいほど明るくなるような補正です
γが1のときは元の色に変化はありません

合成色が黒のときはγが大きく,合成色が白のときはγが小さくなり,かつ合成色が灰色のときは基本色に変化がないような処理をしています
これよにってコントラストをあげるように処理していますが,最終的な輝度の変化範囲がγ=2とγ=0.5によって決定されているため,それほど劇的な変化をしないようにしていると言えます

印象的には,「(ハードライトほどではないが)コントラストがちょっとあがる」と覚えておけばいいでしょう



ビビッドライト

blend_mode_vividlight.jpg

Vividは「鮮烈な,強烈な」といった意味合いの単語ですが,単語通りなかなか劇的な変化を呈します
さて,計算式についてですが

@2015/05/01
計算式が間違っていたので修正

と,ちゃんと書き下すととても長くなってシンドイので,もう少し概念的に述べたいと思います

ハードライトが次のような計算式で表せることは示しました

これは,合成色の輝度によって「乗算」か「スクリーン」かを条件分岐していました

ビビッドライトでは,合成色の輝度によって「焼きこみカラー」か「覆い焼きカラー」かを条件分岐する描画モードです

前回の記事中で,「焼き込みカラー」は「乗算」よりも,「覆い焼きカラー」は「スクリーン」よりも強烈な効果であることは述べました
なので,単純にハードライトよりも強くコントラストをあげる描画モードであると言えます

印象的にはまぁ,「すごくコントラストあがる」でいいと思うんですよね



リニアライト

blend_mode_linearlight.jpg


ライト系が続いているので察してしまった方もおられるかもしれませんが,
リニアライトでは,合成色の輝度によって「焼き込みリニア」か「覆い焼きリニア」かを条件分岐する描画モードです

「焼き込みリニア」は「乗算」よりも強烈ですが,「焼き込みカラー」よりは強烈ではありません
また,「覆い焼きリニア」に関しても同様に,「スクリーン」よりも強烈ですが「覆い焼きカラー」よりも強烈ではありません

なので,ハードライトよりも強くコントラストがあがるが,ビビッドライトほど強くはない描画モードです

うーん,印象的には「けっこうコントラストあがる」ですかね






さて,ここからは印象とかで語りづらい,描画モードの中でも特殊な部類に入るもの

差の絶対値

blend_mode_absdif.jpg


読んで字の如く,輝度の差の絶対値です

全体に効果をだすというよりは,ちょっと特殊な効果をだすときに用いる方が多いような気がします
blend_mode_abs_ex.jpg

また,その特性「完全に重なったら真っ黒になる」ことから,絵の差分合わせとかにも使えたりしますね
abs_ex_match.jpg
ウサウサ!



減算

blend_mode_subtract.jpg

差の絶対値の一方向版といったところでしょう


こちらも特殊な用途を想定することの方がおおいでしょう



除外

blend_mode_exclusion.jpg


見た目からいうとまたビミョーなのがきましたが,数式的には「あーそれ」的なやつですね

すなわち,「相加平均から相乗平均を引いたものを2倍したもの」です

「差の絶対値」と比較すると,それよりも若干明るくなるような効果を持っているようです



除算

blend_mode_division.jpg


除算です,除算ですね…

合成色が暗ければ暗いほど結果は明るくなり,合成色が明るければ明るいほど結果は基本色に一致しはじめます

しかし,明るくしたい時は「スクリーン」とか「覆い焼きリニア」とか使いますし,イマイチどう使えばいいのかよく分からない描画モードだと思っています

…ドウナンデショウネェそこんとこ…







以下,さらにその他の描画モード
私がそんなに使わないばっかりにかなり雑な扱いですが,式的に面白くない(というより解析的に綺麗じゃない)ので許してください…

比較(暗)

blend_mode_darken.jpg


基本色と合成色とで暗い方を結果とします

グラデーションでの結果(上図)を見ると,処理の割には面白い結果が得られてますね


比較(明)

blend_mode_darken.jpg


基本色と合成色とで明るい方を結果とします


ピンライト

blend_mode_pinlight.jpg


ライト系です,○○ライトという名前です

ピンライトでは,合成色の輝度によって「比較(暗)」か「比較(明)」かを条件分岐する描画モードです

なかなか興味深い結果を呈しますね…
とってもうまくこれを使うと,かなり複雑な図が作れそうだが果たして…



で,どれを覚えておけばいいの?

ここまで書いておいて難ですが,個人的には

Must
 ・スクリーン
 ・オーバーレイ

Good to know
 ・覆い焼きリニア
 ・乗算

Other
 ~その他全部~

ただ,この記事の目的は用途と利便性は置いといて,数式追いつつ様々な描画モードの性質について調べてまとめたものです
今まで使ったことがなかった描画モードを使うきっかけや,色々な描画モードを使って独自の効果を実現できる助けになることを願っています


おわりに

以上ヒヨッコがレポートする内容でした,ピヨピヨ
間違い等発見しましたら突っ込んで頂けると幸いです

…ってか描画モード思ったより多くて,疲れたピヨ…でもまだ残ってるピヨ…ピヨーピヨー

今回扱っていない描画モード達「輝度」とか「彩度」とか「色相」とか「カラー」とかは,またいずれかの機会に…


Refs

参考にさせて頂いたサイト等:追記含む
Wiki - Blend Modes
Wiki - Dodging and Burning
Insight into Photoshop 7.0 Blending Modes
Photoshopのブレンドモードがよく分からない人のための手引き その①
ブレンドモード詳説
Photoshopの描画モード(ブレンドモード)を理解するための、画像合成は計算だという話

それにしても先人様方の記事分かりやすくてビビる…







Appendix:本記事中で言及しなかった事項

言及する必要がなかった,あるいは優先度的に必要なかった,というのが本質な残念な原稿
上から下に向けって本記事に関して無益な情報に変化していきます


ピクセル量子化の話

記事中では「黒は0,白は1」と記述していて連続量っぽく見えますが,この輝度の範囲は正規化されたあとの話で,PC上では現実問題有限桁の数しか扱えないので連続量じゃなくて離散量です
本記事中にでてくる画像はすべてRGB各8ビットを割り当てているモードにしてまして,各チャンネル256通り,RGB全体では256の3乗で16,777,216通り表せます,所謂トゥルーカラーとよばれるやつですね

なお,記事中にでてくる輝度のグラデーションは縦横256のサイズ(にしたハズ)なので,1ピクセル隣にいくと輝度が1変わります
…まぁ,後で非可逆圧縮のJPEGで吐き出していたことに気付いたので,作ってる最中の苦労はほとんど意味なかったんですけど…



RGB表色系とCMY表色系の話

「輝度が明度と一致しない」のところで触れようと思ってたんですが,説明が色々適当なので付録入り

RGB表色系はPCとかディスプレイとかでよく使われる表色系,加法混色,輝度と明度が一致
CMYK表色系は印刷するときに使われる表色系,減法混色,輝度と明度は不一致
これはCMYにK(Key,黒色)を加えたものですが,もとのCMYを考えてみます,これも減法混色

加法混色と減法混色とで,逆になっているので気づいた人居る気がしますが,下図のような関係だったりします,大まかには
color_rgb_cmyk.jpg

なので,CMYでの輝度ってのはRGBをただ反対にしたもので,「暗さ」に一致した指標だったりします
ただ,CMYKではK(Key)が加わっているため,こんなに簡単に変換できませんが(できていたらCMYK表色系はなかったかもしれませんね)…



基本色と合成色の表記について

Adobeさん的には

 基本色は、画像内の元のカラーです。
 合成色は、ペイントツールまたは編集ツールで適用されるカラーです。
 結果色は、合成によって生成されるカラーです。

とのことで,ここから名前については引っ張ってきています.

なお,英語表記ではAdobeさん的には「

The base color is the original color in the image.
The blend color is the color being applied with the painting or editing tool.
The result color is the color resulting from the blend.

というわけで,基本色(Base Color),合成色(Blend Color),結果色(Result Color)という言い方らしいです.

本記事中では合成色(Ref)としてますが,これは単純に「基本色と合成色どっちもBから始まるから面倒くせぇな」ぐらいの意図しかないです;;
あと「計算中に参照する情報だしReferentialでいいだろ」っていう適当な解釈でもありますが,後々かんがえるとこれはどちらかというと補助的な情報に対する形容詞なので,今回だと不適切な気がするんですよねぇ…今更なので直さないですが(

週アレ(1) 計算式からみる描画モード(前編)

週に一回アレしてアレする記事:一回目


あんまり需要がなさそうな,だけど誰かもう少し不思議に思ってもよさそうな描画モードの話
 (とかく「この描画モードを指定するとこうなる!」という印象ベースの話は聞きますが,そうなる裏付けとなる理論的な話ってばあまり聞かないんですよね…私だけ…?><)


描画モードの処理について

ここで言う描画モードはフォトショやクリペなど,レイヤーという機能が存在するペイント系にはほぼ必ずあると言っても過言ではない,レイヤーに設定できる「描画モード(ブレンドモード)」についてです

描画モードの本質は要するに2つのレイヤーがあったとき,ピクセルの色をどう混ぜあわせて最終的な色にするか,その計算方法を指定するものです(下図)
byouga_base2.jpg

便宜的に下のレイヤーのピクセルを基本色,上のレイヤーのピクセルを合成色とします
こう聞くと,「あれ…でも描画モードって一つのレイヤーに設定するものだし…”2つのレイヤーに対して指定する”ってなんか違くない?」と思う人も居ると思います

その通りです
なので正確には,「レイヤーの描画モードは,”そのレイヤーまでの描画結果”と”そのレイヤーのピクセル”とをどう合成するかを指定する」ものだと思った方がいいです

例えば,3枚のレイヤーがあった場合は下図のように処理される,ということです
byouga_base3.jpg
「なんか計算」の部分は省きました,じゃないと画像大きくなっちゃうので…><

となると,どのレイヤーよりも最初に描画されるレイヤーがあるべきで,大抵は新しくファイルを開いた時に存在する「背景」レイヤーがそれにあたります
ちなみに,背景レイヤーも非描画に設定可能なので,その場合はどうなるんでしょうね…
 (「どのピクセルもα値0のレイヤー」が仮想的に一番下に存在すると仮定したりするんでしょうね,きっと:未検証)

この処理フローに例外が加わるのは,「下のレイヤーでクリッピング」などの機能を利用した場合ですが,その辺は今回のお題から逸れるので触れません


ピクセルの色表現について

まずこれについて述べておかないと,後々話がこじれる気がした

レイヤー内の各ピクセルは(表色系により異なりますが)何らかの値を持ちます
(今回は面倒なのでアルファ値については考えないことにします)

(RGB表色系ではRGB毎に)「どのくらい明るいか」を表す値、輝度値が入っています
byouga_intensity.jpg

ピクセル値(輝度値)が「1」の時が白に該当し,「0」の時が黒に該当します

ただしこれだけでは黒と白だけで,赤や青といった色は表現できません
そこで,パソコンなどでは「赤(R)」,「緑(G)」,「青(B)」の三つの”チャンネル”というものを考えます(RGB表色系)
各ピクセルは三つのチャンネル毎のピクセルをもっており,それぞれの組み合わせで色を表現します
byouga_intensity2.jpg
↑こんな感じに

基本的に描画モードによる演算は,各チャンネルに対して独立に処理されます
”基本的に”なので,例外的な描画モードも存在するんですが…
まぁそれはおいおい説明していきましょう


描画モード:通常

特に書くことがないんですが,図の見方を説明する上で有用なので書きます
blend_mode_normal.jpg

左側のブロックがベースとなるレイヤー(基本色)となります
真ん中のブロックが上から描画するレイヤー(合成色)となります
そして,右側のブロックが基本色と合成色を描画モードによる計算した結果の輝度です

基本色は縦方向にグラデーション(0~1),合成色は横方向にグラデーション(0~1)しているので,あらゆる基本色と合成色の組み合わせがこれで見れます
(なお,基本色と合成色はいずれもすべてアルファ値は1とします)

描画モード「通常」は上から描画するレイヤーの色が最終結果となりますので,計算式的には以下のようになります


ところで,「基本色+合成色=結果」みたいな図はたまにみるんですが,どちらかというと「基本色に合成色を作用させる」処理であり,必ずしも「基本色」と「合成色」が交換可能な計算ばかりではないため,敢えて「基本色」←「合成色」という書き方をしています


乗算

お次は「乗算」
blend_mode_mul.jpg


「乗算」は計算式を見るとそのままであることが分かると思います

この計算式から,
 ・基本色または合成色が0(黒)であるピクセルは,すべて0(黒)となる
 ・基本色が1(白)の場合は合成色が,合成色が1(白)の場合は基本色が結果となる
事がわかります

注目すべきはこの計算によって「どのような色の組み合わせも,基本色(あるいは合成色)より明るくならない」ことです
なので「なんとなく暗くしたい時に使う」描画モードといえるでしょう(雑


スクリーン

そして「スクリーン」
blend_mode_screen.jpg


「乗算」に比べ非常にややこしく感じます

とりあえず数式から分かることを書きましょう
 ・基本色または合成色が1(白)であるピクセルは,すべて1(白)となる
 ・基本色が0(黒)の場合は合成色が,合成色が0(黒)の場合は基本色が結果となる

…さっき似たようなのを見ましたね
実は今まで「黒をピクセル値0,白をピクセル値1」と置いていましたが,これを逆にして「黒を1,白を0」と置いた場合の「乗算」が,元の系での「スクリーン」に一致します

と言われてもすぐにピンとこないと思いますが,数式上ではそう解釈できる部分が存在します
「黒を白に,白を黒に対応させる」操作は「0を1に,1を0にする操作」です

ピクセル値を

と置いた時,「0を1に,1を0にする操作」は

で表されます

上の数式内ではとかとかの部分ですね

この「黒を1,白を0」とするのは他の描画モードでもでてくる考えです
ところで,「黒を1,白を0」としている時は,一体どんな指標が1だと黒で,0だと白なんでしょう
RGB表色系ではピクセル値は明るさと比例する指標であり,それとは逆なのだから,「黒を1,白を0」はいわば「暗さ」を表す指標だと言えそうです
blend_ld_rel.png
 (ちなみに,「黒を1,白を0」としても,便宜上「黒はピクセル値1,白をピクセル値0」という言い方をされます
  なお,表色系によって”ピクセル値”(PixelValue)と”明るさ”(明度:Brightnessや輝度:Intensity)という指標は一致しないこともあるので注意

とかく「乗算」と「スクリーン」は並べて説明されることが多いですが,その理由は計算式的にも密接な繋がりがあるからでした

「乗算」とは逆に,この計算によって「どのような色の組み合わせも,基本色(あるいは合成色)より暗くならない」性質を持ちます

なので「なんとなく明るくしたい時に使う」描画モードといえるでしょう(雑*2


焼き込みリニア

blend_mode_mul_linear.jpg


足して1引く操作です,とだけ言うと「あっそう」で終わってしまうんですが

この式だけ見てても分からないですが,「乗算」と「スクリーン」の関係のように,この式も黒と白の立ち位置を反転させるとその意味するところが見えてきます



「黒を白に,白を黒に対応させる操作」は,「0を1に,1を0にする操作」であり,これはにより実現することは述べましたね
そう思って見ると上記の式は丁度,「黒を1,白を0とした時の加算」に対応します
言い方を変えれば,「暗さを足している」とも言えますね

ただ,自然界では光が重なる現象(後述:覆い焼きリニア(加算))はあっても,光が定量吸収される現象はそうそうないので(少なくとも目で見てる分には),この描画モードがあまり使われない理由はその辺だと思うんですよね…

「乗算」と同じく「どのような色の組み合わせも,基本色(あるいは合成色)より明るくならない」性質を持ちます,「暗さを足している」という観点からも納得できるかと思います ・
印象的には,「乗算よりも暗くなる描画モード」と覚えておけば必要十分だと思います


覆い焼きリニア(加算)

blend_mode_add.jpg


非常に直観的な式ですね

先に述べた「焼きこみリニア」と対になる描画モードです
最終的な式だけを見比べた場合,「焼きこみリニア」とは1引くかどうかが違うだけだし,それだけで意味的には黒と白の立ち位置が反転するわけで,なかなか不思議ですよね

さて,「覆い焼きリニア」ですが「焼き込みリニア」とは違って非常によく使われます
特に「炎」の表現に使われ,およそ「光りモノ」はこれを使うと大体綺麗になります,「星空」とか「流星」とかね
基本的にはそういった光りモノの「露光」の効果をだす際に使うものだと思ってます

「スクリーン」と同じく「どのような色の組み合わせも,基本色(あるいは合成色)より暗くならない」性質を持ちます
特に「炎」とかに使う,「スクリーンよりも強く光る」と覚えておけばいいでしょう



焼き込みカラー

どうでもいいですが,この描画モードの紹介順序は特に意味はないです,若干説明がつけやすい描画モード順にはなっていると思いますが
blend_mode_burn_color.jpg


ここら辺からちょっと意味を見出そうとすると難しくなってきます

数式的にそのまま見ると,白と黒の役割を入れ替えるのを反転(Inverting)とすると,「基本色を反転したものを合成色で割り,その結果を反転する」操作となります,ややこしいですね

この数式中で合成色の役割を考えると,どうやら「基本色を反転したもの」をどれくらい小さくするか(?),という指標だと捉えられます

…言い方が悪かったですが,
 ・合成色が1の時,「基本色を反転したもの」に変化はない
 ・合成色が0の時,「基本色を反転したもの」は無限大に倍増される
となります

「基本色を反転したもの」はすなわち「基本色の暗さ」の指標です
なのでこの描画モードは,言い換えれば「基本色の暗さを合成色の暗さで適当に倍増させる」ものだと思える…んだと思います(二重思
最後の反転は元の系に戻すだけの処理なのでどうでもいいですしね…

解釈が難しいのではっきりこうだ! と言えないですが,「基本色の暗さを合成色の暗さに応じて増大する」描画モードで,通常「乗算」や「焼き込みリニア」よりも強烈な効果になります
印象的には「なんか凄くなる」でいいと思います(適当


覆い焼きカラー

blend_mode_dodge_color.jpg


流れ的に分かると思いますが,「焼きこみカラー」と対をなす描画モードです
話の流れも「焼きこみカラー」と対をなします(断言

で,
 ・合成色が1の時,「基本色」は無限大に増大される
 ・合成色が0の時,「基本色」に変化はない
となりますね

「基本色の明るさを合成色の明るさに応じて増大する」描画モードで,通常「スクリーン」や「覆い焼きリニア」よりも強烈な効果になります
印象的にはやっぱり「なんか凄くなる」ようです




さて,ここからは条件分岐が必要となる,非線形な描画モードについて説明していきます

オーバーレイ

blend_mode_overlay.jpg


基本色により条件分岐が発生していますが,砕いてみていくとそんなに難しくなかったりします

基本色が0.5未満の時は「乗算」と同じような計算を,基本色が0.5以上の時は「スクリーン」と同じような計算をします
2倍している箇所は,「乗算」の時は基本色の0~0.5の範囲を0~1に,「スクリーン」の時は基本色の暗さ0~0.5を0~1に,それぞれスケーリング(正規化)している処理です

さて,そうするとこの描画モードは
 ・基本色が0.5未満(ある程度暗い)時は上のレイヤーを描画モード「乗算」として処理する
 ・基本色が0.5以上(ある程度明るい)時は上のレイヤーを描画モード「スクリーン」として処理する
ことになります

「乗算」と「スクリーン」の処理結果を思い出してください
「乗算」は「なんか暗くする」,「スクリーン」は「なんか明るくする」効果でしたね

つまり,基本色が暗いピクセルはより暗く,明るいピクセルはより明るくする効果がある描画モードが「オーバーレイ」となります
描画モードを「オーバーレイ」にした場合,「下のレイヤーのコントラストは下がらない」という性質を持ちます

基本的には無理にでもコントラスト上がるので,印象的には「なんか格好良く(ドギツク)なる」といったところでしょうか

ちなみに,同じレイヤーを「オーバーレイ」にして重ねたもの(上段)と,レイヤーに対してトーンカーブによりコントラストを上げたもの(下段)を並べてみました(下図)
overlay_contrast_comp.jpg
殆ど同じですね!
(というより,そうなるようにトーンカーブ調整しました,と言いますか)

絵描き終わったとき,表示レイヤーをコピーして「オーバーレイ」に設定するだけで,簡単にコントラストが上がる絵の出来上がり! とかいう小技は私割とよくやります
(「Ctrl+A」で全体選択→「Ctrl+Shift+C」で表示レイヤーコピー→「Ctrl+V」でレイヤー貼り付け→「オーバーレイに設定」でコントラストあがる→「不透明度調整」でいい感じに)


ハードライト

blend_mode_hard_light.jpg


「オーバーレイ」と名前は似てないですが,実は「オーバーレイ」における基本色と合成色の役割を逆にすると一致します

~~割愛~~

基本的には無理にでもコントラスト上がるので,印象的には「なんか格好良く(ドギツク)なる」といったところでしょうか(再








この辺で筆者が力尽きたので,残っている描画モードは次回「週アレ(2) 計算式からみる描画モード(後編)」に託します…来週の私が頑張ってまとめてくれることに期待します(丸投げ

とりあえずここまででまとめに入ります


ここまでのまとめ

今回は割とよく使うと思われる描画モードについて,それぞれの計算式と効果について大雑把に触れてきました

「覆い焼きリニア(加算)」や「焼きこみリニア」は名前からもなんとなく直観的に理解している方は居られたかもしれませんが,「乗算」や「スクリーン」などは計算式まで見て使っていた方は少なかったんじゃないかと思います

実際問題これらの描画モードについて計算式から理解している必要はないことは確かです
一方で,計算式を知っているからこそ,「こういう効果をつけるためには,この描画モードでこういう色付けをすれば出来そうだ」といった逆算できたりするので,決してこれらの知識が無駄になることはないと思っています

特に今回の記事ではかなり基本的な描画モードについても触れていますので,よく使う方は一度見ておいてもいいんじゃないかなと思います

週アレまとめ

週に一回(多少雑でも)何か興味持ったことについてまとめよう,と思い立ってのまとめ記事
いつまで続くか不透明ですが,出来る限り続けていこうと思います

FC2Ad

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。