WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

Canvas に複数の画像を順番通り描画

by WebSurfer 24. October 2019 12:30

Canvas に複数の画像を指定した順序で描画し、その後 Canvas に描画された画像を Data Url 形式で取得する方法を書きます。元の話は Teratail のスレッド「CanvasにdrawImageで描写した画像をtoDataURLで取得したい」です。

Canvas と img タグの画像

上の画像がその結果を Chrome で表示したもので、左が Canvas の画像、右が canvas.toDataURL メソッドを使って Data Url 形式で取得した画像データを img 要素の src 属性に設定して表示した結果です。

Canvas に表示する複数の画像の数だけ new Image() で img 要素を生成し、その src 属性に画像の url を設定すると img 要素が画像に読み込みを完了した時点で load イベントが発生するので、それにリスナをアタッチして Canvas への書き込みと Data Url 取得の処理を行うのが基本です。

ただし、問題は、Canvas に描く画像が複数ある場合、img 要素による画像の読み込みにかかる時間が画像のサイズなどによって異なるので、複数ある img 要素の load イベント発生の順番は不定となることです。

もう一つ、canvas.toDataURL メソッドを使って Canvas から Data Url 形式のデータを取得するタイミングも問題です。全ての画像の Canvas への描画が終わるのを待たなければなりません。

上の問題に対応するには、最後の load イベントの発生を待って、そのイベントリスナで複数ある全ての img 要素の画像 を希望する順番に context.drawImage メソッドで Canvas に書き込むのがよさそうです。

context.drawImage は同期メソッドなので、同じリスナ内で、全ての画像の context.drawImage が終わった後に canvas.toDataURL メソッドを記述すれば Data Url 形式のデータを取得できます。(リスナの外に canvas.toDataURL メソッドを置くのはダメです。img 要素の load イベントが発生する前に canvas.toDataURL に制御が飛ぶので)

検証に使ったコード、即ち上の画像を表示したコードを以下に書いておきます。(ASP.NET のページを利用していますが、そこは本題とは関係ないので気にしないでください) 元の画像は 125px x 75px の色が異なる .png 画像 5 枚です。それらを 20px づつ右下にずらしながら描画しています。

<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="0076Canvas2.aspx.cs" Inherits="_0076Canvas2" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; 
  charset=utf-8"/>
  <title>0076Canvas2.aspx</title>
  <script src="/Scripts/jquery.js"></script>
  <script type="text/javascript">
  //<![CDATA[
    window.onload = function () {
      // それぞれ 125px x 75px の単色 png 画像
      // 色は名前の通り赤, 青, 緑, 黄, 橙
      var srcs = [
          "/Images/red.png",
          "/Images/blue.png",
          "/Images/green.png",
          "/Images/yellow.png",
          "/Images/orange.png"
      ];

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

      // canvas に描かれる順番を保証するには以下のようにする。
      // そうすればリスナの中に canvas.toDataURL("image/png");
      // を記述でき Data Url が取得できる
      var images = [];
      var loadCount = 0;
      for (let i = 0; i < srcs.length; i++)
      {
        images[i] = new Image();                
        images[i].onload = function () {
          loadCount++;
          if (loadCount == images.length)
          {
            for (let j = 0; j < images.length; j++)
            {
              context.drawImage(images[j], j * 20, j * 20);
            }

            // image.onload のリスナの中でないと Data Url
            // は取得できないので注意
            var result = canvas.toDataURL("image/png");
            document.getElementById("image1").src = result;
          }
        };
        images[i].src = srcs[i];
      }
    }
    //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server">
  <div style="float: right;">
    <canvas id="mycanvas" width="205" height="155">
    </canvas>
  </div>
  <div>
    <img id="image1" alt="" />
  </div>
  </form>
</body>
</html>

他に jQuery.Deferred() を使う方法もあります。そのコードを以下に書いておきます。

