Processingでスリットアニメーションを作ってみたよ
きっかけ
できたもの
投稿時は知らなかったのですが、この類のアニメーションは「スリットアニメーション」と呼ばれているそうです。Wikipediaにも載ってます。
どうやるの
その前にスリットアニメーションについて。
まずアニメーションさせたい連番画像を用意します。
そしてスリットシートを用意します。
さて、上の画像とスリットを重ねてみましょう。
この画像、スリットによって半分隠されているにも関わらず、画像から図形を認識することができます。アモーダル補完というものだそうです。
今度は隙間分の間隔を一つあけてもう片方の画像に重ねてみましょう。
これもスリットによって半分隠されていますが、青い四角形を認識できます。
では、この2つの画像から隠れていない部分のみを取り出してそのまま重ねてみましょう。
ぱっと見た感じ、よくわからないものが出来上がりましたが、これがスリットアニメーションで再生するための画像になります。この上でスリットシートを動かすことによって、隠れていない部分が次々に変わっていき、アニメーションとして見せることができます。
もしフレーム数が増えれば、用意するスリットシートの黒塗りの幅が広くなります。(8フレームのアニメーションだと、遮蔽部の幅が7ピクセル+透明が1ピクセルのスリットシートになる)
よし、つくろう
今回作るものはスリットアニメーションのための画像とスリットシートです。
コード全文
final int FRAME = 16; //総フレーム数
final String EXTENSION=".png"; //使用する画像の拡張子
void setup() {
size(512, 512);//画面幅は必ずFRAMEの倍数にすること
}
void draw() {
background(255);
if (frameCount <= FRAME) {
drawHexagon(200);
saveFrame("####" + EXTENSION);
} else {
createSlitImage();
createSlit();
exit();
}
}
void drawHexagon(float radius) {
pushMatrix();
translate(width/2, height/2);
rotate(PI * frameCount/FRAME * (1/3.0));
stroke(0);
strokeWeight(25);
for (float i=0, n=6; i < n; i++) {
line(0, 0, cos(i/n * TWO_PI) * radius, sin(i/n * TWO_PI) * radius);
line(cos(i/n * TWO_PI) * radius,
sin(i/n * TWO_PI) * radius,
cos(((i+1)%n)/n * TWO_PI) * radius,
sin(((i+1)%n)/n * TWO_PI) * radius);
}
popMatrix();
}
void createSlitImage() {
PImage[] loadedImage = new PImage[FRAME];
for (int i = 0; i < FRAME; i++) {
loadedImage[i]=loadImage(nf(i + 1, 4, 0) + EXTENSION);
}
loadPixels();
for (int i=0; i<height * width; i++) {
pixels[i]=loadedImage[i%FRAME].pixels[i];
}
updatePixels();
save("slitImage" + EXTENSION);
}
void createSlit() {
PGraphics slit=createGraphics(width, height);
slit.beginDraw();
slit.strokeWeight(1);
slit.stroke(0, 255);
for (int i = 0; i <width; i++) {
if (i%FRAME == 0)continue;
slit.line(i, 0, i, height);
}
slit.endDraw();
image(slit, 0, 0);
slit.save("slit" + EXTENSION);
}
これを保存して実行すると、「0001~16.png」「slit.png」「slitImage.png」の計18枚の画像が生成されると思います。
ではslitImage.pngの上にslit.pngを重ねて動かしてみましょう。
動きましたね!
コードの解説
今回はcreateSlitImage()メソッドを中心に、スリットアニメーションの生成方法を説明していくので、drawHexagon()メソッドの説明は省かせていただきます。
createSlitImage()メソッド
void createSlitImage() {
PImage[] loadedImage = new PImage[FRAME];
for (int i = 0; i < FRAME; i++) {
loadedImage[i]=loadImage(nf(i + 1, 4, 0) + EXTENSION);
}
loadPixels();
for (int i=0; i<height * width; i++) {
pixels[i]=loadedImage[i%FRAME].pixels[i];
}
updatePixels();
save("slitImage" + EXTENSION);
}
画像の読み込み
PImage[] loadedImage = new PImage[FRAME];
for (int i = 0; i < FRAME; i++) {
loadedImage[i]=loadImage(nf(i + 1, 4, 0) + EXTENSION);
}
まず最初にsaveFrame()で保存した画像を全て読み込んで、PImageの配列に格納しています。
スリット単位での分割
loadPixels();
for (int i=0; i<height * width; i++) {
pixels[i]=loadedImage[i%FRAME].pixels[i];
}
updatePixels();
そして、loadPixels()で画面上のピクセルを読み込んでいます。これをしないとぬるぽが出ます。
pixels[]というものはPImageの1ピクセルの色情報(color型)を1次元の配列にしたものらしいです。
for文内における左辺のpixels[i] は実行画面のものを表しており、これにloadedImageのピクセルをフレーム毎に代入していきます。
ここで重要なのは、右辺がloadedImageの i%FRAME 番目を参照していることです。
i%FRAME は i を FRAME で割ったときの余りを表しており、for文内では
0, 1, 2, 3, ... ,12 ,13 ,14, 0, 1, 2, ... , 12, 13, 14, 0, ..
と変化します。
既に気が付いた方もいると思いますが、この方法では画面の幅がFRAMEの倍数になっていないと、改行時に参照すべき画像と実際に参照される画像にずれが生じてしまい、正常に生成されなくなります。
~適切ではない画面幅で生成した画像~
そして保存へ…
save("slitImage" + EXTENSION);
保存しましょう。この項目ではそれだけです。
以上の手順で16フレーム分の画像をスリットアニメーションとして1枚の静止画にまとめています。
スリットシート生成の解説
void createSlit() {
PGraphics slit=createGraphics(width, height);
slit.beginDraw();
slit.strokeWeight(1);
slit.stroke(0, 255);
for (int i = 0; i <width; i++) {
if (i%FRAME == 0)continue;
slit.line(i, 0, i, height);
}
slit.endDraw();
image(slit, 0, 0);
slit.save("slit" + EXTENSION);
}
アニメーションを再生させるのに使うスリットシートもプログラムで生成しちゃいましょう。
ここでPGraphicsを使用しているのは、透明度を持った画像として保存するためです。隙間が生じる間隔さえ正しければスリットシートとして機能するので、先ほどの画像生成と比べるとそこまで気を付けることはありません。
終わりに
きっかけとなったツイートは手で描いていましたが、そのとき私は「手で描けるならプログラムだともっと正確に描けるはず!」「アニメーションが1枚の静止画として集約されている!すごい!」「最近TLでよく見るpixels[]使ってみたい」などといった色んな思いがあって、このスリットアニメーションをProcessingで実装するに至りました。記事も書いてみました。
頑張ってわかりやすく書いたつもりですが、この記事を見たProcessing使いがスリットアニメーションを作ることを心のどこかで祈っています。
この記事が気に入ったらサポートをしてみませんか?