"BOKU"のITな日常

62歳・文系システムエンジニアの”BOKU”は日々勉強を楽しんでます

WEBブラウザで手描きしたイメージをJPEG・PNG変換後Base64エンコード/django+Fabric.js

ブラウザ上でイメージを手描きして、それをPython等の後続処理で使えるようにBase64形式に変換して、textareaにセットするところまでやってみます。

f:id:arakan_no_boku:20191216111232p:plain

 

はじめに

 

Python等で分類器などを作ってWEBブラウザ上でデモなどをするとき、必要になるのが「画像イメージをWEBブラウザ上で取得して、SUBMITでPython等サーバー側の処理に渡せる形式(Base64)に変換する」という処理です。

今までも、散発的には記事にしてるのですが、自分でもど忘れした時に、あちらこちら見るのが面倒になってきたので、ちょこっとまとめておこうと思います。

今回は、画面上で手で描画したイメージを取り込むパターンでやってみます。

Django2.0+bootstrap4のベース(jQuery付き)です。

 

手書き描画にはライブラリを使います

 

ブラシを使ったFREE DRAWINGは、HTML5CANVASを使えば実現できるのですが、素で扱うと少々面倒くさいです。

なので、今回はCANVASの扱いを簡単にしてくれる「Fabric.js」を使います。 

fabricjs.com

現状、ブラシの種類も少なくて、基本的な機能だけ提供されていますが(Free Drawingは・・です。それ以外は豊富です)、今回はそれで充分ですから。

一応、以下を引用しておきます。

In the near future, we are planning to add more options for free drawing — various versions of a brush (e.g. spray-like or chalk-like). Also custom brush patterns, and an option to extend with your own, similar to Fabric image filters.

近い将来、さまざまなバージョンのブラシ(例えばスプレーまたはチョークのような)など、フリー描画のオプションを追加する予定です。

また、カスタムブラシパターンと、ファブリックイメージフィルタと同様に独自に拡張するオプションもあります。

 

まずはHTML部分から

 

Django2.0のテンプレート機能を使います。

Base.htmlを読み込んで使う、以下の記事のやり方を踏襲しています。

arakan-pgm-ai.hatenablog.com

埋め込む部分のHTMLです。

{% extends 'base.html' %}
{% load static %}
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}
{% load widget_tweaks %}