// jQuery.Deferred を使用
var images = [];
var dfds = [];
for (let i = 0; i < srcs.length; i++)
{
    images[i] = new Image();
    let dfd = new $.Deferred();
    dfds[i] = dfd;
    images[i].onload = function () { dfd.resolve(); };
    images[i].src = srcs[i];
}

$.when.apply($, dfds).done(function () {
    for (let i = 0; i < images.length; i++)
    {
        context.drawImage(images[i], i * 20, i * 20);
    }
    var result = canvas.toDataURL("image/png");
    document.getElementById("image1").src = result;
});

全ての img 要素の load イベントが発生し終わるのを待って Canvas への描画と Data Url 形式のデータを取得するというところは同じですが、見かけは何となくカッコいいかも。(笑)

Tags: , , , ,

JavaScript

Dictionary の JSON シリアライズ

by WebSurfer 2. June 2018 18:56

Dictionary<TKey, TValue> クラスのオブジェクトを、ASP.NET MVC の Controller クラスの Json メソッドなどで使われている JavaScriptSerializer クラスを用いて JSON 文字列にシリアライズするとどうなるかという話を書きます。

Dictionary の JSON シリアライズ

例えば、以下のような Controller のアクションメソッドで Dictionary<string, Car> オブジェクトを作って Json メソッドで JSON 文字列にシリアライズしてみます。(注: TKey は JSON の「名前:値」ペアの「名前」になるので string 型にする必要があるようです。実際 int 型ではエラーになりました)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Mvc5App.Controllers
{
  public class Car
  {
    public string Make { get; set; }
    public string Model { get; set; }
    public int Year { get; set; }
    public float Price { get; set; }
  }

  public class HomeController : Controller
  {        
    public ActionResult JsonDictionary()
    {
      Dictionary<string, Car> dic = 
                          new Dictionary<string, Car>()
      {
        { "Car1", new Car() { Make="Audi",
            Model="A4", Year=1995, Price=2995f } },
        { "Car2", new Car() { Make="Ford",
            Model="Focus", Year=2002, Price=3250f } },
        { "Car3", new Car()  {Make="BMW",
            Model ="503i",Year=1997,Price=1995f } }
      };            

      return Json(dic);
    }
  }
}

上のアクションメソッドをブラウザから POST 要求すると、以下の JSON 文字列が応答として返ってきます。(注: POST 要求するのは、Json メソッドはデフォルトでは GET 要求を拒否するからです)

{
"Car1":{"Make":"Audi","Model":"A4","Year":1995,"Price":2995},
"Car2":{"Make":"Ford","Model":"Focus","Year":2002,"Price":3250},
"Car3":{"Make":"BMW","Model":"503i","Year":1997,"Price":1995}
}

つまり、JSON の「名前:値」ペアとして、Dictionary<TKey, TValue> の TKey が「名前」に、 TValue(Car の JavaScript オブジェクトの JSON 文字列) が「値」に設定され、そのペアが Dictionary の要素の数(上の例では 3 つ)並べられた JSON 文字列になります。

知ってました? 実は自分は知らなかったです。(汗) Dictionary<TKey, TValue> なんて JSON にシリアライズできないと思ってました。(恥)

デシリアライズも、もちろんできるようです。例えば、jQuery alax を利用して上記アクションメソッドを呼び出すと、自動的に JSON 文字列が JavaScript オブジェクトにデシリアライズされ、コールバックの引数として渡されます。

以下の例を見てください、function(data) ... の引数 data にデシリアライズされた JavaScript オブジェクトが渡されます。それを jsonresult という id を持つ div 要素の中に書き込んだのが上の画像です。

function jsonDictionary() {
    $.ajax({
        type: 'POST',
        url: '/home/jsonDictionary'
    })
    .done(function (data) {
        $("#jsonresult").empty();
        $.each(data, function (index, car) {
            $('#jsonresult').append(
                '<p><strong>' + index + '</strong></p>' +
                '<p>' + car.Make + ' ' + car.Model +
                ', Year: ' + car.Year +
                ', Price: $' + car.Price + '</p>');
        });
    })
    .fail(function (jqXHR, textStatus, errorThrown) {
        $("#jsonresult").text('textStatus: ' + textStatus +
          ', errorThrown: ' + errorThrown);
    })
}

