2017/07/03

[JavaScript]GitHub風のアイコン(Identicon)を生成しダウンロードする

SNSをはじめ、個人を識別するためにユーザ名とは別にアイコンが使われることが多い。そのなかでもGitHubの初期アイコンのような、機械的に生成したアイコンを「アイデンティコン(Identicon)」と呼ぶ。

JavaScriptでアイデンティコンを生成するツールをつくったので、その説明をする。
具体的な処理は、以下の3つだ。
  • 文字列からハッシュ値を取得する
  • ハッシュ値からアイコン(SVG)を生成する
  • SVGのアイコンをPNGで保存する


アイデンティコン とは


アイデンティコンとは、IPアドレスなどからハッシュ値を取得、それをもとに色や形を決めて生成したアイコンのこと。

今回実装するのは、5×5の25ブロックからなるアイデンティコンの生成ツール。ただし、アイコンはGitHubのような左右対称はなくランダムとなる。


やることは以下のとおり。
  1. 入力された文字列を、SHA-1で40桁のハッシュ値を生成する
  2. ハッシュの先頭25文字の文字コードにより、描画するかどうか判定する
  3. ハッシュの残り15文字をさらに3分割し、文字コードよりRGBのカラーコードを生成する
  4. SVGのパスを生成し、画面に表示する
  5. canvasを使って、SVGをPNGに変換しダウンロードする



アイデンティコンの生成ツールを実装する


<input type="text" id="source">
<button id="generate">generate</button>
<button id="download">download</button>

<div class="identity-icon">
</div>
const identityIcon = document.querySelector('.identity-icon');
const source = document.getElementById('source');
const generate = document.getElementById('generate');
const download =  document.getElementById('download');

/**
 * 文字列からRGBを取得する
 * @param {string} val RGBに変換する文字列
 * @return {Array} [r, g, b]で返す
 */
function generateRGBCode(val) {
  if (!val) { return [0,0,0]; }
  const len = Math.floor(val.length / 3);
  // /[\s\S]{1,n}/g
  const regexp = new RegExp(`[\\s\\S]{1,${len}}`, 'g');
  const codes = val.match(regexp);

  const rgb = [];
  codes.forEach(a => {
    let n = 0;
    for (let x of a) {
      n += x.charCodeAt();
    }
    rgb.push(n % 256);
  });

  return rgb;
}

/**
 * IdentityIconを生成し、SVGのパスを返す
 * @param {string} hash sha1などでハッシュ化した値(40桁)
 * @param {number} size アイコンの横幅
 * @return {string} SVGのパス
 */
function generateIcon(hash, size) {
  const forDraw = hash.substr(0, 25);
  const forColor = hash.substr(25);

  // rgb(x, y, z)
  const hue = `rgb(${generateRGBCode(forColor).join(',')}`;

  const interval = size / 5;
  let x = 0;
  let y = 0;
  let path = '';

  for (let c of forDraw) {
    const isDraw = c.charCodeAt() % 2 === 0;

    path += `<rect x="${x}" y="${y}" width="${interval}" height="${interval}" fill="${isDraw ? hue : 'white'}" />`;

    if (x < size - interval) {
      x += interval;
    } else {
      x = 0;
      y += interval;
    }
  }

  return [
    `<svg width="${size}" height="${size}">`,
    '<g>',
    path,
    '</g>',
  ].join('');
}

/**
 * generateボタン
 */
generate.addEventListener('click', () => {
  const hash = sha1(source.value);
  const size = 300;

  identityIcon.innerHTML = generateIcon(hash, size);
});

/**
 * downloadボタン
 */
download.addEventListener('click', () => {
  const hash = sha1(source.value);
  const size = 300;

  identityIcon.innerHTML = generateIcon(hash, size);
  const svg = identityIcon.querySelector('svg');
  const svgData = new XMLSerializer().serializeToString(svg);
  const canvas = document.createElement('canvas');
  canvas.width = svg.width.baseVal.value;
  canvas.height = svg.height.baseVal.value;

  const ctx = canvas.getContext('2d');
  const img = new Image();
  img.onload = () => {
    ctx.drawImage(img, 0, 0);
    canvas.toBlob(blob => {
      saveAs(blob, 'icon.png');
    }, 'image/png');
  };
  
  img.src = "data:image/svg+xml;charset=utf-8;base64," + btoa(unescape(encodeURIComponent(svgData)));
});
入力された文字列のハッシュ化は「js-sha1」というハッシュ関数ライブラリを使用している。sha('文字列') でハッシュ値を生成してくれる。


次にハッシュ値の先頭25桁を取得し、1文字ずつcharCodeAtで文字コードに変換し、2で割り切れたらブロックを描画(色を付ける)、割り切れなかったら白ブロックにする。

という具合に5×5のアイコンを生成している。
この部分を修正すれば、GitHubのような左右対称のアイコンも生成できる。


ハッシュ値の残り15桁で描画するブロックの色(RGB)を決める。
15桁を5桁ずつに3分割し、それぞれでcharCodeAtで文字コードを取得し合算する。それを255で割った余りをRed, Green, Blueに当てはめている。


ダウンロード機能は、一旦SVGを描画したあとにXMLSerializerを使いDOMツリーをテキストに変換。それをImageとして読み込み、canvasのdrawImageで描画し、toBlob関数とFileSaver.jsを使ってダウンロードしている。





関連記事




参考サイト




以上

written by @bc_rikko

0 件のコメント :

コメントを投稿