まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

2021/04

タイトルそのままです。なんらかの画像をコピーしてクリップボードに格納された状態から、ブラウザ画面内の <canvas> 要素内に画像データをペーストして表示する、という処理が実現できないか試してみました。

結論としてはなんとなく実現できていると思います。サンプルを公開しているのでまずは挙動を試してみてください。


PC でもスマホでも、まず対象の画像をコピーします。今回は「いらすとや」さまのこの画像を使って試してみることにします:
https://www.irasutoya.com/2021/04/blog-post_12.html

2021041402


まずは画像をコピーします。PC の場合は右クリックから「コピー」、スマホの場合は対象画像を長押しするとコピーできると思います:
2021041403


その後、こちらのページを開きます:
https://dotnsf.github.io/web_image_paste/

2021041401



表示された画面内の「ペースト」と書かれた箇所にカーソルを移動した上でペースト(貼り付け)してください(スマホの場合は「ペースト」と書かれた箇所を長押ししてペーストを選択してください):
2021041404


うまくいくと最初にコピーした画像がブラウザ画面内の矩形部分(<canvas>)のサイズに合わせてペーストされます:
2021041405


このサンプルのソースコードはこちらで公開しています:
https://github.com/dotnsf/web_image_paste

このソースコードの中で特に該当の機能を実現しているファイルが index.html です。以下、解説を加えながらこのファイルの内容を紹介します。

まず該当部分の HTML は以下のようになっています:
<div class="container">
  <div id="canvas_div">
    <div id="cdiv">
      <div id="box" contenteditable="true">
        ペースト
      </div>
      <canvas width="80%" height="60%" id="mycanvas"></canvas>
    </div>
  </div>
</div>

画面内の「ペースト」と書かれている部分の <div id="box"> 要素に contenteditable="true" という属性がついています。これによって、この要素部分はペースト可能(Ctrl+V や右クリックメニューでペーストできる)として扱うことができるようになります。単にこの部分に画像をペーストして表示できるようにするだけであれば、この HTML だけで実現できます。

問題はここでペースト処理された画像を、この <div id="box"> 内に表示するのではなく(そのままだと <div id="box"> 内に画像がペーストされて表示されてしまうので、表示しないような処理を加えた上で)代わりにすぐ下の <canvas> 要素内に表示したい、という点です。

