Processing の filter(BLUR) 関数でぼやけさせるとき、影が黒っぽくなってしまう現象をコントロールしてみます。
👉 Read this article in English.
前回の記事「僕の Processing が p5.js の drawingContext に負けるはずがない!」で、白い円をランダムにぼやけさせるコードを書きました。
そのときに生成された画像がこちらです。
元々こちらの例にあるような p5.js の drawingContext での効果を模倣しようと作ったものです。
off / on
— Shunsuke Takawo (@takawo) August 9, 2022
drawingContext.filter = "blur(" + int(random(15)) + "px)" pic.twitter.com/0SvEqQOrvu
このときは、p5.js での結果と Processing の結果でなんとなくニュアンスが違うなと、単にそう思っていただけでした。
影が黒くなるわけ
このニュアンスの違いについて、えみーんさんから Tweet をいただき、「あ、私のは黒い影が出てるんだ」と気付きました。
出来ました!
— え ͤみ ͫ ͤー ͤん ͛ (@emeen231) September 30, 2022
circle単位でfilter(BLUR, ~);をかけるのではなくPGraphicsごとに実行すると円の周りが黒くなることなく重なってくれます pic.twitter.com/Z4w7qo1fOb
なぜ黒い影が出てしまうのか?「黒」で思い当たるのはコードのこの部分です。
p.background(0.0, 0.0);
全体のコードはこちらです。
/**
* ランダムなボケのレイヤーを重ねる
*
* @author @deconbatch
* @version 0.1
* @license CC0
* Processing 3.5.3
* created 2022.09.23
*/
void setup(){
size(640, 640);
int layerNum = 12; // レイヤー数
background(0.0);
for (int i = 0; i < layerNum; i++) {
image(
getLayer(
random(10.0) // ランダムなボケ
),
0, 0);
}
}
/**
* getLayer : _blur 分の BLUR をかけたレイヤーを返す
*/
PGraphics getLayer(float _blur) {
int cNum = 24;
PGraphics p = createGraphics(width, height);
p.beginDraw();
p.background(0.0, 0.0);
p.noStroke();
p.fill(240.0);
for (int i = 0; i < cNum; i++) {
p.circle(
random(width),
random(height),
random(60.0)
);
}
p.filter(BLUR, _blur);
p.endDraw();
return p;
}
レイヤーを複数重ねるため背景を透明にしているのですが、そのときに指定していた明度がゼロ、つまり「黒」でした。
試しにこの明度を上げてみましょう。
p.background(240.0, 0.0);
影が白くなりました!
影の色をコントロール
どうやら背景の明度、いやたぶん「色」が、ぼやけたときの影に影響を与えているようです。確認のため、青っぽい色を背景に指定してみます。透明度はゼロのままです。
p.background(64.0, 128.0, 255.0, 0.0);
ピンクにすると?
p.background(255.0, 128.0, 222.0, 0.0);
やはり、背景の「色」が影の色になるようです。
レイヤー毎に色を変えてみると、このとおり。
p.colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
p.background(random(210.0, 360.0), 80.0, 90.0, 0.0);
影の色をまだらにできる?
背景の色を指定することで、影の色をキャンバス全体で一様に決めることはできました。では、キャンバスの部分毎に色を変えることはできるでしょうか?
なんかおかしい…
例えば、p.background() の替わりに、このようなコードを書いてみました。キャンバスを4分割し、それぞれの色を変えています。
int div = 2;
p.blendMode(REPLACE);
p.noStroke();
for (int x = 0; x < div; x++) {
for (int y = 0; y < div; y++) {
float rw = width / div;
float rh = height / div;
p.fill(
(x * 60.0 + y * 150.0) % 360.0,
80.0,
90.0,
0.0
);
p.rect(x * rw, y * rh, rw, rh);
}
}
p.blendMode(REPLACE); が肝です。結果はこのようになります。
うん!うまく行って… あれ?何か変です。
よく見ると、それぞれの区分の左端だけが黒い影になっています。四角形の透明度を 100.0 にするとこうなるので、塗りそこねてるわけじゃありません。
64分割にして円を黒くすると、うまく行っていないことがよりはっきりとわかります。
どうも、各 rect() の左端だけ色が反映されていないようです。
苦肉の策
いろいろとトライしてみましたが、この問題は解決できませんでした。
苦肉の策として、透明度を 0.0 じゃなくて 1.0 にすることで、rect() の左端にも色を反映させることはできます。
しかし、完全な透明ではないので、背景に色が若干残ってしまいます。
そういうとこも可愛い
思ったとおりに動いてくれないのは困ります。しかし、こういうところも Processing が完成された既成の製品ではなく、人の手による手作りの環境という感じがして、私は好きなのです。
最後に影に色をつけた作例を紹介します。
/**
* ランダムなボケでカラフルでファンシーな絵を作る
*
* @author @deconbatch
* @version 0.1
* @license CC0
* Processing 3.5.3
* created 2022.10.08
*/
void setup(){
size(640, 640);
smooth();
int layerNum = 12; // レイヤー数
background(255.0);
blendMode(SUBTRACT);
for (int i = 0; i < layerNum; i++) {
image(
getLayer(
random(10.0) // ランダムなボケ
),
0, 0);
}
}
/**
* getLayer : _blur 分の BLUR をかけたレイヤーを返す
*/
PGraphics getLayer(float _blur) {
PGraphics p = createGraphics(width, height);
p.beginDraw();
p.colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
// ランダム色の格子柄
int div = 8;
p.blendMode(REPLACE);
p.noStroke();
for (int x = 0; x < div; x++) {
for (int y = 0; y < div; y++) {
int rw = round(width / div);
int rh = round(height / div);
int rx = x * rw;
int ry = y * rh;
p.fill(
random(360.0),
90.0,
80.0,
1.0
);
p.rect(rx, ry, rw, rh);
}
}
// ランダム配置の円
int cNum = 12;
p.blendMode(BLEND);
p.fill(0.0, 0.0, 0.0, 100.0);
for (int i = 0; i < cNum; i++) {
p.circle(
random(width),
random(height),
pow(random(9.0), 2)
);
}
p.filter(BLUR, _blur);
p.endDraw();
return p;
}
参考までに、今回の実行環境
上記実行時の環境は Processing 3.5.3 on Linux でした。
試しに Processing 4.0.1 on Linux で実行したら、rect() の右端と下端に色が付かないという結果になりました。