JavaScript」タグアーカイブ

jjs and gjs

jjs という JavaScript 実行コマンドがあることを今更知った。
JVM 上で動く、Jython みたいなものという解釈でいいのかな。
Open 含む JDK 1.8 の付属品で nashorn が実行エンジン。

openjdk_jjs

Fedora 23 にはデフォルトで入っている。
更に /etc/alternatives/jjs へのリンクが最初から /usr/bin にある。

Oracle Blogs 日本語のまとめ: [Java, JavaScript] Oracle Nashorn: A Next-Generation JavaScript Engine for the JVM

なんか起動が遅い、Java だから当然なのだが。
これでも IronPython より全然マシだ、てか DLR は完全に失敗作。

Gjs 同様に引数無しならインタラクティブシェルとして使える。
Ctrl+D で同様に終了できるけど改行してくれないな、別にイイけど。
引数に *.js を与えれば実行エンジンになる、まんま Gjs だな。

Gjs との違いは何だろう、遅くてもメリットがあるならヨシだし。

Nashorn(Java8のJavaScriptエンジン)でシェルスクリプト – よしなしごと

ってナンジャコリャ!ってくらい拡張されている。
ただこのシェル関連はインタラクティブシェルでは使えないようだ。

gjs, osascript で試すと全部できなかった、あたりまえか。
ヒアドキュメントだけでも他の実装に反映してほしいな。

Java Platform, Standard Edition Nashornユーザーズ・ガイド、リリース8

なんと JavaFX まで使えてしまうようです。
ただ OpenJDK には含まれていないので正規の Java8 が必要。

Changes/Java8 – FedoraProject

let は使えない。
実行引数の取得方法が実装毎で違うのはしかたがないが。
arguments オブジェクトが実行引数にも使えるのってコレ混乱しないかな?
引数指定に “–” が必要なのはどうなんだろう。
コレを忘れると引数を全部実行しようとするようだ。

#!/usr/bin/jjs

// gjs @ ARGV
// jjs @ $ARG or arguments (./hoge.js -- arg1 arg2)
// osascript @ function run(argv){}

var argv = $ARG;
//let argv = ARGV

if (argv.length == 0) {
    print("No argv");
} else {
    argv.forEach(function(element) {
        print(element);
    });
}

jjs, gjs とも console.log() は使えず print() で標準出力。
function run は AppleScript の機能なので当然 osascript のみ。
他色々あるけど、そもそもインポートできるものがバラバラ。
シバンを書いておかないとワケワカになるかも。

色々あるけど Java の資産があるなら便利だよね。
それよりバイトコードにビルドせず jre が使えることのほうが大きいかも。

ってドンダケ筆者はコンパイルが嫌いなの!と思われそう。
しかしなんか本格的に JavaScript の時代になってきているNE!

JavaScript clientWidth

あけましておめでとうございます。

さて元旦からいきなりですが。
iOS9 Safari Bug | PaePoi
やっとコレの回避策が解りました。

【Javascript】ブラウザの表示領域サイズ取得について – すたら日記

うおぉ、document.documentElement.clientWidth を使うのか!
古いページだけどモバイル Safari でもこのメソッドは使えるようです。
window.innerWidth のバグじゃなかったのね。

ついでに、window.outerWidth は Safari で使えないようだ。
それはもうどうでもよくて。

早速パソコン表示は 728px バナーに戻すとしよう。
よしよし元通り、パソコン用でモバイルバナーはやはり変だったし。

チェック用途の実験ページも書き換えて。
ソースはパソコンで Ctrl+U ね(手抜き)
画面サイズ取得

safari_width

あれ?キチンと window.innerWidth で取得できている。
試しに document.documentElement.clientWidth をコメントアウト。
やはり駄目、取得後ならイケるみたいだが理由はワカンネエ!
レスポンシブ・ウェブデザインってムズい!

以下おまけ。

aqua

無料ガチャでアクアきたー!
って水属性ばかり強くなってどないせーと、風と光をくれよ…
特攻は HR 2 人、まあいいかお年玉をもらった。

Similar image @ Canvas

前回 Gjs でやった近似画像検索を Web ブラウザで。

Web で画像のピクセルデータ取得は意外に簡単だった。
…けれど困った結果になってしもーたがね。

とりあえず
16 3月 2014 | while(isプログラマ)
なるほど、Image と Canvas だけでできるのか。
つまり外部ライブラリは必要無いと。