今回のサンプルではそういった処理は JavaScript で実現しています。まず「ペースト」と書かれた <div id="box"> 要素にペースト処理が実行されたイベントをフックして、imagePaste() 関数(後述)を実行するように指示しています。加えて false を返すことでフックしたイベントをキャンセルし、通常処理(この場合は <div id="box"> へのペースト処理)が実行されないようにしています(false を返さないと、<canvas> に画像をペーストした後でも <div id="box"> 内にも画像が残ってしまうので、それを避けるための処理です):
$(function(){
  $('#box').on( "paste", function( e ){
    imagePaste( e );
    return false;
  });

  :
  :

そして imagePaste() 関数の実装がこちらです:
function imagePaste( event ){
  var blobimg = null;
  var items = ( event.clipboardData || event.originalEvent.clipboardData ).items;
  for( var i = 0; i < items.length; i ++ ){
    if( items[i].type.indexOf( "image" ) == 0 ){
      blobimg = items[i].getAsFile();
    }
  }

  if( blobimg != null ){
    var bloburl = URL.createObjectURL( blobimg );

    var canvas = document.getElementById( "mycanvas" );
    var ctx = canvas.getContext( '2d' );

    var img = new Image();
    img.src = bloburl;
    img.onload = function(){
      var sw = img.naturalWidth;
      var sh = img.naturalHeight;
      var dw = canvas.width;
      var dh = canvas.height;
      ctx.drawImage( img, 0, 0, sw, sh, 0, 0, dw, dh );
    };
  }
}

まずクリップボード内に登録されているデータを配列で取得し、その中に画像("image")が含まれているかどうかを調べます。存在している場合はそのバイナリデータを取得します。

このバイナリデータが見つかった場合は URL.createObjectURL で画像データの URL を生成して画像化し、<canvas> 内に drawImage() 関数を使って描画します。その際に画像全体の高さや幅を <canvas> 全体の高さや幅に調整して表示するので、それらの情報を取得した上でパラメータ指定しています。

これによって「ペースト」のエリアにペーストした画像データを <canvas> 内に描画し、もとの「ペースト」のエリアには描画しない、という処理が実現できました。


本当は「ペースト」のためのエリアを使わずに <canvas> だけでここに画像を直接ペーストできるようになるのが理想なんですが、実現の可否含めてその方法がわかっていません。実現方法のヒントがありましたら教えていただけると嬉しいです。

 

Github を頻繁に使っていると、この図を頻繁に目にすることになります:
2021041001


"Contributions" というタイトルが付けられたこの図は Github への貢献を可視化したもので、自分のプロファイルページに表示されます。何をもって「貢献」とみなすか?についてはこちらのページを参照していただきたいのですが、簡単に言うと「コミット」のことのようです。自分のリポジトリに対するコミットの回数が日毎にカウントされ、コミットがなかった日はグレーで、コミットがあった日はその回数に応じて緑が濃くなって、およそ過去1年分が表示されているようでした:
2021041002
(↑自分は 2020/11/05 に 12 回コミットしていたらしい様子。。)


ちなみに縦1列が1週間を意味しています。一番上が日曜日、一番下が6日後の土曜日で、縦7データです。これが一年分あるので横幅は50強のデータがあります。この画面をキャンバスとみなして絵や文字列メッセージを表示できたら・・・というのが当初のアイデアでしたが、1年もの間、コミットの回数をこのためだけに調整するのは難しい。。

コミットの回数はコミットの粒度にも関係しているので、短絡的に「回数が多い=より貢献している」とは言い切れない部分もありますが、一方でこういったゲーミフィケーション的な視覚化はモチベーションにも繋がると思っていて、視覚化の UI 含めてよく考えられていると思っています。


・・・で、自分でもよくわからないのですが、たまに狂気的なアプリアイデアが浮かぶことがあります。今回もそれに近い感じで、「この Github Contributions 図を自分でも作ってみたい」「自由な位置に色を配置してデジタルサインのように見えたら面白そう」といった不思議な欲求が生まれて、そこから味付けしたサービスを作って公開してみたのでした。


作ってみたのがこちらです。申し訳ないですがスマホからの利用を想定しておらず(理由は後述)、PC ブラウザからアクセスしてください:
https://github-commit.mybluemix.net/

2021041101


(↓2021/04/12 追記)
バージョンアップして、画面右上に "Message" ボタンを付けました。このボタンを使うと下で紹介するように1件ずつコミットしなくても、メッセージを直接入力してまとめてコミットすることができるようにしました。
(↑2021/04/12 追記)

画面上部に Github Contributions に似た図が表示され、直近1年分のコミット結果が視覚化されます。ちなみにこの部分は(一部わざと変えている部分もありますが)本物同様の動的な SVG を生成して表示を実現しています。

ここでの「コミット結果」とは Github へのコミットではなく、あくまで「このサービス内でのコミット」を対象にしています。なので最初にアクセスした時点ではコミットは1つも登録されていないはずなので、Contributions は全てグレーで表示されているはずです。

コミットは画面下部で行います。右側のカレンダーをクリックしてコミットする対象の日をカレンダーから指定することもできます(指定しない場合は「今日」のコミットとして扱われます)※。

※実際の Git にはこのような日付を調整してコミットする機能はありません。
2021041102


仮に 2021/02/14 を選択してみました。Contributions 図も 2021/02/14 だけが赤枠で表示され、これから行うコミットの対象となる日が明示されます(繰り返しますが、実際の Git にはこのような機能はありません):
2021041103


対象日が決まったらコミットします。画面左下のコマンドライン($ に続く部分)はテキストが入力できるので、まずはここを選択します:
2021041104


選択後に git のコミット命令("git commit")を入力します。-m オプション後にコミットコメントを指定することもできます(指定しない場合はコメントなしとしてコミットされます)。最後に Enter キーを押します(上で書いた件の補足ですが、この部分のインターフェースが PC での作業を想定しており、スマホではなく PC でのアクセスでないと色々面倒だと思っています):
2021041105


するとカレンダーで指定した日(指定されなかった場合は今日)に1回コミットが記録されます:
2021041106


これを繰り返して色を濃くしたり、別の日にコミットを記録することができます。なお画面右上の "Reset" で全てコミットを取り消してやり直すことができます。またクッキーでブラウザを登録するので、同じブラウザからであれば次回アクセス時に続きを行うことができるようになっています。注意点として、Contributions は直近1年分を対象としているので、画面内に表示できそうでも1年以上前の日付は登録できません。また翌週(翌日曜)になるとデータが横に1列ずれてしまい、一番左にあった列のデータは表示されなくなることに注意が必要です。

で、気長に頑張るとこんなこともできます。というか、これをやってみたかったがためのサービス開発&ブログでした:
2021041107


自分でやってみてわかりましたが、このハローワールド程度でもかなり根気のいる作業です(苦笑)。ただ "Github Art" みたいな感じで面白くもあるなと。4段階のグラデーションも付けられるので、がんばれば絵の一部も描けるかも。。と思ってますが、実際はかなり大変だと思ってます。


Github を使ったことのない人はぜひコミットの練習に使ってみてください(実際の手順とはちと異なりますけど (^^;)。もしかしたら「Github で仕事してる風に見える」かも。

元ネタはこの記事です:
Cloudflare の ngrok的なサービスがあるらしい ⇒ $ cloudflared tunnel --url localhost:8080 といった使い方

(特に開発中のサービスなど)ローカルホスト内で動いている状態のウェブサービスを試験的・一時的にインターネットに公開して外部から利用できるようにするものです。この手のツールとしては ngrok が有名ですが、CDN で有名な Cloudflare 社も同様のツールを公開していたんですね。。

上記ページはその導入手順を含めて紹介しています。また公式のインストールページもリンクされています:
https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation


が、残念ながらラズベリーパイ向けのインストール方法はここで書かれている方法だけではわかりにくい内容でした(要はラズパイの場合、ソースコードからビルドする必要があるのですが、ビルドの前提となる Go 言語のインストールがまた面倒で・・・)。というわけで、ラズベリーパイ環境向けの cloudflared のインストール手順を以下に紹介します。


【ラズベリーパイ向け cloudflared のインストール手順】
(1) Go 言語のインストール

この後の手順で Go 言語を使って cloudflared をビルドします。そのため Go 言語をインストールしておく必要があります。

以前に紹介した以下のサイトを参照して、ラズベリーパイに Go 言語をインストールしてください:
ラズベリーパイに Go 言語をインストールする


(2) cloudflared のビルド

Go 言語用のソースコードをダウンロードして、Go 言語でビルドしてツールを作成します。

まず GitHub からソースコードをダウンロードします:
$ git clone https://github.com/cloudflare/cloudflared.git

ダウンロードしたソースコードを Go 言語でビルド&インストールします:
$ cd cloudflared

$ make cloudflared

$ go install github.com/cloudflare/cloudflared/cmd/cloudflared

ここまでの手順で ~/go/bin/cloudflared に実行バイナリが作られています。このままだと利用が不便なので、パスの通ったフォルダ(例えば /usr/local/bin/ 以下)にこのファイルをコピーします:
$ sudo cp ~/go/bin/cloudclared /usr/local/bin/

これでインストール完了です。


【ラズベリーパイで cloudflared を使ってみる】

ではインストールした cloudflared を実際に使ってみます。そのためには何でもいいのですが、何らかのウェブアプリケーションをラズベリーパイ上で動かす必要があります。docker で nginx を動かすとか、適当なウェブアプリを動かすとかでも構いません。こちらの方法を参照いただいて Node-RED をインストールして動かす、とかでも構いません(以下は最後の方法を実行し、 $ node-red コマンドまでを実行して、Node-RED が 1880 番ポートで稼働しているという前提で説明を続けます):
2021040801
(↑プライベートアドレスの 1880 番ポートで待受け準備完了した様子)


ラズベリーパイ上で何かウェブアプリが動いていたら、ラズベリーパイの端末(既に利用中の場合は別のターミナルの画面)から cloudflared コマンドを実行します:
$ cloudflared tunnel --url localhost:1880

※上述コマンドの 1880 部分は実際にリクエストの待受けをしているポート番号を指定します

すると実行結果の画面に以下のようなメッセージが表示されます:
  :
  :
2021-04-08T05:01:22Z INF +-------------------------------------------------------+
2021-04-08T05:01:22Z INF |  Your free tunnel has started! Visit it:              |
2021-04-08T05:01:22Z INF |    https://xxxxxx-xx-xxxxxx-xxxxxx.trycloudflare.com  |
2021-04-08T05:01:22Z INF +-------------------------------------------------------+
  :
  :

この青字で表示された URL が cloudflared のトンネリングによってインターネットに公開されたアドレスです。自分の PC はもちろん、全く別のネットワークに接続された別の PC からでもウェブブラウザでこのアドレスにアクセスすると、ラズベリーパイの localhost:1880 で稼働しているアプリケーションをパブリックなインターネットから見ることができるようになります:
2021040802
(↑パブリックアドレスで https で待受け準備完了した様子)



ラズベリーパイへの Go 言語インストール、普通にリポジトリから $ sudo apt-get install go でインストールできるかと思っていたら出来なかったので手順を調べました:
go


2021/04/07 現在ではラズベリーパイに Go 言語をインストールするには最新版バイナリ(のアーカイブ)をダウンロードして展開するのがてっとり早そうでした。

まずはこのページで Go 言語の最新バージョンを確認します:
https://golang.org/dl/

2021040701


2021/04/07 時点での最新版安定(stable)バージョンは 1.16.3 のようでした。以下、このバージョンをインストールする前提で説明を続けます。

ラズベリーパイのターミナルから、ラズパイアーキテクチャ(armv6l)向け最新バイナリをダウンロードして、/usr/local 以下に展開します:
$ wget https://golang.org/dl/go1.16.3.linux-armv6l.tar.gz

$ sudo tar -C /usr/local -xzf go1.16.3.linux-armv6l.tar.gz

これで /usr/local/go フォルダ以下に Go 言語がコピーされました。実際の go コマンドは /usr/local/go/bin/go に存在している状態です。

このままだとパスが通っていなくて不便なので、パスを通します。~/.bashrc ファイルをテキストエディタで開いて、最終行に以下を追加して保存します:
export PATH=$PATH:/usr/local/go/bin

保存後、 $ source ~/.bashrc を実行するか、ターミナルを一度終了して開き直すと設定が有効になります。go version コマンドを実行して、以下のような結果が表示されればインストール成功です:
$ go version
go version go1.16.3 linux/arm

 

Ubuntu ベースの環境(ラズパイなども含む)にアプリケーションを追加インストールする際には apt コマンドを使います。例えばこんな感じ:
$ sudo apt install nginx
※ apt の代わりに apt-get を使うこともありますが、現在推奨されているのは apt コマンドらしいです。

このコマンドは Ubuntu システム標準のソースリポジトリにあらかじめ nginx が登録されていて、その中で具体的に必要なモジュール一式やその導入手順が参照できるようになっていることで実現しています。インストールコマンドとしては上記のたった1行のコマンドで済むようになっていますが、実際には最新バージョンがいくつで、その導入にはどのような依存モジュールのどのバージョンが必要で、現在のシステムに導入済み(インストールの必要ないもの)は何で、、といった情報が参照され、必要なモジュールだけがインストールできるようになっています。

が、たまにこのコマンドだけではインストールできないことがあります。原因はいくつかありますが、その1つが「対象アプリケーションが標準のソースリポジトリに登録されていない」場合があります。要は目的のアプリケーションをインストールする手順や依存ライブラリが標準構成のソースリポジトリには登録されていないのでインストールできない、ということになります。例えば CloudFoundry の CLI ツールである cf (アプリ名としては cf-cli)をそのまま apt コマンドでインストールしようとしても以下のようなエラーになります:
$ sudo apt install cf-cli
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package cf-cli

このように標準ソースリポジトリに登録されていないことが原因でインストールできない(が、別のソースリポジトリを登録すればよい)場合は、対象アプリケーションが登録されたリポジトリを登録しなおしてあげればインストールできるようになります。

リポジトリを登録し直す方法はアプリケーションによっても異なるのですが、例えば上述の cf-cli の場合は公式ドキュメントによると以下の手順でリポジトリを登録できるようです:
$ wget -q -O - https://packages.cloudfoundry.org/debian/cli.cloudfoundry.org.key | sudo apt-key add -

$ echo "deb https://packages.cloudfoundry.org/debian stable main" | sudo tee /etc/apt/sources.list.d/cloudfoundry-cli.list

このコマンドの最後に /etc/apt/sources.list.d/ ディレクトリ内に cloudfoundry-cli.list というファイルを作成して登録しています(apt-add-repository コマンドを使って登録する方法もありますが、その場合も同ディレクトリ内にファイルが追加されます)。

なお、このようにソースリポジトリに変化があった場合はインストール前にキャッシュ内容を更新する必要があります:
$ sudo apt update

更新後は cf-cli がインストール可能です:
$ sudo apt install cf-cli

ここまでは apt の仕組みなどにあまり詳しくなくても、インストール手順を調べながら詳しく知らずに実行していることもあると思っています。


さて、問題はここからです。自分も知らなかったのですが、このように「標準構成以外に追加登録したリポジトリ」を削除したい、つまり追加登録しなかった状態に戻すにはどのようにしたらいいでしょうか? これ、意外と情報を見つけるのが難しくて苦戦したのでした。その備忘録としての本ブログでもあります。

答は以下です。3段階の手順が必要でした(特に (2) を忘れがち)。

(1) /etc/apt/sources.list.d/ 以下に追加された該当ファイルを削除する
$ sudo rm /etc/apt/sources.list.d/cloudfoundry-cli.list

(2) 記録された該当キーを見つけて削除する
$ apt-key list

(結果の一覧から cloudfoundry-cli のキーを見つける。仮に "1234ABCD" だったとする)

$ sudo apt-key del 1234ABCD

(3) リポジトリ更新
$ sudo apt update


無事にリポジトリ情報を削除して、削除した状態で更新できました。めでたしめでたし。



【参考】
https://unix.stackexchange.com/questions/219341/how-to-apt-delete-repository



このページのトップヘ