パイこね変換器を作りたい

スポンサーリンク
スポンサーリンク

パイこね変換器

シリコンに色を付ける動画

を見ていたら、パイこね変換

パイこね変換 - Wikipedia

という用語を知った。なんかやってみたいなーと思ったので、やってみることにした。

スポンサーリンク

どうやろう

ローカルだけでやってもいいんだけど、どうせなら公開してみたいなーと思った。でもサーバーのリソース使うようなやり方だと、処理しきれるのかなー?となったのでブラウザ上だけでやれるような方法を探したい。まぁなんかあるだろ。

スポンサーリンク

ファイルをアップロードして表示する

まずはこの部分を作って、パイ揉み処理は後で作ろう。

 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];
              }

    これも追加。結果

    右上になんか変なのがつくし水平中心が埋まんねーけどできた!

    スポンサーリンク

    できあがり

    パイこね変換

    まんぞく~^^

    スポンサーリンク

    参考

    https://code-kitchen.dev/html/input-file/#javascript%E3%81%A7%E9%81%B8%E6%8A%9E%E3%81%95%E3%82%8C%E3%81%9F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B
    File APIとCanvasでローカルの画像をアップロード→加工→ダウンロードする | Tips Note by TAM
    TAM のテクニカルチームがお届けする WEB技術ブログ!
    403 Forbidden
    HTMLElement: change イベント - Web API | MDN
    change イベントは , , 要素において、ユーザーが要素の値を変更したときに発行されます。 input イベントとは異なり、 change イベントは要素の値 (value) が変更されるたびに発生するとは限りません。
    : ボタン要素 - HTML | MDN
    は HTML の要素で、マウス、キーボード、指、音声コマンド、その他の支援技術で起動することができる操作可能要素です。起動すると、フォームを送信したりダイアログを開いたりといった操作を実行します。
    https://code-kitchen.dev/html/button/
    Visual Studio Codeのhtmlフォーマッターのメモ - Qiita
    概要 Visual Studio Code(以降VSCodeと記述)のデフォルトのhtmlフォーマッターをカスタマイズしたときのメモです。 VSCodeのhtmlフォーマッターは (
    ImageData - Web API | MDN
    ImageData インターフェイスは、 要素の領域の基礎をなすピクセルデータを表します。ImageData() コンストラクターや、canvas に関連付けられた CanvasRenderingContext2D オブジェクトの crea...
    キャンバスとピクセル操作 - Web API | MDN
    これまで、キャンバスの実際のピクセルは見てきませんでした。 ImageData オブジェクトを使用して、ピクセルデータを操作するためにデータ配列へ直接読み取りや書き込みを行うことが可能です。また、画像のスムージング(アンチエイリアシング)の...
    - HTML | MDN
    要素の range 型は、指定された値より小さくなく、別に指定された値より大きくない値をユーザーに指定させるために使用します。しかし、厳密な値は重要とはされません。これは通常、 number 入力型のようなテキスト入力ボックスではなく、スラ...
    https://code-kitchen.dev/html/input-range/

    コメント

    タイトルとURLをコピーしました