フィネットエンジニアだより

株式会社フィネットのエンジニア感があるんだかないんだかよくわからないブログ

#pixela で #めそ子イラストコンテスト のイラストを描いた話

ご安全に!角田です。

めそ子イラストコンテストの締切が近づいてきたので慌てて Pixela でめそ子のイラストを描いたのでその技術的裏側について補足しておこうと思います。

mesoko.jp

ドット絵を描く

Pixelaは基本APIでpixelの値を入れる(色つけ)形なのでそのままではお絵かきには厳しいため、まずはドット絵用のツールでドット絵を描きます。 今回はPiskelを利用しました。はじめての利用だったけど使いやすかった!

www.piskelapp.com

最終的にはPixelaの色しか出せないので、Pixelaのグラフから各色を抜き出してカラーパレットに設定していきます。 色見本のブログがあったのでこれが大変役立ちました。

ryosms.livedoor.blog

描いたドット絵はこちら

Pixelaは縦が7ピクセル、横が50ピクセルちょいなので、7*50で描きました。

www.piskelapp.com www.piskelapp.com

ドット絵のピクセルを読み込んでPixelaに値を登録する

JSで画像をCanvasに読み込んで、1ピクセルずつ読んで色からグラフのquantityに変換してPixelaにAPIで登録していきます。 開始点の日付から1日ずつPixelを読み進めていく感じです。 今回はajisaiとmomijiのカラースキームを使ったのでその対応表を仕込んでいますが、他の色の対応表を作ればその色でいけます。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>image2pixela</title>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>  
  <script type="text/javascript">
    // PixelaのAPIキーを指定
    const secret = 'thisissecret'
    // Pixelaのユーザ名を指定
    const userName = 'username'
    const urlBase = `https://pixe.la/v1/users/${userName}/graphs/`
    // Pixelaに登録するグラフと画像のファイル名を一致させる
    const targets = ['akari-saiki', 'hisaki-hanasaki']
    const colors = [{
      // ajisai
      '-10': [71,141,141],
      '-6': [85,170,170],
      '-4': [141,199,199],
      '-1': [199,226,226],
      '0': [238,238,238],
      '1': [234,213,255],
      '4': [213,170,255],
      '6': [170,85,255],
      '10': [106,0,213],
    }, {
      // momiji
      '-10': [106,177,35],
      '-6': [128,213,43],
      '-4': [170,226,114],
      '-1': [213,241,184],
      '0': [238,238,238],
      '1': [255,213,213],
      '4': [255,128,128],
      '6': [255,43,43],
      '10': [255,0,0],
    }]
    window.onload = () => {
      for(let i = 0; i < targets.length; i++) {
        const canvas = document.getElementById(targets[i])
        const ctx = canvas.getContext('2d')
        const img = new Image()
        // 他の画像形式の場合は拡張子調整してください
        img.src = `${targets[i]}.gif`
        img.onload = () => {
          ctx.drawImage(img, 0, 0)
          const data = ctx.getImageData(0, 0, canvas.width, canvas.height);
          const values = getValue(data, colors[i])
          // 基準日の1年前の週の日曜日から描画スタート
          const baseDate = '2019-02-23'
          const endDate = moment(baseDate)
          const startDate = moment(baseDate)
          startDate.subtract(1, 'years')
          startDate.subtract(startDate.day(), 'days')
          values.forEach(value => {
            fetch(
              urlBase + targets[i] + `/${startDate.format('YYYYMMDD')}`,
              {
                'mode': 'cors',
                'method': 'PUT', 
                'headers': {'X-USER-TOKEN': secret}, 
                'body': JSON.stringify({'quantity': value})
              }
            ).catch((error) => console.error(error))
            console.log(startDate.format('YYYYMMDD') + ':' + value)
            startDate.add(1, 'days')
          })
        }
      }
    }
    function getValue(data, colors) {
      const values = []
      for(let i = 0; i < data.width; i++) {
        for(let j = 0; j < data.height; j++) {
          var idx = (i + j * data.width) * 4
          Object.keys(colors).forEach(key => {
            const value = colors[key]
            if (data.data[idx] === value[0] &&
                data.data[idx + 1] === value[1] &&
                data.data[idx + 2] === value[2]) {
              values.push(key)
            }
          })
        }
      }
      return values;
    }
  </script>
</head>
<body>
  <canvas id="akari-saiki" width="50" height="7"></canvas>
  <canvas id="hisaki-hanasaki" width="50" height="7"></canvas>
