p5.jsの続きです。前回は「Generative Design with p5.js」の「文字 P_3_1_1_01」を見ました。今回は画像編「画像 P_4_1_1_01」を見ていきます。それでは行きましょう!
【目次】
- 画像 P_4_1_1_01
- JSコード全体
- 全体構成
- グローバル変数
- setup()メソッドの中身
- draw()メソッドの中身
- cropTiles()関数
- mouseMoved()メソッド
- mouseReleased()メソッド
- keyReleased()メソッドの中身
- 動作確認
- 最後に
※参考:前回記事
https://https://www.i-ryo.com/entry/2020/05/28/190542
※参考:p5.jsを習得するためにやったことまとめ
qiita.com
画像 P_4_1_1_01
書籍「Generative Design with p5.js」より
Generative Design with p5.js - [p5.js版ジェネラティブデザイン] ―ウェブでのクリエイティブ・コーディング
- 作者:Benedikt Gross,Hartmut Bohnacker,Julia Laub
- 発売日: 2018/06/22
- メディア: 単行本
ソースコード一覧
※参考:Generative Design with p5.js[p5.js版ジェネラティブデザイン] ―ウェブでのクリエイティブ・コーディング
画像 P_4_1_1_01
最初は画像1枚。カーソルを載せると四角い枠が現れる。クリックするとその枠の範囲が切り抜かれた升目状になる。マウスを動かすと元に戻る。
※参考:p5.js Web Editor
JSコード全体
// P_4_1_1_01 // // Generative Gestaltung – Creative Coding im Web // ISBN: 978-3-87439-902-9, First Edition, Hermann Schmidt, Mainz, 2018 // Benedikt Groß, Hartmut Bohnacker, Julia Laub, Claudius Lazzeroni // with contributions by Joey Lee and Niels Poldervaart // Copyright 2018 // // http://www.generative-gestaltung.de // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * cutting and multiplying an area of the image * * MOUSE * position x/y : area position * left click : multiply the area * * KEYS * 1-3 : area size * r : toggle random area * s : save png */ 'use strict'; var img; var tileCountX = 4; var tileCountY = 4; var tileCount = tileCountX * tileCountY; var imgTiles = []; var tileWidth; var tileHeight; var cropX; var cropY; var selectMode = true; var randomMode = false; function preload() { img = loadImage('data/image.jpg'); } function setup() { createCanvas(800, 600); image(img); tileWidth = width / tileCountY; tileHeight = height / tileCountX; noCursor(); noFill(); stroke(255); } function draw() { if (selectMode) { // in selection mode, a white selection rectangle is drawn over the image cropX = constrain(mouseX, 0, width - tileWidth); cropY = constrain(mouseY, 0, height - tileHeight); image(img, 0, 0); rect(cropX, cropY, tileWidth, tileHeight); } else { // reassemble image var imgIndex = 0; for (var gridY = 0; gridY < tileCountY; gridY++) { for (var gridX = 0; gridX < tileCountX; gridX++) { image(imgTiles[imgIndex], gridX * tileWidth, gridY * tileHeight); imgIndex++; } } } } function cropTiles() { tileWidth = width / tileCountY; tileHeight = height / tileCountX; imgTiles = []; for (var gridY = 0; gridY < tileCountY; gridY++) { for (var gridX = 0; gridX < tileCountX; gridX++) { if (randomMode) { cropX = int(random(mouseX - tileWidth / 2, mouseX + tileWidth / 2)); cropY = int(random(mouseY - tileHeight / 2, mouseY + tileHeight / 2)); } cropX = constrain(cropX, 0, width - tileWidth); cropY = constrain(cropY, 0, height - tileHeight); imgTiles.push(img.get(cropX, cropY, tileWidth, tileHeight)); } } } function mouseMoved() { selectMode = true; } function mouseReleased() { selectMode = false; cropTiles(); } function keyReleased() { if (key == 's' || key == 'S') saveCanvas(gd.timestamp(), 'png'); if (key == 'r' || key == 'R') { randomMode = !randomMode; cropTiles(); } if (key == '1') { tileCountX = 4; tileCountY = 4; cropTiles(); } if (key == '2') { tileCountX = 10; tileCountY = 10; cropTiles(); } if (key == '3') { tileCountX = 20; tileCountY = 20; cropTiles(); } }
冒頭コメントの前半はクレジット。 後半の概要の訳。
画像の領域を切り取り、乗算する
マウス
位置x / y:エリアの位置
左クリック:エリアを乗算
キー
1-3:面積
r:ランダム領域を切り替えます
s:pngを保存
全体構成
// グローバル変数 function preload() { // ページ読み込み前の処理 } function setup() { // ページ読み込み時の処理 } function draw() { // 一定時間ごとに繰り返し実行 } function cropTiles() { // タイルのトリミング } function mouseMoved() { // マウスが移動している時の処理 } function mouseReleased() { // マウスを離した時の処理 } function keyReleased() { // キーが離れた時の処理 }
いつものsetup()
、draw()
、keyReleased()
などの他にもいくつかメソッドが見受けられる。
cropTiles()
はp5.jsの組み込みメソッドではなく今回独自に生成された関数だった。
mouseReleased()
は初めて出て来たマウスを離した時の処理だった。
The mouseReleased() function is called every time a mouse button is released.
mouseReleased()関数は、マウスボタンが離されるたびに呼び出されます。
グローバル変数
冒頭のグローバル変数の設定。改行が入って3グループに分かれている。
まず変数img
を宣言
var img;
値はまだない
次にタイル関係の設定がある。
var tileCountX = 4; var tileCountY = 4; var tileCount = tileCountX * tileCountY; var imgTiles = []; var tileWidth; var tileHeight; var cropX; var cropY;
変数tileCount
は式になっていて変数tileCountX
とtileCountY
を掛けた値
最後は「Mode」ということで初期設定だろうか。
var selectMode = true; var randomMode = false;
真偽値(true
、false
)が設定されている。
setup()メソッドの中身
setup()メソッド全体
ページ読み込み時の処理
function setup() { createCanvas(800, 600); image(img); tileWidth = width / tileCountY; tileHeight = height / tileCountX; noCursor(); noFill(); stroke(255); }
ブロックごとに見ていく
キャンバスを作成
createCanvas(800, 600);
createCanvas()
メソッド実行。引数は左から800、600
キャンバス作成
画像読み込む、タイルサイズ設定
image(img); tileWidth = width / tileCountY; tileHeight = height / tileCountX;
image()
メソッド実行。引数はimg
tileWidth
をwidth
割るtileCountY
にtileHeight
をheight
割るtileCountX
に
image()
メソッドは以前も出て来た、p5.jsのキャンバス上で画像を描画するメソッド。今回は変数img
を読み込む。
※参考:【p5.js】Generative Design with p5.js「画像 P_4_0_01」のコードを読み解く - クモのようにコツコツと
tileWidth
はタイルの幅でキャンバス幅をタイルの横側の数で割る。
tileHeight
はタイルの高さでキャンバス高さをタイルの縦側の数で割る
カーソル、塗り、線の設定
noCursor(); noFill(); stroke(255);
noCursor()
メソッド実行noFill()
メソッド実行stroke()
メソッド実行(引数は255)
noCursor()
はカーソルを非表示にするメソッド
※参考:【p5.js】Generative Design with p5.js「文字 P_3_1_1_01」のコードを読み解く - クモのようにコツコツと
noFill()
は塗りを無しにするメソッド
※参考:【p5.js】Generative Design with p5.js「形 P_2_0_02、03」のコードを読み解く - クモのようにコツコツと
stroke()
は線の色で255はRGB3色とも真っ白ということ。
draw()メソッドの中身
draw()メソッド全体
一定時間ごとに繰り返し実行
function draw() { if (selectMode) { // in selection mode, a white selection rectangle is drawn over the image cropX = constrain(mouseX, 0, width - tileWidth); cropY = constrain(mouseY, 0, height - tileHeight); image(img, 0, 0); rect(cropX, cropY, tileWidth, tileHeight); } else { // reassemble image var imgIndex = 0; for (var gridY = 0; gridY < tileCountY; gridY++) { for (var gridX = 0; gridX < tileCountX; gridX++) { image(imgTiles[imgIndex], gridX * tileWidth, gridY * tileHeight); imgIndex++; } } } }
ブロックごとに見ていく。
if文の大枠
if (selectMode) { // in selection mode, a white selection rectangle is drawn over the image // (処理) } else { // reassemble image // (処理) }
- もし
selectMode
がtrueならば - 画像の上に白い四角を描画
- さもなくば画像を再構成
if部分のコメント
in selection mode, a white selection rectangle is drawn over the image
選択モードでは、白い選択長方形が画像の上に描画されます
else部分のコメント
reassemble image
画像を再構成する
白い四角を描画
if文のif部分(画像の上に白い四角を描画)
// in selection mode, a white selection rectangle is drawn over the image
cropX = constrain(mouseX, 0, width - tileWidth);
cropY = constrain(mouseY, 0, height - tileHeight);
image(img, 0, 0);
rect(cropX, cropY, tileWidth, tileHeight);
- 変数
cropX
でconstrain()メソッドを実行(引数は左から
mouseX,
0,
width引く
tileWidth`) - 変数
cropY
でconstrain()メソッドを実行(引数は左から
mouseY,
0,
height引く
tileHeight `) image()
メソッドを実行(引数は左からimg
,0
,0
)rect()
メソッドを実行(引数は左からcropX
,cropY
,tileWidth
,tileHeight
)
`constrain()メソッドは初めて出て来た!
Constrains a value between a minimum and maximum value.
値を最小値と最大値の間で制約します。
`constrain()メソッドの引数
constrain(n, low, high)
n Number: number to constrain
low Number: minimum limit
high Number: maximum limit
n番号:制約する番号 低い数:最小制限 高い数:上限
cropX
のconstrain()
はmouseX
を「0」から「キャンバス幅引くタイル幅」にする。
cropY
のconstrain()
はmouseY
を「0」から「キャンバス高さ引くタイル高さ」にする。
image()
メソッドは第2引数が横位置、第3引数が縦位置なので画像位置の初期値はいずれも0
rect()
メソッドは四角の描画で横位置はcropX
、縦位置はcropY
、幅はtileWidth
、高さはtileHeight
この部分で画面上に白い四角が描画される。
画像を再構成
次はif文のelse部分(画像を再構成)
// reassemble image var imgIndex = 0; for (var gridY = 0; gridY < tileCountY; gridY++) { for (var gridX = 0; gridX < tileCountX; gridX++) { image(imgTiles[imgIndex], gridX * tileWidth, gridY * tileHeight); imgIndex++; } }
- 変数
imgIndex
の値を0
に - for文が多重ループになっている
- 外側のfor文の条件:変数
gridY
の初期値は0
、gridY
をtileCountY
の値分繰り返す、gridY
を1つ加算 - 内側のfor文の条件:変数
gridX
の初期値は0
、gridX
をtileCountX
の値分繰り返す、gridX
を1つ加算 image()
メソッドを実行(引数は左から配列imgTiles[]
のimgIndex
キー,gridX
掛けるtileWidth
,gridY
掛けるtileHeight
)imgIndex
に値を1つ加算
多重ループでタイルの横の数、縦の数分処理を繰り返す。これで升目状になる。
for文の中ではimage()
メソッドで配列imgTiles
の中のimgIndex
の画像を横のグリッド数掛けるタイル幅、縦のグリッド数掛けるタイル高さの位置に配置する。
配列imgTiles
のimgIndex
キーの値を一つ加算して次の画像を描画
この部分で升目状の画像に再構成されるわけだ!
cropTiles()関数
cropTiles()関数全体
タイルのトリミング設定。「crop」は「作物」という意味でタイルを作るということか。
function cropTiles() { tileWidth = width / tileCountY; tileHeight = height / tileCountX; imgTiles = []; for (var gridY = 0; gridY < tileCountY; gridY++) { for (var gridX = 0; gridX < tileCountX; gridX++) { if (randomMode) { cropX = int(random(mouseX - tileWidth / 2, mouseX + tileWidth / 2)); cropY = int(random(mouseY - tileHeight / 2, mouseY + tileHeight / 2)); } cropX = constrain(cropX, 0, width - tileWidth); cropY = constrain(cropY, 0, height - tileHeight); imgTiles.push(img.get(cropX, cropY, tileWidth, tileHeight)); } } }
これはp5.jsの組み込みメソッドではなく本件で独自に作成している関数。
変数設定
最初に変数の設定
tileWidth = width / tileCountY;
tileHeight = height / tileCountX;
imgTiles = [];
- 変数
tileWidth
をwidth
割るtileCountY
に - 変数
tileHeight
をheight
割るtileCountX
に - 変数
imgTiles
を空の配列に
いずれも先程のdraw()
メソッドの処理の中に出てくる変数!
多重ループ部分
その後の処理はボリュームがあるのでさらに細かく見ていく。まずは外側の多重ループ
for (var gridY = 0; gridY < tileCountY; gridY++) { for (var gridX = 0; gridX < tileCountX; gridX++) { // 処理 } }
- 外側のfor文の条件:変数
gridY
の初期値は0
、gridY
をtileCountY
の数繰り返す、gridY
を1つ加算する - 内側のfor文の条件:変数
gridX
の初期値は0
、gridX
をtileCountX
の数繰り返す、gridX
を1つ加算する
先程のdraw()
の後に出て来た多重ループと似ている。グリッドの横位置、縦位置をタイル数文増やして行く設定と思われる。
if文
for文の中にはif文がある
if (randomMode) { cropX = int(random(mouseX - tileWidth / 2, mouseX + tileWidth / 2)); cropY = int(random(mouseY - tileHeight / 2, mouseY + tileHeight / 2)); }
- もし
randomMode
がtrue
であれば cropX
でint()
メソッドを実行(引数はrandom()
メソッド)
random()
メソッドの引数は左からmouseX
引くtileWidth
割る2
、mouseX
足すtileWidth
割る2
cropY
でint()
メソッドを実行(引数はrandom()
メソッド)
random()
メソッドの引数引数は左からmouseY
引くtileHeight
割る2
、mouseY
足すtileHeight
割る2
int()
メソッドは少数を切り捨てて整数にするメソッド。
※参考:【p5.js】Generative Design with p5.js「形 P_2_0_02、03」のコードを読み解く - クモのようにコツコツと
random()
メソッドは引数2つの間のランダムな数値を出す
※参考:【p5.js】クリエイティブコーディングに挑戦(その1) - クモのようにコツコツと
cropX
はマウスの縦位置から高さの半分の間、cropY
は横位置からタイルの横幅の半分を引いた値のランダムな数値が出る。
これがランダムモードのトリミング位置になる。
その他の処理
if文の後にいくつかの処理がある
cropX = constrain(cropX, 0, width - tileWidth); cropY = constrain(cropY, 0, height - tileHeight); imgTiles.push(img.get(cropX, cropY, tileWidth, tileHeight));
- 変数
cropX
でconstrain()
メソッドを実行(引数は左からcropX
,0
,width
引くtileWidth
) - 変数
cropY
でconstrain()
メソッドを実行(引数は左からcropY
,0
,height
引くtileHeight
) imgTiles
オブジェクトのpush()
メソッドを実行
push()
メソッドの引数でimg
オブジェクトのget()
メソッドを実行
(引数は左からcropX
,cropY
,tileWidth
,tileHeight
)
constrain()
メソッドは先程draw()
メソッド出て来た。値を最小値と最大値の間で制約する。
cropX
を0〜キャンバス幅引くタイル幅、cropY
を0〜キャンバス高さ引くタイル高さの範囲にする。
imgTiles
は配列なのでここで使われるpush()
はJS組み込みメソッドの配列追加か。
※参考:Array.prototype.push() - JavaScript | MDN
p5.jsのpush()
メソッドは図の描画だがpop()
メソッドとセットになるはずなので。
※参考:【p5.js】Generative Design with p5.js「文字 P_3_1_1_01」のコードを読み解く - クモのようにコツコツと
で、そのpush()
の中に追加されるのはimg
の画像にget()
メソッドを実行したもの。
このget()
メソッドは今回初めて出て来た!
Get a region of pixels, or a single pixel, from the canvas.
キャンバスからピクセルの領域または単一のピクセルを取得します。
なるほど、このメソッドでトリミングした画像が作られる訳だ!
引数は4つある
get(x, y, w, h)
x Number: x-coordinate of the pixel
y Number: y-coordinate of the pixel
w Number: width
h Number: height
x番号:ピクセルのx座標 y番号:ピクセルのy座標 w番号:幅 h番号:高さ
ピクセル横位置がcropX
、ピクセル縦位置がcropY
、画像幅がtileWidth
、画像高さがtileHeight
になると!
mouseMoved()メソッド
マウスが移動している時の処理
function mouseMoved() { selectMode = true; }
selectMode
をtrue
に
マウスを動かすとselectMode
がtrue
になる。
mouseReleased()メソッド
マウスを離した時の処理
function mouseReleased() { selectMode = false; cropTiles(); }
selectMode
をfalse
にcropTiles()
関数を実行
マウスを離す、とあるがクリックの度に実行、という動きのようだ。クリックする度にcropTiles()
が実行されて升目状の画像が作られる。
keyReleased()メソッドの中身
keyReleased()メソッド全体
キーが離れた時の処理
function keyReleased() { if (key == 's' || key == 'S') saveCanvas(gd.timestamp(), 'png'); if (key == 'r' || key == 'R') { randomMode = !randomMode; cropTiles(); } if (key == '1') { tileCountX = 4; tileCountY = 4; cropTiles(); } if (key == '2') { tileCountX = 10; tileCountY = 10; cropTiles(); } if (key == '3') { tileCountX = 20; tileCountY = 20; cropTiles(); } }
いつもの画像保存以外にもいくつかif文が見受けられる。
sまたはSキーの処理
if (key == 's' || key == 'S') saveCanvas(gd.timestamp(), 'png');
- もし
s
またはS
キーを押したら - キャンバスを
png
画像に保存する。
これはいつものやつ
rまたはRキーの処理
if (key == 'r' || key == 'R') { randomMode = !randomMode; cropTiles(); }
- もし
r
またはR
を押したら randomMode
の設定を逆にするcropTiles()
メソッドを実行
ランダムモードを切り替えてcropTiles()
の画像描画が実行される
1キーの処理
if (key == '1') { tileCountX = 4; tileCountY = 4; cropTiles(); }
- もし
1
キーを押したら tileCountX
を4
にtileCountY
を4
にcropTiles()
メソッドを実行
2キーの処理
if (key == '2') { tileCountX = 10; tileCountY = 10; cropTiles(); }
- もし
2
キーを押したら tileCountX
を10
にtileCountY
を10
にcropTiles()
メソッドを実行
3キーの処理
if (key == '3') { tileCountX = 20; tileCountY = 20; cropTiles(); } }
- もし
3
キーを押したら tileCountX
を20
にtileCountY
を20
にcropTiles()
メソッドを実行
1〜3キーを押すとトリミングのサイズが変わる!
動作確認
数字キー1〜3を押すとトリミング枠のサイズが変わる。
rまたはRキーを押すとトリミング範囲がランダムになる。
最後に
前回は画像のリサイズでしたが、今回は画像をトリミングして新たに画像を生成する、という内容でした。画像生成系の新しいメソッドに遭遇しました。トリミングする場所によって違う模様みたいになって面白い挙動です。
次回はまた「形」編の続きを見て行きたく思います。それではまた!
※参考:p5.jsを習得するためにやったことまとめ
qiita.com