パイこね変換器
シリコンに色を付ける動画
最近ずっとシリコンに色つける動画見てる pic.twitter.com/QDxcdm3t3Q
— ねりまちゃん☁ (@nerimarina) August 4, 2021
を見ていたら、パイこね変換

という用語を知った。なんかやってみたいなーと思ったので、やってみることにした。
どうやろう
ローカルだけでやってもいいんだけど、どうせなら公開してみたいなーと思った。でもサーバーのリソース使うようなやり方だと、処理しきれるのかなー?となったのでブラウザ上だけでやれるような方法を探したい。まぁなんかあるだろ。
ファイルをアップロードして表示する
まずはこの部分を作って、パイ揉み処理は後で作ろう。
HTML
<input type="file" id="selectfile" name="selectfile" accept='image/*' required>
inputタグのtypeをファイルにするとファイル選択ボタンができる。accept=’image/*’でMinetype画像ファイルを限定できる。requiredで入力必須。
Javascript
const fileInput = document.getElementById('selectfile'); fileInput.addEventListener('change', func);
要素を取得して、リスナを設定する。changeっていうのが変更が確定したときに実行されるように設定するやつ。変更が発生するとfuncが実行されると。そんでfuncの中身を作ってく。この関数の書き方が色々あるから迷うんだよなー。
File
とりま
const loadImage = () => {
const file = fileInput.files[0];
console.log(file);
}
てして
素材を用意しまして、ファイルを選択してみると
読めたっぽい。じゃあ今度はこれを表示する。
FileReader
<div id="preview"></div>
inputタグの下に追加しとく。
上のconsole.logの行の代わりに
const reader = new FileReader();
reader.onload = function (e) {
const imageUrl = e.target.result;
const img = document.createElement("img");
img.src = imageUrl;
preview.appendChild(img);
}
reader.readAsDataURL(file);
これはまぁコピペです。fileをDataURLとして読み込んだ後に、imgタグを作成して、srcにDataURLを設定する。もっかい読み込んでみると
表示された!
画像を加工できるようにする
画像を読み込めたので今度は画像を加工できるようにしてく。読み込んだ時点で加工の処理を走らすこともできるんだろうけど、読み込んでからボタンを押して処理を実行する感じにしたい。
ボタンを設置したり
<h1>パイこね変換器</h1> <input type="file" id="selectfile" name="selectfile" accept='image/*' required><br> <button onclick="">実行</button> <h2>処理前</h2> <div id="preview"></div> <h2>処理後</h2> <div id="result"></div>
HTMLをこんな感じにした。
ボタンを押すと画像を表示する
要素を取得して、canvasに変換して、表示する。
function knead() {
const preimg = document.getElementById("preimg");
image = new Image();
image.onload = () => {
var canvas = document.getElementById("result");
var ctx = canvas.getContext("2d");
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0)
}
image.src = preimg.src;
console.log(preimg);
}
これなんか拡張機能作ったときやったな。
加工できるようにする
ImageData形式というものに変換すればいいみたい。context.getImageDataで取得。
var imagedata = ctx.getImageData(0,0,image.width,image.height);
ImageDataは画像を一次元の配列にしたものみたい。例えば4×4の画像の場合
0,0 | 0,1 | 0,2 | 0,3 | ||||||||||||
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
1,0 | 1,1 | 1,2 | 1,3 | ||||||||||||
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
2,0 | 2,1 | 2,2 | 2,3 | ||||||||||||
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
3,0 | 3,1 | 3,2 | 3,3 | ||||||||||||
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
インデックスはこう振られてる感じか。グレスケ化を調べてみて
image.onload = () => {
var canvas = document.getElementById("result");
var ctx = canvas.getContext("2d");
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
var imagedata = ctx.getImageData(0, 0, image.width, image.height);
for (let index = 0; index < imagedata.data.length; index += 4) {
var r = imagedata.data[index];
var g = imagedata.data[index + 1];
var b = imagedata.data[index + 2];
var gray = (r + g + b) / 3;
imagedata.data[index] = gray;
imagedata.data[index + 1] = gray;
imagedata.data[index + 2] = gray;
}
ctx.putImageData(imagedata,0,0)
}
こんな感じにしたら
できた!あとはいよいよパイ揉みすればいいんだな。
パイこねをコードで表現したい
流れは、拡大縮小して、半分に分割して、上下に並び替えるって感じ。言うのは簡単だよね…。
拡大縮小
うんうん唸りながら
image.onload = () => {
var canvas = document.getElementById("result");
var ctx = canvas.getContext("2d");
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
var imagedata = ctx.getImageData(0, 0, image.width, image.height);
var trnsdata = ctx.createImageData(image.width * 2, image.height / 2);
for (let idxw = 0; idxw < trnsdata.width - 1; idxw += 2) {
for (let idxh = 0; idxh < trnsdata.height - 1; idxh++) {
var idxt = trnsdata.width * 4 * idxh + idxw * 4;
var idxi = imagedata.width * 4 * (idxh * 2) + (idxw / 2) * 4;
trnsdata.data[idxt] = imagedata.data[idxi];
trnsdata.data[idxt + 1] = imagedata.data[idxi + 1];
trnsdata.data[idxt + 2] = imagedata.data[idxi + 2];
trnsdata.data[idxt + 3] = imagedata.data[idxi + 3];
}
}
canvas.width = canvas.width * 2;
canvas.height = canvas.height / 2;
ctx.putImageData(trnsdata, 0, 0)
}
縦方向は間引いて、横方向は伸長させた。半分で2倍なので考えることが少なくて済んだ。ただ横方向の補間をしてない。とりまここまでの結果
こうなった。そんで補間を効かすと
for (let index = 4; index < trnsdata.data.length; index += 8) {
var r = (trnsdata.data[index - 4] + trnsdata.data[index + 4]) / 2;
var g = (trnsdata.data[index - 4 + 1] + trnsdata.data[index + 4 + 1]) / 2;
var b = (trnsdata.data[index - 4 + 2] + trnsdata.data[index + 4 + 2]) / 2;
var a = (trnsdata.data[index - 4 + 3] + trnsdata.data[index + 4 + 3]) / 2;
trnsdata.data[index] = r;
trnsdata.data[index + 1] = g;
trnsdata.data[index + 2] = b;
trnsdata.data[index + 3] = a;
}
こんなー
おー、いーんじゃない?
分割して上下に結合
計算が大変。なんとか
var halfwidth = trnsdata.width / 2;
var rowsize = trnsdata.width * 4;
var ti = 0;
for (let ih = 1; ih <= trnsdata.height; ih++) {
for (let i = 0; i < halfwidth * 4; i++) {
var idxt = rowsize * ih - halfwidth * 4;
tempdata.data = trnsdata.data[idxt + i];
ti++;
}
}
for (let ih = 1; ih <= trnsdata.height; ih++) {
for (let i = 0; i < halfwidth * 4; i++) {
var idxt = i + trnsdata.width * 4 * (ih - 1);
tempdata.data = trnsdata.data[idxt];
ti++;
}
}
こんな感じに。実行すると
やったー!とりあえず1回こねれた~
複数回こねられるようにする
いっぱいこねたい。
回数を設定できるようにする
<input type="range" id="slider" min="1" max="10" value="5"><label> 5回こねる</label><br><br>
これなー。
var range = document.getElementById("slider");
function changeLabel() {
var label = document.getElementsByTagName("label");
label[0].innerText = " " + range.value + "回こねる";
}
range.addEventListener("input", changeLabel);
これもー。あとはfor?全部括って
for (let idxc = 0; idxc < tempdata.data.length; idxc++) {
imagedata.data[idxc] = tempdata.data[idxc];
}
これも追加。結果
右上になんか変なのがつくし水平中心が埋まんねーけどできた!
できあがり
まんぞく~^^
参考







コメント