</body>
</html>

画像を読み込んでCanvasに叩き込むのにセキュリティ的な制限があるためローカルでは動作しないので、何かしらのWebサーバを立ち上げて動かす必要があります。 画像ファイルはHTMLと同じパスに置きます。 なお読み込んだだけでそのままPixelaのAPIを叩きに行くアグレッシブな仕様です。

できあがったものがこちらになります

https://pixe.la/v1/users/razon/graphs/akari-saiki?date=20190223 https://pixe.la/v1/users/razon/graphs/hisaki-hanasaki?date=20190223

みんなもPixelaでお絵かきしてみてくれよな!

SUZURIのPixela Shopをそれっぽい感じにする

ご安全に!角田です。

Pixela Shopができましたね!

blog.a-know.me

ただちょっと気になってて、

ということなので、雑にGreasemonkeyを書いて解決していきます。

Greasemonkey - Wikipedia

できあがったものがこちらになります

gist.github.com

いい感じですね。
そんな訳でこのエンジニアブログも今年最後な気がするので、みなさまよいお年を!

あわせて読みたい

twilog.org

GASでつくる #Backlog 警察

ご安全に!角田です。

このエントリは、Backlog Advent Calendar 2018の26日目(勝手にやっているだけ)です。

adventar.org

この記事でやること

先の記事 でGAS使って興が乗ったので、ついでに期限切れのタスクをSlackに通知するBacklog警察もGASで実装してしまおうと思います。
よろしくお願いします。

🔑BacklogのAPIキー発行

個人設定 - API から、Backlog警察用のAPIキーを発行します。管理用のユーザとかでやるのが無難でしょうか。

backlog.com

🖇SlackのIncoming Webhook設定

SlackのアプリにIncoming Webhookを追加します。

f:id:razon:20181225104434p:plain
f:id:razon:20181225104808p:plain
f:id:razon:20181225104810p:plain
f:id:razon:20181225104811p:plain
WebhookのURLができました。

💨GASを書く

function BacklogPolice() {
  var response = fetchBacklogIssues();
  postSlack(JSON.parse(response));
}

var backlogNamespace = 'your_backlog_namespace';
var backlogUrl = 'https://' + backlogNamespace + '.backlog.jp/';

function fetchBacklogIssues() {
  var baseUrl = backlogUrl + 'api/v2/issues';
  var apiKey = 'your_backlog_api_key'
  // 取得対象のプロジェクトIDのリストを指定
  var projectIds = [n];
  var statusIds = [0, 1, 2, 3];
  var sysdate = new Date();
  sysdate.setDate(sysdate.getDate() - 1);
  var params = {
    'apiKey': apiKey,
    'dueDateUntil': formatDate(sysdate)
  };
  for (var i = 0; i < projectIds.length; i++) {
    params['projectId[' + i + ']'] = projectIds[i];
  }
  for (var i = 0; i < statusIds.length; i++) {
    params['statusId[' + i + ']'] = statusIds[i];
  }  
  var paramString = '';
  for (var key in params) {
    if (0 < paramString.length) {
      paramString += '&';
    }
    paramString += key + '=' + params[key];
  }
  return UrlFetchApp.fetch(baseUrl + '?' + paramString);
}

function postSlack(issues) {
  if (issues.length <= 0) {
    return;
  }
  var baseUrl = 'your_slack_incoming_webhook_url';
  var headers = {
    'Content-Type': 'application/json'
  };
  // ここはお好みでいい感じにカスタマイズしてください!
  var params = {
    'channel': '#backlog_notify',
    'username': 'Backlog警察',
    'icon_emoji': ':backlog:',
    'text': createPostMessage(issues),
  }
  var options = {
    'method': 'POST',
    'headers' : headers,
    'payload': JSON.stringify(params)
  };
  UrlFetchApp.fetch(baseUrl, options);
}

function createPostMessage(issues) {
  var message = '<!channel> Backlogのタスクが期限切れになっています!速やかにタスクを処理するか、期限日を調整してください。\n';
  for (var i = 0; i < issues.length; i++) {
    var issue = issues[i];
    message += '[' + issue.issueKey + ']: ';
    message += '<' + backlogUrl + 'view/' + issue.issueKey + '|' + issue.summary + '>';
    message += '[' + issue.status.name + '], ';
    message += formatDate(new Date(issue.dueDate)) + ', ';
    message += issue.assignee.name + '\n';
    return message;
  }
}