上のコードの例では、jQuery の jQuery.each() メソッドを使っていますが、JavaScript の for ... in ループを使って同じ処置を行う例も書いておきます。

for (var key in data) {
    $('#jsonresult').append(
        '<p><strong>' + key + '</strong></p>' +
        '<p>' + data[key].Make + ' ' + data[key].Model +
        ', Year: ' + data[key].Year +
        ', Price: $' + data[key].Price + '</p>');
}

元の .NET Framework の Dictionary<string, Car> オブジェクトにも JavaScriptSerializer や Json.NET (Newtonsoft.Json) を使ってデシリアライズできます。

string json = "上の JSON 文字列";

// JavaScriptSerializer
JavaScriptSerializer serializer = new JavaScriptSerializer();
Dictionary<string, Car> dic =
    serializer.Deserialize<Dictionary<string, Car>>(json);

// Json.NET (Newtonsoft.Json)
Dictionary<string, Car> dic =
  JsonConvert.DeserializeObject<Dictionary<string, Car>>(json);

ただし、DataContractJsonSerializer では、自分が試した限りですが、ダメでした。何かやり方はあるのかもしれませんが。

Tags: ,

JavaScript

JavaScript で全角数字を半角に変換

by WebSurfer 24. September 2017 14:12

先の記事「全角数字を半角に変換」は C# のコードで書きましたが、それと同等なコードを JavaScript で実装してみました。

JavaScript で全角数字を半角に変換

JavaScript の String.prototype.replace(), String.fromCharCode(), String.prototype.charCodeAt() の使い方の備忘録ですので、記事としては面白くないと思います。スミマセン(汗)

上記のメソッドを利用して、テキストボックスに全角数字交じりの文字をタイプしてフォーカスを外すと全角数字 ⇒ 半角数字に変換するサンプルを以下に書いておきます。上の画像はその実行結果です。

<%@ Page Language="C#" %>

<!DOCTYPE html>

<script runat="server">

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>JavaScript で全角数字 ⇒ 半角数字</title>
  <script src="/Scripts/jquery-1.10.2.js"></script>
  <script type="text/javascript">
  //<![CDATA[
    var zen2han = function(str) {
      str = str.replace(/[0-9]/g, function (s) {
        return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);
      })
      return str;
    }

    $(function () {
      $("#text1").on("change", function () {
        var str = $(this).val();
        $(this).val(zen2han(str));
      });
    });
  //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server">
    <h2>JavaScript で全角数字 ⇒ 半角数字</h2>
    <p>入力してフォーカスを外すと全角数字 ⇒ 半角数字に変換</p>
    <input id="text1" type="text" style="width: 350px;" />
  </form>
</body>
</html>

テキストボックスの change イベントでの書き換えのコーディングが面倒なので、その部分は jQuery を使ってますが、変換するためのメソッドは上記の JavaScript のメソッドを使っています。

replace メソッドは、第一引数の正規表現 /[0-9]/g にマッチした文字列の一部または全てを、第二引数に指定される文字列で置き換えた新しい文字列を返します。

第二引数には新しい部分文字列を生成するために実行される関数を指定することができます。上の例では匿名関数 function (s) { ... } を使っています。

全角数字の UTF-16 コードから 0xFEE0 を引くと半角数字の UTF-16 コードになるので、匿名関数 function (s) { ... } は JavaScript の charCodeAt メソッドと fromCharCode メソッドを利用して全角数字を半角数字の文字列に変換します。

具体的には (1) charCodeAt メソッドでインデックス 0 に位置する文字の UTF-16 コードを表す整数を取得、(2) それから 0xFEE0 を引いて半角数字の UTF-16 コードを取得、(3) fromCharCode メソッドで指定された UTF-16 値の文字列を取得して戻り値として返しています。

Tags: , ,

JavaScript

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  November 2019  >>
MoTuWeThFrSaSu
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678

View posts in large calendar