canvasで書いたデータをDBに保存(canvas +JavaScript + Rails)
canvas APIで描画したデータを保存する方法を学んだので実際に行ったことをアウトプットしていきます。
結論として、最初から保存される状態や形式、データ型などに拘らなければすぐ終わったのですが、随分と遠回りしてしまいました😅
最終的にはcanvasデータをバイナリ形式のままでDBに保存、読み込み編集できるというのと、画像としてPCに保存できるようにするということで2つに分けて実装することにしました。
バージョンについては前回のアプリをベースにしています。
Rails 5.2.3
rbenv 1.1.2
ruby 2.5.1
はじめに
スクール学習中に経験したのですが、jQueryをRails上で反映させるにあたりgem 'turbolinks'の有無でローカル環境とデプロイ環境で動作が変わりました。
残したままでlink_toタグにdata: {'turbolinks' => false}
を付与してリンク先で切っても、デプロイ環境ではエラーが出続けたり色々あったので、この辺りは大体切っています。
後は、application.jsのrequire部分のrails-ujs
とjQuery-ujs
。この辺も競合したのでどっちかだけ残すように気をつけています。
canvasデータを画像としてPCへ保存
これは公式や個人記事でのソースがあったので色々参考にしながら実装しました。
割と決まった書き方になっていたのでそのまま写している部分もあります。
とりあえず「保存」というボタンを実装し、JavaScript(jQuery)でイベントを書いていく。
$('#canvas-submit').on('click', function(){ // 処理用の関数などを書く });
ボタンを押した時の処理をする関数をsaveCanvas()
として関数を作成していく。
function saveCanvas(){ let imageType = "image/png"; let fileName = "sample.png"; var base64 = cvs.toDataURL(imageType); var blob = base64toBlob(base64); var url = (window.URL || window.webkitURL); var dataUrl = url.createObjectURL(blob); var a = document.getElementById('canvas-submit'); a.href = dataUrl; a.download = fileName; } function base64toBlob(base64){ var tmp = base64.split(','); var data = atob(tmp[1]); var mime = tmp[0].split(':')[1].split(';')[0]; var buf = new Uint8Array(data.length); for (var i = 0; i < data.length; i++) { buf[i] = data.charCodeAt(i); } var blob = new Blob([buf.buffer], { type: mime }); return blob; }
1つ目の関数について。1,2行目は変数宣言。これらは後で利用します。
3行目はcanvasタグのURLをbase64形式で取得している。
4行目は下記関数を呼び出しのコード。base64データををblobデータに変換している。
5行目はブラウザの種類?(Chrome, Safariなど)URLオブジェクトを作成した際に変数名が異なるので名前を統一する為に変数宣言して代入させている。
6行目はcreateObjectURL()という引数で指定されたオブジェクトを表すURLを生成するメソッドを5行目で宣言した変数urlに使用させているコード。引数には4行目で変換したblobデータを渡している。
最終的にaタグのリンクに持たせるダウンロード用のURLを作成している。
7行目ではクリックしたボタン要素を変数に代入。8、9行目はダウンロード用のURLの用意とファイル名をセット。
最終的には、クリックするとcanvasデータをPCでダウンロードするようになっている。
下側の関数では前述したとおりbase64データをblobデータに変換している。
1行目はカンマで分割してデータを配列で分けてtmpに代入、tmp[0]:データ形式(data:image/png;base64,)、tmp[1]:base64データ
2行目ではbase64のデータをatobメソッドでデコード。ランダムな文字列が並んでいるbase64データを引数に渡している。
3行目はtmp[0]の文字列(data:image/png;base64)からコンテンツタイプ(image/png)部分を取得し代入(=> image/png;base64 => image/png)
4行目では1文字ごとにUTF-16コードを表す 0から65535 の整数を取得、newなのでオブジェクトを生成してるのかな。
5行目からのfor文はcharCodeAt()メソッドで、与えられたインデックスに位置する文字の UTF-16 コードを表す 0 から 65535 の整数を返している。
この値はコンソールで表示させると配列で表示されるのは確認している。
その下の変数blobはblobオブジェクトを作成。引数にはこれまでで用意した変数を渡している。
最後は呼び出した関数base64toBlob()にreturnで値を返している。
参考にさせていただいた記事
HTMLCanvasElement.toBlob()
String.prototype.charCodeAt()
Blob()
Canvas上のイメージを画像ファイルとして保存する方法
Canvas に描いた画像を png などの形式の Blob に変換する方法
canvasデータをDBに画像ファイル(.pngとか)として保存させる
色々あって失敗。file_fieldに直接value渡そうとして、セキュリティ上の問題で対策されていて実装に失敗したり。最終的にたどり着いた記事
最終的には「そもそも各投稿毎に保存されて編集もできるようにする」という要件を満たす為に、画像として色々変換させてから保存させるのって読み込む時も面倒になるんじゃってことで断念。
DBにバイナリデータとして保存させる
再度読み込み編集、更新する物を変換しきってしまうのは面倒だと思ったので、Railsのデータ型を変えて保存させてしまった方が早いのでは?となり。
postsテーブルのimageカラムのデータ型をtext
→binary
に変更するためのマイグレーションファイルの発行。
% rails g migration change_data_image_to_post
生成された中身。
class ChangeDataImageToPost < ActiveRecord::Migration[5.2] def change end end
RailsのMigrationでbinary型を選択すると、MySQLを使用した場合はblob型で解釈されるとのこと。
また、binary型はdefaultでは64KB(65535Byte)までしか保存できないというのを目にしました。
canvasのデータが書き込まれる毎にサイズが増えていく、というのをコンソール上でBlob{size: 79345}
のような形で確認。
簡単に64KBは超えてしまっていたのでmediumblob(最大16MB)に変えます。
下記のように書くとマイグレーション後にはdb/schema.rb
でlimit: 16777215
となっていることが確認できます。
class ChangeDataImageToPost < ActiveRecord::Migration[5.2] def change change_column :posts, :image, :binary, limit: 10.megabyte end end
最初に発行した時にpostsと記入しなかったので、1行目複数形でないのが少し気になりましたが書き換えてしまうとマイグレーションできなかったので名前はこのまま。
change_column :posts, :image, :binary, limit: 10.megabyte
は
メソッド テーブル名 カラム名 データ型 オプション
の順番で書かれている。
基本的な書き方についてや、メソッドの種類、オプション種類については一通り公式ドキュメントに書いてある。
Railsドキュメント/マイグレーションとは
細かくどういうことをするのかとかは説明の上手い人の記事見たりしながら自分のアプリ上で何が起こるか目にした方が早いです。
% rails db:migrate
でDBにマイグレーション情報を適用させる。適用できたかどうかは% rails db:migrate
をする前後で
% rails db:migrate:status
で確認すれば差異がわかる。移行前は以下のような感じで表示される。
database: freehands_writer_development Status Migration ID Migration Name -------------------------------------------------- up 20200521070549 Create posts down 20200602044245 Change data image to post
downはまだマイグレーションされてない状態。
% rails db:migrate
した後に確認して二つともup
になっていればOK。
参考にさせていただいた記事を貼っておきます
ActiveRecordでbinary型をblob以外の型にする
【Rails・MySQL】MySQLのデータ型とRailsのマイグレーションファイルのデータ定義の対応まとめ
バイナリデータを投稿できるようにしていく
送信ボタンを押した時にcanvasのデータを送信できるようにJavaScriptを書いていく。
// form.html.erb // labelタグは必要なくなる + file_fieldから変更。 <%= form.hidden_field :image, class: 'hidden' , value: '' %> //index.erb.html, show.erb.htmlなどのビューではimage_tagは外しておく。
$("#post_submit").on("click", function(e) { e.preventDefault(); var base64 = cvs.toDataURL(); $('#post_image').val(base64); $('#new_post').submit(); });
file_fieldではvalueを渡せなかったのでフォームヘルパーを変更し、hidden_fieldを使用。
念のためconsole.log
などでvalに挿入できたか確認→OK。
atobメソッドでデコードをしなかったらこんな感じ(つーか、blobにするとエンコードしないとRailsからエラー返されて表示すらできない)。
1行目のpreventDefault();
がないとイベントが発生したらformの動作が勝手に進行してしまうのでここで止めてる...というくらいの感覚で使用してます。
一応確認として、引数eをconsole.logしてみるとjquery.fn.initが取得できた。押した時のDOM要素とイベント~.submit
みたいなのが書いてあったので、これに対してpreventDefault()
を使用することで止めているのが分かる。
最後に処理を止めているform要素のidに対して、submitメソッドを使用することでDBにデータ送信。
この流れでbase64データをDBに保存することができた。
投稿したデータを表示できるようにする
Base64形式で保存された物はimage_tag
で、src属性に@post.image
を指定しておくだけで、そのまま画像として表示することができる。
表示できるデータがない場合は、alt: 'No Image'
で何もデータがない時に表示させる文字を指定しておく。
DBからデータを読み込んだ際にJavaScriptを使ってcanvasに反映させるには、Railsの変数をJSに渡して使用する必要があるのではないかと思い、それを可能にするgem 'gon'を導入。
// どの環境においても使用するため、Gemfile最下部に記述 gem 'gon' // 対象ディレクトリ上にcdして % bundle install // application.html.erb のheadタグ内で記述してgonを使って読み込みできるようにしておく <%= include_gon %>
これでgem 'gon'
を使用する準備はできたので、次は実際に繋ぐためのコードを書いていく。
def edit @post = Post.find(params[:id]) gon.post_image = @post.image end
各投稿のcanvasデータを編集するedit.html.erb
に変遷した時、gonを使用してRailsで変数宣言。
変数名の前にgon.
を書くことでこの変数をJavaScriptで使用することができる。
後はこの変数を使ってcanvas上にBase64の画像データを読み込ませるように記述していく。
var img = new Image(); img.src = gon.post_image; img.onload = function(){ conText.drawImage(img, 0, 0, 800, 600); }
1行目では画像オブジェクトを生成し、2行目で先ほど表示させたようにsrc属性にBase64データを代入。
ここでRailsのgonを使用して定義した変数を使用している。
3行目~4行目で画像データををcanvasに設定している。引数は表示する画像、x, y, 大きさx, 大きさy。
0にしないと表示する場所が微妙に変わってしまうのと、大きさの数値を元の数値と合わせないと拡縮されてしまうので注意が必要。
参考にさせていただいた記事
【gem gon】Railsで定義した変数をさくっとJavascriptで使う
[JavaScript] Base64形式の画像データをcanvasに表示する
名前を間違えたり、gem 'gon'にたどり着くまで時間がかかった。
色々と可能性を感じるgem。また使える機会があれば!
したらな❗️ 👋