function formatDate(date) {
  var format = 'YYYY-MM-DD';
  format = format.replace(/YYYY/g, date.getFullYear());
  format = format.replace(/MM/g, ('0' + (date.getMonth() + 1)).slice(-2));
  format = format.replace(/DD/g, ('0' + date.getDate()).slice(-2));
  return format;
}

🔫GASのトリガーを追加

f:id:razon:20181225110056p:plain
f:id:razon:20181225110058p:plain
f:id:razon:20181225110100p:plain

💪できあがったものがこちらになります

f:id:razon:20181226091752p:plain

よかったですね。
いやよくないですね。あまりBacklog警察にはお世話になりたくないものです!

合同勉強会 in 大都会岡山 -2018 Winter- & 忘年会議2018を開催しました #gbdaitokai #忘年会議

ご安全に!角田です。 2018/12/22(土)に、年末恒例の「合同勉強会 in 大都会岡山 -2018 Winter-」「忘年会議2018」を開催しました。

gbdaitokai.connpass.combonenkaigi.connpass.com

当日の様子や資料なども公開されているので、是非ご覧になってみてください。

togetter.com gbdaitokai.connpass.com

話したこと

Vue CLI が生成したプロジェクトの雛形から、Vue CLIの内側を読み解いていく、みたいな話をしました。
本来のプランであればVueめっちょできるおじさんが場にいて、ツッコミが入りながら皆で理解を深めていければいいなあという目論見があったのですが、みなさんシャイなのかあまりそういうのがなかったのが悔やまれるところでした。

平成30年7月豪雨岡山県災害義援金と平成30年北海道胆振東部地震災害義援金について

先日の合同勉強会 in 大都会岡山 -2018 Summer-に引き続き、災害義援金の募金を募ったのでした。

inet-engineer.hatenablog.com

今回は7月豪雨と北海道地震に半分ずつ寄付してきました。

私の 煽り 呼びかけに応じて募金してくださったみなさま、本当にご協力ありがとうございました。

最後に

岡山での年末恒例となっている当イベントですが、運営がコンテンツを作り込むというより、「みんなで一緒に作り上げていく」という点を重視しています。(運営側が無理なくできる、というところを狙っている面もあります)
毎年多くの方にお越しいただきまして、今年も参加者100人を超える規模になってきたにも関わらず大きなトラブルもなく無事に開催できているのは、毎年発表枠に申し込んでくださったり、足を運んでくださる皆様の多大なるご協力あってのことです。
毎年のことになりますが本当にありがとうございます。

あと今年は運営メンバーを公募したのですが、おかげ様で色々とスムーズに運営することができました。
スタッフの皆様も本当にありがとうございました。
(「来年はスタッフやりたい!」という人は声掛けしてくれていいのよ!)

また来年も開催するので、引き続きご協力いただければ幸いです。
重ねてになりますが、皆様本当にありがとうございました!

2018年度忘年会

イベント委員会森山です。

12月14日、弊社の忘年会が行われました。
連絡ミスで人数が合っておらず、開始で少々バタついてしまい申し訳なかったです。

会場は去年に引き続きやま幸さんです。
f:id:inet_engineer:20181216000635j:plain
f:id:inet_engineer:20181216001030j:plain

スケジュールも直前でちょっと変わっていて、今年は社長挨拶からスタートです。
流れでこのとき幹事挨拶が飛ばされているのですが、まぁいいかと思って黙っていました。
f:id:inet_engineer:20181216002408j:plain

内定者のお二人です。あと一人いるのですが、時間の都合で不参加となりました。
これからよろしくおねがいします。

f:id:inet_engineer:20181216003711j:plainf:id:inet_engineer:20181216003630j:plain

乾杯でスタートです。

f:id:inet_engineer:20181216002837j:plainf:id:inet_engineer:20181216003010j:plainf:id:inet_engineer:20181216003022j:plain

今年は量より質でというオーダーらしいです。

f:id:inet_engineer:20181216004037j:plainf:id:inet_engineer:20181216004047j:plain

今年の催しはビンゴゲームです。
貸し出してもらったビンゴマシーンを使用しています。音は気になりますが、ガラガラするやつよりは扱いが楽でした。
景品はギフト券とやま幸さんの商品券です。ビンゴできた皆さんおめでとうございます。