[3][HTML][canvas]canvasで画像解析 – wataメモ
ピクセル毎のデータはこんなにアッサリ得られるようだ。

ただ Web での画像読み込みはローカルファイルが使えない。
下記コードを試すには Apache 等を使う必要あり。

更に有名な話で、ブラウザの画像読み込みは非同期である。
処理は onload ハンドラから行うのを忘れずに。

現行ブラウザはまだ let が使えないので var に書き換えて。
それと webkit では sort 関数の戻り値は整数にする必要があった。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Similar Image Sample</title>
<!-- for Smart Phone -->
<meta name="viewport" content="
    width=device-width,
    initial-scale=1.0,
    minimum-scale=1.0,
    maximum-scale=1.0,
    user-scalable=no" />
<style>
body {
    -webkit-text-size-adjust: 100%;
}
</style>
<script type="text/javascript"><!--

    const XY = 48;

    function init() {
        // 画像のロードは非同期なので onload 内で処理
        // ローカルファイルは読み込めないので Apache 等で
        var sample = new Image();
        sample.onload = function() {
            // ロードしたので Lab 値を得る
            var sample_lab = getImageLab(sample);
            // 比較対象
            var datas = ["nae01.jpg","nae02.jpg","nae03.jpg","nae04.jpg","nae05.jpg"];
            var diff = [];
            datas.forEach(function(element, index) {
                var data = new Image();
                data.onload = function() {
                    var lab = getImageLab(this);
                    // 比較
                    var distance = 0;
                    sample_lab.forEach(function(element, index) {
                        distance += getLabDistance(element, lab[index]);
                    });
                    diff.push({n:distance, name:datas[index]});
                    // 5枚ともロードが完了したら出力
                    if (diff.length == 5) {
                        output(diff);
                    }
                }
                data.src = element;
            });
        }
        sample.src = "nae.jpg";
    }

    function output(diff) {
        // sort
        diff.sort(function(a, b) {
            //return a.n > b.n; // no webkit
            return a.n - b.n;
        });
        // 確認
        var out = "";
        diff.forEach(function(element, index) {
            out += element.name + " " + element.n + "<br />";
        });
        document.getElementById("ID_TEXT").innerHTML += out;
    }

    // 画像をリサイズしピクセルごとのLab色空間上の座標を取得する
    function getImageLab(image){
        //
        var canvas = document.createElement("canvas");
        canvas.width = canvas.height = XY;
        var ctx = canvas.getContext("2d");
        ctx.drawImage(image, 0, 0, XY, XY);
        // 取り出し、コレは同期する
        var pixeldata = ctx.getImageData(0, 0, XY, XY);
        var lab = [];
        for(var i=0; i<XY; i++){
            for(var j=0; j<XY; j++){
                var r = pixeldata.data[j*4 + i*XY*4];
                var g = pixeldata.data[j*4 + i*XY*4 + 1];
                var b = pixeldata.data[j*4 + i*XY*4 + 2];
                lab.push(rgb2lab([r, g, b]));
            }
        }
        document.getElementById("ID_CANVAS").appendChild(canvas);
        return lab;
    }

    // xyz色空間上の座標をlab色空間上の座標に変換する
    function xyz2lab(xyz) {
        var threshold = 0.008856;

        var ref_x = 0.96422;
        var ref_y = 1.0000;
        var ref_z = 0.82521;

        var var_x = xyz[0] / (ref_x * 100);
        var var_y = xyz[1] / (ref_y * 100);      
        var var_z = xyz[2] / (ref_z * 100);
          
        var_x = (var_x > threshold) ? var_x = Math.pow(var_x, 1/3 ) : (7.787 * var_x) + (16 / 116);
        var_y = (var_y > threshold) ? var_y = Math.pow(var_y, 1/3 ) : (7.787 * var_y) + (16 / 116);
        var_z = (var_z > threshold) ? var_z = Math.pow(var_z, 1/3 ) : (7.787 * var_z) + (16 / 116);

        var l = ( 116 * var_y ) - 16;
        var a = 500 * ( var_x - var_y );
        var b = 200 * ( var_y - var_z );
        return [l, a, b];
    }
     
    // rgb値をxyz色空間上の座標に変換する
    function rgb2xyz(rgb) {
        var r = rgb[0] / 255;
        var g = rgb[1] / 255;
        var b = rgb[2] / 255;
     
        r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
        g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
        b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
     
        r = r * 100;
        g = g * 100;
        b = b * 100;
          
        var xyz = [];
          
        //sRGB D50
        xyz.push(r * 0.4360747 + g * 0.3850649 + b * 0.1430804);
        xyz.push(r * 0.2225045 + g * 0.7168786 + b * 0.0606169);
        xyz.push(r * 0.0139322 + g * 0.0971045 + b * 0.7141733);
        return xyz;
    }
     
    // rgb値をlab色空間上の座標に変換する
    function rgb2lab(rgb) {
        var xyz = rgb2xyz(rgb);
        var lab = xyz2lab(xyz);
        return lab;
    }
     
    // 2つの座標を比較し距離を返す
    function getLabDistance(p1, p2){
        return Math.sqrt( Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[2] - p1[2], 2) );
    }
    //-->