{% block header %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.3/fabric.js"></script>
{% endblock %}

{% block title %}
  サンプルその1
{% endblock %}

{% block content %}
<div class="container">
    {% if base64text %}
    {% else %}
    <div class="row my-4">
        <canvas id="canvas" style="border:solid 1px red" width="300" height="300"></canvas>
    </div>
    {% endif %}
    <form action="" method="post">{% csrf_token %}
        {% if base64text %}
        <div class="form-group row my-4">
           	<img src="{{ base64text }}" />
        </div>
        <a href="{% url 'canvas01' %}"><h2>もう一回</h2></a>
        {% else %}
        <div class="row my-4">
        	<div class="col-lg-2">       
	            <button type="submit" class="btn btn-primary">サブミット</button>
    		</div>
        	<div class="col-lg-2">       
                <input type="button" id="tobase64btn" onclick="canvas_to_base64()" value="Base64変換" />
            </div>
           	<div class="col-lg-2">       
                <input type="button" id="clearbtn" onclick="canvas_clear()" value="クリアー" />
            </div>
            <div class="col-lg-4"></div>
        </div>    
        <div class="form-group row my-4">         
            {{form.areaone|add_class:"form-control"}}
        </div>
        {% endif %} 
    </form>
</div>
<script type="text/javascript" src="{% static "js/canvas01.js" %}"></script>  
{% endblock %}

補足します。

まず、Fabric.jsを使うためにライブラリを読み込みます。

<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.3/fabric.js">

手描きするためのエリアはCanvasで定義します。

  <canvas id="canvas" style="border:solid 1px red" width="300" height="300"></canvas>

 JavaScript内でID指定で参照参照しているため、idは「canvas」から変更できません。

Canvasに描いた図を画像フォーマット(JPEG)にしてBase64変換するJavaScriptの関数をキックするボタン。

<input type="button" id="tobase64btn" onclick="canvas_to_base64()" value="Base64変換" />

および、Canvas内をクリアするボタン

<input type="button" id="clearbtn" onclick="canvas_clear()" value="クリアー" />

 を追加しています。

{% if base64text %}以下の部分が、画像を描いてBase64変換後に、「サブミット」ボタンを押した後に表示する画面。

{% else %}以下の部分が、初期表示です。

とりあえず、初期表示画面のイメージです。

f:id:arakan_no_boku:20191216184617p:plain

 

Canvasを操作するJavaScript

 

上記で読み込んでいるJavaScriptです。

canvas01.js

var canvas = new fabric.Canvas('canvas', {
    isDrawingMode: true
});
fabric.Object.prototype.transparentCorners = false;
canvas.freeDrawingBrush = new fabric['PencilBrush'](canvas);
canvas.freeDrawingBrush.color = "black";
canvas.freeDrawingBrush.width = 18;

function canvas_clear() {
    canvas.clear();
    $("#resultbase64").val('');
}

function canvas_to_base64() {
    $("#resultbase64").val(canvas.toDataURL("image/jpeg"));
}

fabric.Canvasオブジェクトを生成し、isDrawingMode: trueとするだけで、Free Drawingができるようになります。

あとは、ブラシオブジェクトを生成して、色と太さをセットしているだけです。

超簡単です(笑)

HTML5Canvasを使えば、画像をjpegフォーマットにして、Base64形式に変換する処理も「canvas.toDataURL」一発でできます。

もちろん、PNG形式にもできますが、今回はjpegにしました。

 

TextareaにBase64形式テキストをわざわざセットしているわけ

 

入力した画像をJavaScriptで変換しただけでは、djangoのコントローラ側(Pythonプログラム)に渡せないためです。

requestに乗せて、python側で「request.POST["areaone"]」のように受け取るためには、formを使う必要があります。

なので、一旦JavaScriptBase64形式にしたものを、formで定義したtextareaにセットして、Submitボタンでtextareaの内容としてrequestにのせてます。

今は検証用なので表示して、かつ、わざわざボタン操作で変換しています。

本来は、textareaをHidden(隠しフィールド)にして見えなくする必要があります。

なお。

textareaはdjangoフォームを使っています。

forms.pyに以下のように書いています。

from django import forms


class UserForm(forms.Form):
    areaone = forms.CharField(
        label='',
        widget=forms.Textarea(
            attrs={
                'id': 'resultbase64',
                "rows": 20,
                "cols": 10,
            }
        )
    )

これをHTMLでは以下のようにうけてます。

 {{form.areaone|add_class:"form-control"}} 

 

djangoのコントローラです

 

実行できるように、djangoのviews.pyに以下のように書きます。

from django.shortcuts import render
from . import forms
from django.template.context_processors import csrf


def freedraw(request):
    if request.method == 'POST':
        c = {
            'base64text': request.POST["areaone"],
        }
    else:
        form = forms.UserForm(label_suffix=':')
        c = {'form': form}
        c.update(csrf(request))
    return render(request, 'canvas01.html', c)

最後にurls.pyに以下を追加して、demo02でアクセスできるようにします。 

path('demo02/', views.freedraw, name='canvas01'), 

これでOK。

 

実行してみます

 

 プロジェクトフォルダ(manage.pyがあるフォルダ)をカレントにして。

python manage.py runserver

 それで、例えば「http://localhost:8000/demo02/」のようにアクセスします。

初期表示画面で、手書きの数字を書いて「Base64変換」ボタンを押した状態です。

f:id:arakan_no_boku:20191216210934p:plain

ここから「サブミット」を押すと。

f:id:arakan_no_boku:20191216211034p:plain

ちゃんと手書き数字画像がrequestで渡されて、表示できてます。

OKです。

とりあえず、今回はこんなところです・・が、ちょっとだけおまけがあります。

 

おまけ

 

Chromeデベロッパーツールを使ってデバッグしていたら、コンソールに表示されて、どうしても消えないエラーメッセージがありました。

こういうやつです。

Unchecked runtime.lastError: The message port closed before a response was received.

 気持ち悪かったのですが、以下の記事によれば、無視してかまわないみたいです。

まめに調べてくださってる方がいるんですね。

onoredekaiketsu.com

これはよい情報だと思ったので、リンクをはっておきます。

ではでは。