f:id:inet_engineer:20181216004526j:plainf:id:inet_engineer:20181216004628j:plain
f:id:inet_engineer:20181216005156j:plainf:id:inet_engineer:20181216005251j:plainf:id:inet_engineer:20181216005327j:plainf:id:inet_engineer:20181216005407j:plain
f:id:inet_engineer:20181216005455j:plainf:id:inet_engineer:20181216005517j:plainf:id:inet_engineer:20181216005539j:plainf:id:inet_engineer:20181216005613j:plain

今年も1年お疲れ様でした。来年も頑張っていきましょう。

Backlogのダッシュボードに草を生やす #Backlog #Pixela

f:id:razon:20181128180253p:plain

ご安全に!角田です。

このエントリは、Backlog Advent Calendar 2018の5日目です。

adventar.org

QMK(急にメンションが来たので)書きました。

本題

Backlogのお知らせにはMarkdownが書けるので、ここにPixelaのグラフを放り込んで賑わいを可視化するといい感じなのでは?と思ったので、そんな感じで行きたいと思います。よろしくお願いします。

blog.a-know.me

📊Pixelaのグラフを作る

まずはPixelaのユーザ登録とグラフ作成を行います。
Pixelaは基本APIでビシバシやっていくので、今回はPostmanでAPIを叩いていきます。

ユーザ作成

f:id:razon:20181128164747p:plain
トークンは自分でいい感じのやつを生成してください。

グラフ作成

f:id:razon:20181128165020p:plainf:id:razon:20181128165026p:plain
先ほどユーザ作成時に指定したトークンをリクエストヘッダに指定して、PixelaのAPIを叩いていくことになります。

⚡BacklogのWebhook→Pixelaのincrement API連携

Zapier でいいかなという気もしますが今回は趣向を変えてGoogle Apps Scriptでやります。
(Slackに流すのはZapierでやってるんですよね)

function doPost() {
  var headers = {
    'X-USER-TOKEN': 'thisissecret'
  };
  var options = {
    'method': 'PUT',
    "headers" : headers
  };  
  var response = UrlFetchApp.fetch('Pixela graph increment url', options);  
}

WebhookはPostで飛ぶのでdoPostを実装します。

f:id:razon:20181128165924p:plainf:id:razon:20181128165936p:plain

📄Backlogのお知らせにPixelaのグラフを貼り付ける

スペース設定のお知らせでPixelaのグラフを貼り付けます。

f:id:razon:20181128170334p:plain

⚙BacklogのWebhookを指定

プロジェクト設定のインテグレーションで、Webhookを追加します。

f:id:razon:20181130131902p:plainf:id:razon:20181130131916p:plain

f:id:razon:20181128170834p:plain
WebhookのURLにGASの公開URLを指定します。今回は、課題かWikiの更新が入るとPixelaのquantityがインクリメントされるようにしました。「今生きてるプロジェクト全部の合計で見れればいいかな」と思ったので、対象のプロジェクト全部に同じWebhookを作成して、すべての合計が1つのグラフに集約される形としています。この辺はお好みでグラフを分けるなり重み付けを行うなり調整してみればよいのではないでしょうか。

💪完成したものがこちらになります

f:id:razon:20181204204247p:plain

草生えました。よかったですね。
いやよくないわ!草がいまいち賑わってないわ!これからがんばります。

Python Boot Camp in 岡山が開催されました #pycamp

f:id:razon:20181126111832j:plain

ご安全に!角田です。

11/23(金)に、Pythonのハンズオンイベントである「Python Boot Camp in 岡山」を弊社会場で開催しました。

pyconjp.connpass.com

当日の模様はこちら↓

togetter.com

雑感

講師がハンズオンのカリキュラムを実践しながら参加者に教えていくスタイルだったのですが、事あるごとにTAや参加者からツッコミや「この場合ってどうなるんですか?」みたいなやりとりが入り、和気藹々と進行していってよかったと思います。(イワタプロ とか相当攻めっ攻めでしたね)

今まで会場に入れたことのない人数(参加者18人+スタッフ9人)だったため、だいぶ熱気がすごくて酸素薄い感じだったんじゃないかなというのと、ちょっと社に備え付けてあるWi-Fiが死にかけてる感じだったので、そこはご迷惑おかけしてしまった感があります。次回の改善点としたいと思います。

お陰様でたのしい一日を過ごすことができました。
スタッフの皆様、参加者の皆様、本当にありがとうございました。

勉強会会場、お貸しします

そんな訳で「勉強会開催したいんだけど、会場どうしよっかな…」みたいなことをお考えでしたら、倉敷になりますが是非弊社を選択肢のひとつとしてご検討いただければと思います。
お気軽にお問い合わせください!