DistroWatch

昨日夜勤から帰ると Fedora 23 がリリースされていた。
iso が落とせない…
四日午前 10 時起床、落とせない…
五日午前 0:30 帰宅、まだ落とせない…

筆者だけ?と思い検索するともう落とした人が結構いるみたいだ。
fedup というものなら即手に入るってことかな?
筆者はクリーンインストールしたいんですけど。

https://getfedora.org/ からは iij に転送されるようだ。
他国から落とすしか無さそうだ。

DistroWatch.com: Put the fun back into computing. Use Linux, BSD.
を覗くとリンクがあった。
http://dl.fedoraproject.org/pub/fedora/linux/releases/23/Workstation/x86_64/iso/Fedora-Live-Workstation-x86_64-23-10.iso
からなら落とせるようだ、あぁやっと手に入る。

fedora_dl

同じように ISO を落とせない人は御参考までに。
変更点レポートは明日。

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 アプリでやりたいんですけどね。

Smart Phone Auto Link

iOS 9.0.1 にアップデートが早速きた。
window.innerWidth 初期値の不具合は直っていなかった。
うーん、やっぱり対策しなきゃダメか。

面倒だから当面は全部モバイル用 CSS にする。
いや、上手い回避法が思いつかないだけです。

色々調べているうちに面白いことを見つける。

自動リンクの確認

住所をタップで Map が開くのって便利そうだな。
iPhone でもやってほしいな日式住所対応で。

てか Google ってココまでやるのかと。
スマホのアプリ作りは個人じゃ話にならんレベルになっているや。

iOS9 Safari Bug

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

ios9_1

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

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

ios9_2

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

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

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