</script>

</head>
<body onLoad="init()">
<div id="ID_TEXT" style="font-size:12pt"></div>
<div id="ID_CANVAS"></div>
</body>
</html>

blink_draw

webkit_draw

Gjs の時と全然順番が違うんですけど。
blink と webkit も数値が全然違う。
いや iPhone 版 Chrome は Safari と同じ数値になってしまう。
それより縮小率によって順番がコロコロ変わるのは頂けない。
色々とどういうことだってばよ。

Gjs は縮小率を変えても順番はほぼ変わらないようです。
圧縮手段が違うのがそんなに影響するのかな。

GdkPixbuf で上記と同じ 24x24px の画像を作って比較してみる。

#!/usr/bin/env python3

from gi.repository import Gtk, Gdk, GdkPixbuf
  
class DrawWin(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.pixs = []
        for s in ["nae.jpg", "nae01.jpg","nae02.jpg","nae03.jpg","nae04.jpg","nae05.jpg"]:
            p = GdkPixbuf.Pixbuf.new_from_file(s)
            pixbuf = GdkPixbuf.Pixbuf.scale_simple(p, 24, 24, GdkPixbuf.InterpType.BILINEAR)
            self.pixs.append(pixbuf)
        # self
        self.resize(24*6, 24)
        self.connect("delete-event", Gtk.main_quit)
        self.show_all()

    def do_draw(self, cr):
        offset = 0
        for pixbuf in self.pixs:
            Gdk.cairo_set_source_pixbuf(cr, pixbuf, offset, 0)
            offset += 24
            cr.paint()
 
DrawWin()
Gtk.main()

って、小さすぎてわかんないや。
両方のスクリーンショットを撮って eog で拡大比較。

pixbuf_and_blink

なんだこれ、blink 雑すぎ!
これじゃ縮小率でコロコロ順番が変わって当然。
webkit も多分同じなんだろうな。

ということで、この方法は使えない。
同一画像がゼロにはなるので同一画像検索には使える。
って、それでいいならもっと簡単な手段があるってば。

縮小しなければなんとかなりそうだけど色々と効率が悪いかも。
外部ライブラリに頼るか別の手段にするか、うーん。。。

Similar image @ Gjs

あるアプリを作るのに近似画像を探す処理が必要になった。
Lab色空間 – Wikipedia
を得て元画像の全体ピクセル近似値を調べればいいと解った。

[PHP]似た画像を検索して近い順番に並べる(類似画像検索) | PHP Archive
手段を見つけた、PHP だけど計算方法はどの言語でも同じだろう。
ただ GD を利用しているので他言語では別の画像解析手段が必要。

ということで、同様な処理を Gjs(Linux) でやってみる。
後で osascript(Mac) 版や Web 版を作りたくなった場合に流用できるので。

Gjs だから画像は当然 GdkPixbuf を GD の代わりに使う。
とはいえ GD と同じ関数があるはずもなく違う手段で同様な処理を作る。

画像処理のサンプル一覧
imagecolorat は gdk_pixbuf_get_pixels からオフセット。
imagecolorsforindex は rowstride 値を使って取得。
実は随分前からコレで詰まっていたおかげでブログの更新が…

正直上手く変換できた自信は無いけどなんとかなったっぽいコード。

#!/usr/bin/gjs

const GdkPixbuf = imports.gi.GdkPixbuf;

// 比較元ファイル
let sample = GdkPixbuf.Pixbuf.new_from_file("nae.jpg");
let sample_lab = getImageLab(sample);

// 比較対象ファイル名の配列
let datas = ["nae01.jpg","nae02.jpg","nae03.jpg","nae04.jpg","nae05.jpg"];

let diff = [];
datas.forEach(function(element, index) {
    let data = GdkPixbuf.Pixbuf.new_from_file(element);
    let lab = getImageLab(data);
    // 比較
    let distance = 0;
    sample_lab.forEach(function(element, index) {
        distance += getLabDistance(element, lab[index]);
    });
    diff.push({n:distance, name:datas[index]});
});

// sort
diff.sort(function(a, b) {
    return a.n > b.n;
});

// 確認
diff.forEach(function(element, index) {
    print(element.name, element.n);
});

// 画像をリサイズしピクセルごとのLab色空間上の座標を取得する
function getImageLab(image){
    let thumb = image.scale_simple(4, 4, GdkPixbuf.InterpType.BILINEAR)
    let pixeldata = thumb.get_pixels();
    let rowstride = thumb.get_rowstride();
    let lab = [];
    for(let x=0; x<4; x++){
        for(let y=0; y<4; y++){
            let i = y * rowstride + x * 3;
            lab.push(rgb2lab([pixeldata[i], pixeldata[i+1], pixeldata[i+2]]));
        }
    }
    return lab;
}

// xyz色空間上の座標をlab色空間上の座標に変換する
function xyz2lab(xyz) {
    let threshold = 0.008856;
      
    //Chromatic Adaptation Matrices
    // D50
    let ref_x = 0.96422;
    let ref_y = 1.0000;
    let ref_z = 0.82521;
 
    let var_x = xyz[0] / (ref_x * 100);
    let var_y = xyz[1] / (ref_y * 100);      
    let var_z = xyz[2] / (ref_z * 100);
      
    var_x = (var_x > threshold) ? var_x = Math.pow(var_x, 1/3 ) : (7.787 * var_x) + (16 / 116);
    var_y = (var_y > threshold) ? var_y = Math.pow(var_y, 1/3 ) : (7.787 * var_y) + (16 / 116);
    var_z = (var_z > threshold) ? var_z = Math.pow(var_z, 1/3 ) : (7.787 * var_z) + (16 / 116);
      
 
    let l = ( 116 * var_y ) - 16;
    let a = 500 * ( var_x - var_y );
    let b = 200 * ( var_y - var_z );
    return [l, a, b];
}
 
// rgb値をxyz色空間上の座標に変換する
function rgb2xyz(rgb) {
    let r = rgb[0] / 255;
    let g = rgb[1] / 255;
    let b = rgb[2] / 255;
 
    r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
    g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
    b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
 
    r = r * 100;
    g = g * 100;
    b = b * 100;
      
    let xyz = new Array();
      
    //sRGB D50
    xyz.push(r * 0.4360747 + g * 0.3850649 + b * 0.1430804);
    xyz.push(r * 0.2225045 + g * 0.7168786 + b * 0.0606169);
    xyz.push(r * 0.0139322 + g * 0.0971045 + b * 0.7141733);
    return xyz;
}
 
// rgb値をlab色空間上の座標に変換する
function rgb2lab(rgb) {
    let xyz = rgb2xyz(rgb);
    let lab = xyz2lab(xyz);
    return lab;
}
 
// 2つの座標を比較し距離を返す
function getLabDistance(p1, p2){
    let dist = Math.sqrt( Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[2] - p1[2], 2) );
    return dist;
}

similar_pic

苗ちゃんがカワイイのは当然でどうでもよくて。
同一画像なら完全にゼロ、後は色が似た順に取得できた。
GdkPixbuf なので C や Python でも同じことができるはず。

後は osascript はともかく Web で画像解析はどうするかだ。
というか Web アプリでやりたいんですけどね。

iOS9 Safari Bug

iPhone を iOS9 にアップデート。
まずは Safari で我がサイトの表示確認だな。

ios9_1

ナンジャコリャ!
window.innerWidth が仕事をしていないようだ。

そうだ、ソレを確認できるページを自分で作っていたじゃないか。
画面サイズ取得

ios9_2

上段が縦持ちで表示した時、下段は一度横向きにして戻した時。
一度横向きにすれば iOS8 までと同じ値になるようだ。
window.innerWidth | PaePoi

完全にバグですね。
しかしこれじゃ一時凌ぎの手段も無いぞ。

WordPress のほうは Google 公式プラグインなので問題無し。
本サイトのほうは別の手段でも用意するかな、とほほ。
久々の更新がそれだけってのもアレなので何か…