1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

心臓形♡画像の生成(微調整可)

Last updated at Posted at 2024-12-18

心臓形の数式はいろいろありますが、係数が変更可能な式に UI を付けたものを作ってみた。

See the Pen 心臓形♡生成 by Ikiuo (@ikiuo) on CodePen.

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>心臓形♡画像生成</title>
    <style>
     body {
         width: 40em;
         margin: auto;
         background-color: white;
     }
     .center {
         text-align: center;
     }
     .right {
         text-align: right;
     }
     .large {
         font-size: large;
     }
     .slider {
         width: 20em;
     }
    </style>
  </head>
  <body>
    <table>
      <tr><th><h2>心臓形<span style="color: red;"></span>画像生成</h2></th></tr>

      <tr>
        <td colspan="3" class="center">𝑡 を媒介変数として以下の式で左右対称の図形を生成します</td>
      </tr>
      <tr>
        <td>
          <table style="margin: auto">
            <tr>
              <td>𝑥</td>
              <td>=</td>
              <td>𝑥<sub>1</sub> 𝐬𝐢𝐧 𝑡 + 𝑥<sub>2</sub> 𝐬𝐢𝐧<sup>2</sup> 𝑡 + 𝑥<sub>3</sub> 𝐬𝐢𝐧<sup>3</sup> 𝑡</td>
            </tr>
            <tr>
              <td>𝑦</td>
              <td>=</td>
              <td>𝑦<sub>1</sub> 𝐜𝐨𝐬 𝑡 + 𝑦<sub>2</sub> 𝐜𝐨𝐬 2𝑡 + 𝑦<sub>3</sub> 𝐜𝐨𝐬 3𝑡 + 𝑦<sub>4</sub> 𝐜𝐨𝐬 4𝑡</td>
            </tr>
          </table>
        </td>
      </tr>

      <tr>
        <td>
          <table id="tagParameter" style="margin: auto;">
            <!-- auto -->

            <tr>
              <td class="right">頂点:</td>
              <td>
                <input id="tagResolution" name="tagResolution" class="slider"
                       type="range" min="3" max="500" step="1" value="100"
                       oninput="onUpdate()">
              </td>
              <td id="tagResVal" class="center">&nbsp;</td>
            </tr>

            <tr>
              <td class="center" colspan="3">
                <button onclick="onPreset(0)"></button>
                <button onclick="onPreset(1)">♡形1</button>
                <button onclick="onPreset(2)">♡形2</button>
              </td>
            </tr>
          </table>
        </td>
      </tr>

      <tr>
        <td class="center" style="border: solid 1px lightgray;">
          <canvas id="tagCanvas" hidden></canvas>
          <img id="tagImage">
        </td>
      </tr>

    </table>

    <script>

     const backgroundColor = "#0000";
     const heartColor = "red";
     const drawSize = 512;

     const getTag = ((id) => document.getElementById(id));
     const tagSinCoeff = [...Array(3)].map((_, n) => 'tagSinCoeff' + (n + 1));
     const tagCosCoeff = [...Array(4)].map((_, n) => 'tagCosCoeff' + (n + 1));

     function createHeartCurve(res, coeff) {
         const PI = Math.PI;
         const [se, ce] = coeff
         const [se1, se2, se3] = se;
         const [ce1, ce2, ce3, ce4] = ce;

         let l = [];
         for (var p = 0; p <= res; p++) {
             const q1 = p / res;
             const x = q1 * PI;

             const sv1 = Math.sin(x);
             const sv2 = sv1 * sv1;
             const sv3 = sv1 * sv2;

             const cv1 = Math.cos(x);
             const cv2 = Math.cos(x * 2);
             const cv3 = Math.cos(x * 3);
             const cv4 = Math.cos(x * 4);

             l.push([
                 se1 * sv1 + se2 * sv2 + se3 * sv3,
                 ce1 * cv1 + ce2 * cv2 + ce3 * cv3 + ce4 *cv4
             ]);
         }
         const point = [];
         l.forEach(v => point.push(v));
         l.reverse();
         l.forEach(v => point.push([-v[0], v[1]]));
         return point;
     }

     function drawShape(tag, point) {
         const xlist = point.map((v) => v[0]);
         const ylist = point.map((v) => v[1]);
         const xmin = Math.min(...xlist);
         const xmax = Math.max(...xlist);
         const ymin = Math.min(...ylist);
         const ymax = Math.max(...ylist);
         const swidth = xmax - xmin;
         const sheight = ymax - ymin;

         const scale = drawSize / Math.max(swidth, sheight);
         const xscale = scale * swidth;
         const yscale = scale * sheight;

         const margin = 4;
         tag.width = Math.max(drawSize, xscale) + margin * 2;
         tag.height = Math.max(drawSize, yscale) + margin * 2;

         const ctx = tag.getContext("2d");

         ctx.fillStyle = backgroundColor;
         ctx.fillRect(0, 0, tag.width, tag.height);

         const dcx = tag.width / 2;
         const dcy = tag.height / 2;

         const ecx = (xmin + xmax) / 2;
         const ecy = (ymin + ymax) / 2;

         ctx.lineWidth = 1;
         ctx.beginPath();
         ctx.moveTo(0, 0);
         point.forEach(
             v => ctx.lineTo(dcx + (v[0] - ecx) * scale,
                             dcy - (v[1] - ecy) * scale));
         ctx.closePath();

         ctx.fillStyle = heartColor;
         ctx.fill();
     }

     function setParam(index) {
         const table = [
             [[1.0, 0.0,  0.0], [ 1.0,  0.0,  0.0,  0.0]],
             [[3.0, 2.0, 12.0], [16.0, -4.0, -2.5,  0.0]],
             [[0.0, 0.0, 12.0], [11.0, -4.5, -1.5, -0.5]],
         ];
         const param = table[index];
         tagSinCoeff.forEach((v, n) => getTag(v).value = param[0][n]);
         tagCosCoeff.forEach((v, n) => getTag(v).value = param[1][n]);
     }

     function onUpdate() {
         function setNumber(tag) {
             const nn = Number(getTag(tag).value);
             const sd = '' + Math.trunc(Math.abs(nn) * 100);
             const sn = sd.length < 3 ? ('000' + sd).slice(-3):  sd;
             const st = sn.slice(0, -2) + '.' + sn.slice(-2);
             const sz = '00000'.slice(0, 5 - st.length);
             const ss = (nn < 0 ? '-' : '+') + sz + st;
             getTag(tag + 'Value').textContent = ss;
             return nn;
         }

         const res = tagResolution.value;
         tagResVal.textContent = res * 2;

         const se = tagSinCoeff.map((v, n) => setNumber(v));
         const ce = tagCosCoeff.map((v, n) => setNumber(v));

         const canvas = getTag('tagCanvas')
         drawShape(canvas, createHeartCurve(res, [se, ce]));
         getTag('tagImage').src = canvas.toDataURL('image/png');
     }

     function onPreset(index) {
         setParam(index);
         onUpdate();
     }

     window.onload = (() => {
         const slider = ((tagname, varname, index, vmin, vmax) => [
             '<tr>',
             `<td class="right">${varname}<sub>${index}</sub>:</td>`,
             '<td>',
             `<input id="${tagname}${index}" name="${tagname}${index}" class="slider"`,
             ` type="range" min="${vmin}" max="${vmax}" step="0.01"`,
             ' oninput="onUpdate()">',
             '</td>',
             `<td id="${tagname}${index}Value" class="right">&nbsp;</td>`,
             '</tr>'
         ].join(''));

         getTag('tagParameter').insertAdjacentHTML('afterbegin', [
             [[0, 50], [0, 50], [0, 50]].map((v, n) => slider('tagSinCoeff', '𝑥', n+1, ...v)),
             [[0.01, 30], [-20, 5], [-10, 5], [-10, 5]].map((v, n) => slider('tagCosCoeff', '𝑦', n+1, ...v)),
         ].flat().join(''));

         onPreset(1);
     })

    </script>
  </body>
</html>
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?