巨大なpng画像をjpegに変換しようとするとエラーになる

でかいpng画像をjpegに変換しようとするとエラーになる話。

変換しようとした画像は、101000 x 2400の画像。
$ identify sample.png

sample.png PNG 101000×2400 101000×2400+0+0 8-bit DirectClass 495MB 0.000u 0:00.000

convertコマンドを使って変換しようとすると、以下のようなエラーが出る。
$ convert sample.png sample.jpg

convert: Maximum supported image dimension is 65500 pixels sample.jpg @ error/jpeg.c/JPEGErrorHandler/297.

PHPのgd-jpegで変換しようとしても同じ。
$ php ./conv.php

PHP Warning: imagejpeg(): gd-jpeg: JPEG library reports unrecoverable error: in /home/ayskw/conv.php on line 7
Maximum supported image dimension is 65500 pixels

調べてみると、libjpeg-turboというライブラリの中で最大値が定義されているらしい。
Why does ImageMagick’s montage limit the JPG output to 65500 instead of 65535?

他の環境でも同じような制限がある。

Windows 上では JPEG ライブラリが 65500 pixels を超えるものは扱えないということか。考えてみたら jpg 使わずに gif とか png 使えばいいのねと試してみると、gif や png ならちゃんと描画できた

Python から Graphviz を使う – networkx & PyGraphviz、pydot など

これがJpegの仕様なのかどうかは不明だが、色々制限があるのでこのままだとダメっぽい。
ので、後処理をjpeg前提からpngでも実施できるように変更して問題を回避した。

igo-PHPを使って形態素解析をやってみる

形態素解析と言うとMecabやらChasenやらKuromojiやらを使ってやる場合が多いんだけど、いざPHPでやろうとするとそれぞれにバインディングを準備したりして意外と面倒臭い。

ぼやぼや探していると、igo-PHPというお手軽そうなものがあるので、以下のサイトを参考に試してみる。

続きを読む →

zoomify-image-phpを修正した

Zoomifyという画像表示ソフトウェア向けに画像を加工するプログラムを探していた。加工のプログラム自体はPythonやRuby、Windowsなど色々あったのだけど、今後の扱いを含めるとPHP版で準備したかった。

探してみた

で、色々と探してみるとZoomifyImage ported to PHPという所で公開されているバージョンが一番良いのではないかと試してみたのだけど、いくつか微妙な感じは否めない。

GPL2なので手を入れて公開した

気になったのは、実行時に色々エラーが出てたり、サーバで動かす用のサンプルしかついてなかったり、エラー出力がHTMLになってたり、云々。一回だけ使う分には良かったのかもだけど、そうではなかったので色々と手を入れた。

モノ自体はGPL2のライセンスっぽいので特に問題ないと思うんだけど、元のプログラムがGithubに公開されていないのでどうしたもんかなと思ったけど、まぁやってみた。

手を入れた箇所

元のプログラム自体は特に変えてなくて、色々とMethodを整理したり、CLI用のサンプルを作ったりなので、別にどうということはしていないはず。なので出力が大きく間違ってはないと思う。動いたし。

今後の予定

その代わり、今後積極的になにか手を入れるような予定もない。

あるとすれば今のところ対応できる画像がJpegのみってところ。プログラム中にGDのJpeg向け関数がハードコーディングされているので、なにか1段階噛ませないといけない。

あとは、Packagist対応とかかもしれないけど、そもそもこんなものをPackagistで導入するかな。少し懐疑的かも。

Mysql/MariaDBとPHPでデータを暗号化、復号化する

Mysql/MariaDBの場合

-- AESで暗号化。BLOB型(バイナリ)で入れるならHEX()は不要
-- "E0827B40347D3227B65775B8226A1BF1"が返ってくる
select HEX( AES_ENCRYPT('hogehoge', 'cryptkey') );
-- AESで暗号化されたものを復号化。BLOB型(バイナリ)で入れてあるならUNHEX()は不要
select AES_DECRYPT(UNHEX('E0827B40347D3227B65775B8226A1BF1'), 'cryptkey');

PHPの場合

<?php
// 以下の User Contributed Notes を参照のこと
// see. http://php.net/manual/ja/ref.mcrypt.php#99263
// "E0827B40347D3227B65775B8226A1BF1"が返ってくる
echo bin2hex(mysql_aes_encrypt("hogehoge", "cryptkey")) ;
echo "\n";
echo mysql_aes_decrypt(hex2bin("E0827B40347D3227B65775B8226A1BF1"), "cryptkey") ;
echo "\n";
function mysql_aes_decrypt($val,$ky)
{
$key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
for($a=0;$a<strlen($ky);$a++)
$key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
$mode = MCRYPT_MODE_ECB;
$enc = MCRYPT_RIJNDAEL_128;
$dec = @mcrypt_decrypt($enc, $key, $val, $mode, @mcrypt_create_iv( @mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM ) );
return rtrim($dec,(( ord(substr($dec,strlen($dec)-1,1))>=0 and ord(substr($dec, strlen($dec)-1,1))<=16)? chr(ord( substr($dec,strlen($dec)-1,1))):null));
}
function mysql_aes_encrypt($val,$ky)
{
$key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
for($a=0;$a<strlen($ky);$a++)
$key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
$mode=MCRYPT_MODE_ECB;
$enc=MCRYPT_RIJNDAEL_128;
$val=str_pad($val, (16*(floor(strlen($val) / 16)+(strlen($val) % 16==0?2:1))), chr(16-(strlen($val) % 16)));
return mcrypt_encrypt($enc, $key, $val, $mode, mcrypt_create_iv( mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM));
}

PHP経由でダウンロードしたファイルが壊れてしまう

現象

PHPを経由してダウンロードさせたファイルが壊れてしまう

原因

PHPないからファイルを出力する際に出力バッファに残ったゴミが一緒に出力されてしまい、結果ファイルが壊れてしまう

対策

出力バッファの中に残ったゴミが問題なので、ファイルの中身を出力する前にob_end_clean()を使って出力バッファの中身をクリアすれば良い

未対策の例

<?php
// 前略
header('Content-Type: application/octet-stream');
// ファイルの内容を出力する
readfile($path, FILE_BINARY);
exit;

対策例

<?php
// 前略
header('Content-Type: application/octet-stream');
// ファイルの内容を出力する前に入力バッファの中身をクリアする
ob_end_clean();
readfile($path, FILE_BINARY);
exit;

参考

file_get_contents()と readfile() をベンチで比較してみた

PHPでファイルの中身をそのまま出力する際に、file_get_contents() を使う場合と readfile() を使う場合があって、あんまり考えてなかったのだけど、改めて調べてみた。

単純に比較してもよく分からなかったので、PHPで簡単にベンチとるならUbenchがよさげ で紹介されていたUbench を使ってみる。と言うかこちらが本筋。

後述ようなプログラムを実行してみた結果は以下。100MB程度のファイルを100ほど出力してみたら、実行時間で10倍ほど、ピーク時のメモリで500倍ほど違うらしい。まぁ、実際の例でここまで顕著な差がでるかどうかは別として、気にするべきポイントということがわかった。めでたしめでたし。

$ php bench.php > /dev/null

trial num: 100

function: readfile();
time : 1.501s
memory peak : 256.00Kb
memory usage: 256.00Kb

function: file_get_contents();
time : 18.372s
memory peak : 104.00Mb
memory usage: 256.00Kb

あとUbenchも手軽に使えるのは良い感じだった。まぁ試行回数を設定できたりすると一々for文を書かなくてもよかったりするんだろうけど、プログラムの途中に埋め込んで単純に速度やらを図る分なら便利に使えそう。

ベンチマーク確認用プログラム

<?php
require_once("Ubench.php");
define("BENCH_NUM", 100);
$file = "file.zip"; // ファイルサイズは 104MB
$stderr = fopen( 'php://stderr', 'w' );
$bench = new Ubench;
fwrite( $stderr, "trial num: " . BENCH_NUM . "\n\n");
fwrite( $stderr, "\n" );
fwrite( $stderr, "function: readfile(); \n");
$result = $bench->run("bench_readfile", $file);
fwrite( $stderr, "time        : " . $bench->getTime() . "\n" );
fwrite( $stderr, "memory peak : " . $bench->getMemoryPeak() . "\n" );
fwrite( $stderr, "memory usage: " . $bench->getMemoryUsage() . "\n" );
fwrite( $stderr, "\n" );
fwrite( $stderr, "function: file_get_contents(); \n");
$result = $bench->run("bench_file_get_contents", $file);
fwrite( $stderr, "time        : " . $bench->getTime() . "\n" );
fwrite( $stderr, "memory peak : " . $bench->getMemoryPeak() . "\n" );
fwrite( $stderr, "memory usage: " . $bench->getMemoryUsage() . "\n" );
function bench_file_get_contents($file) {
for($i = 0; $i < BENCH_NUM; $i++) {
print file_get_contents($file);
}
}
function bench_readfile($file) {
for($i = 0; $i < BENCH_NUM; $i++) {
readfile($file);
}
}

n次元の距離と類似度を計算する

n次元の距離と類似度を計算する

類似度と距離 – CatTail Wikiのサイトを見ながら、簡単そうなのをPHPで書いてみた。探せば普通にライブラリがありそうだが、はじめに手を動かすくらいはする。他にも色々載ってるのだけど、簡単そうなものだけピックアップ。

ミンコフスキー距離

ユークリッド距離が,各次元の差の平方和の平方根であったが,それをある種の一般化として,a乗和のb乗根としたのが,ミンコフスキー距離である.なお,a=bとして扱う定義もある.aが大きいほど,次元軸にとらわれない移動(斜め方向のショートカット)を重視する距離である.

a=b=1がマンハッタン距離に,a=b=2がユークリッド距離に,a=b=∞がチェビシェフ距離に一致する.

<?php
$x = array(2, 3, 1);
$y = array(4, 6, 1);
print getMinkowskiDistance($x, $y, 2, 2);
function getMinkowskiDistance($x, $y, $a=1, $b=1) {
$md = 0;
$keys = array_keys($x);
foreach($keys as $key) {
$md += pow(abs( $x[$key] - $y[$key] ), $a);
}
$md = pow($md, 1/$b);
return($md);
}
?>

コサイン類似度

ベクトルx,yのなす角θの余弦cosθをコサイン類似度といい,ベクトルの向きの近さを類似性の指標としたものである.ベクトルの向きが一致している時,最大値の1をとり,直交ならば0,向きが逆ならば最小値の-1をとる.

<?php
$x = array(2, 3, 1);
$y = array(4, 6, 1);
print getCosineSimilarity($x, $y);
function getCosineSimilarity($x, $y) {
$cos = 0;
$i = 0;
$j = 0;
$k = 0;
$keys = array_keys($x);
foreach($keys as $key) {
$i += $x[$key] * $y[$key];
$j += pow($x[$key], 2);
$k += pow($y[$key], 2);
}
$cos = $i / (sqrt($j) * sqrt($k));
return($cos);
}
?>

画像の類似度を判定するためのライブラリ Libpuzzleを試してみる

Libpuzzleは画像の類似度を計算することができるPHPのライブラリ。extensionの形で導入するので、コンパイル等が必要。

以下のサイトを参考に
libpuzzleを使ってみる
phpでlibpuzzleを使ってみる【備忘】
how to install the libpuzzle extension for PHP

インストールする

基本的に、GDで動くのでその環境とlibpuzzleをコンパイルするためのGCCをインストールしておく

予め必要な物を揃えておく

$ yum install gd-devel
$ yum install gcc-c++
$ yum install gd

Libpuzzleをダウンロード

$ cd /usr/local/src
$ wget http://download.pureftpd.org/pub/pure-ftpd/misc/libpuzzle/releases/libpuzzle-0.11.tar.bz2
$ tar -jxvf libpuzzle-0.11.tar.bz2
$ cd libpuzzle-0.11

Libpuzzleをコンパイル

$ ./configure –with-libpuzzle
$ make
$ make install
$ cd php/libpuzzle/
$ phpize
$ ./configure –with-libpuzzle
$ make clean
$ make
$ make install

LibpuzzleをPHPでロードする

$ vi /etc/php.d/libpuzzle.ini

extension=libpuzzle.so

$ /etc/rc.d/init.d/httpd restart

サンプルを動かしてみる

サンプルはLibpuzzle with PHPから拝借

<?php
$file_a = 'images/01.jpg';
$file_b = 'images/01_same.jpg';
// 画像のファイルを指定してシグネチャを生成する
$cvec1 = puzzle_fill_cvec_from_file($file_a);
$cvec2 = puzzle_fill_cvec_from_file($file_b);
// シグネチャを圧縮しておく
$sign_1 = puzzle_compress_cvec($sign_1);
$sign_2 = puzzle_compress_cvec($sign_2);
// シグネチャ同士を比較して画像の類似を判定する
//  返り値は0.0~1.0の範囲で返ってき、少ないほど類似していることを表す
$d = puzzle_vector_normalized_distance(puzzle_uncompress_cvec($sign_1), puzzle_uncompress_cvec($sign_2));
# Are pictures similar?
if ($d < PUZZLE_CVEC_SIMILARITY_LOWER_THRESHOLD) {
echo "Pictures are looking similar\n";
} else {
echo "Pictures are different, distance=$d\n";
}

使ってみた感想

はっきり言えば人間的な「似ている」という判定とは程遠いような気がする。画像の下処理なんかをほとんどしていないので、もう少し工夫の余地があるのかもしれないが、少なくともデフォルトでは「似ている」感はない。

これは2つの意味で、似ていると思う画像が上がってこない場合と、似ていると言われても似ているように見えない場合。それぞれあるのでなんともいえない。ただ、目を薄っすらと開けて気持ちを穏やかに見てみたら、そこはかとなく似ているような気がすることもあるような気もする。

マニュアルを見てみると、デフォルトでは画像を9×9の賽の目に分割しているらしい。ならこれを増減させればと思ってpuzzle_set_lambdas()で賽の目数を変更しようとすると何故かpuzzle_compress_cvec()が動かなくなる。ローカルで動かしてみるとなんかエラーで落ちてるっぽい。

$ time php update2.php

BUG File: [compress.c] Line: [59]
アボートしました

少し試してみた結果、puzzle_set_lambdas()の値を少なくすれば、なんとなく似ている判定の画像が増えるような気がする。ただ同時にこりゃぁ似てねぇだろみたいなものも増えるのでなんとも言えない気持ちになってくる。いじるだけ損する。

あとは事前に画像の加工なんかすれば精度は挙がるのかもしれないけど、どう加工したら良いのかあんまりアイディアはない。なんとなく画像を400×300くらいまで極端に小さくしたら精度が上がるんじゃないかとも思うけど、実際どうなんだろうか。謎

補足事項

LIBPUZZLE – PHP EXTENSIONを参考に

The PHP extension provides bindings for the following tuning functions:
– puzzle_set_max_width()
– puzzle_set_max_height()
– puzzle_set_lambdas()
– puzzle_set_noise_cutoff()
– puzzle_set_p_ratio()
– puzzle_set_contrast_barrier_for_cropping()
– puzzle_set_max_cropping_ratio()
– puzzle_set_autocrop()

Have a look at the puzzle_set man page for more info about those.

Getting the signature of a picture is as simple as:

$signature = puzzle_fill_cvec_from_file($filename);

In order to compute the similarity between two pictures using their
signatures, use:

$d = puzzle_vector_normalized_distance($signature1, $signature2);

The result is between 0.0 and 1.0, with 0.6 being a good threshold to detect
visually similar pictures.

The PUZZLE_CVEC_SIMILARITY_THRESHOLD, PUZZLE_CVEC_SIMILARITY_HIGH_THRESHOLD,
PUZZLE_CVEC_SIMILARITY_LOW_THRESHOLD and PUZZLE_CVEC_SIMILARITY_LOWER_THRESHOLD
constants can also be used to get common thresholds :

if ($d < PUZZLE_CVEC_SIMILARITY_THRESHOLD) {
echo “Pictures look similar\n”;
}

Before storing a signature into a database, you can compress it in order to
save some storage space:

$compressed_signature = puzzle_compress_cvec($signature);

Before use, those compressed signatures must be uncompressed with:

$signature = puzzle_uncompress_cvec($compressed_signature);

Atomエディタでatom-beautifyを使ってPHPのソースコードを整形する(追記あり)

使っているPCが凄まじく貧弱なのが悪いのだけど、今まで使ってきたEclipseの遅さに耐え切れなくなってきた。しかたがないので幾つかのIDEを触ってみたのだけど、ぶっちゃけAtomエディタでいいんじゃないかという結論に陥ってしばらく前から使ってみている。

いくつかのサイトを見ながらPackageや設定をごちゃごちゃしているのだけど、その中でもPHPのソースコードの自動フォーマットの話。はじめのうちちゃんと動かなかったのが動くようになったので一先ず覚書として書き残しておく。

はじめに準備すること

なにはなくともAtomエディタをインストールする必要があるのでやっておく。日本語化など必要な設定があれば良しなに。PHPも動作するように忘れずに。Windows環境の場合、インストーラーがあるのでそちらからインストールしておく。詳しくはマニュアルを参照のこと。

ちなみにPHPは環境変数でPathを通しておくこと。Windowsの場合、コマンドプロンプトから以下のコマンドで動けば良い。

$ php -v

PHP-CS-Fixer をダウンロードする

後ほどインストールする atom-beautify は PHP-CS-Fixerというライブラリを使ってPHPのソースコード整形を行なう。そのために予めダウンロードしておく。

GitHubのページではいくつかのインストール方法が書いてあるが、面倒臭ければ直接php-cs-fixer.pharをダウンロードしてしまう。ダウンロードしたファイルはどこか適当な場所に展開しておく。自分は面倒だったのでDropboxの配下に放り込んだ。確認までに以下のコマンドを実行して結果が返ってくることを確認しておく。

$ php /path/to/php-cs-fixer.phar

atom-beautifyをインストールする

Atomエディタの設定から install を選択、atom-beautify をインストールする。ここら辺は特に何事も無く。

php-cs-fixerの設定をおこなう

atom-beautify をインストールした後、Atomエディタの設定画面からpackagesを選択、atom-beautify の Settings を選択する。えらい長い設定画面が開くので、以下の項目を設定していく。

設定項目が上下に分かれているので要確認。

設定名 説明 設定値
Language Config – PHP – Beautify On Save 保存時に自動で整形するか? お好きに
Language Config – PHP – Default Beautifier どの整形ツールを使うか? PHP-CS-Fixer
Language Config – PHP – Disable Beautifier Languager 整形を有効にするか? チェックを外す
PHP – PHP-CS-Fixer Path php-cs-fixer.pharのパス /path/to/php-cs-fixer.phar
PHP – Fixers php-cs-fixerのオプション お好きに
PHP – Level どの整形基準を使うか お好きに

実行してみる

これまででatom-beautifyの設定が終わったので実際にPHPのプログラムを整形してみる。まずは適当なPHPファイルを開いて、右クリックから「beautify editor contents」を選択するとコードが整形されるはず。実行の際には一瞬GitCatのシルエットがポップアップされる。

ちなみに設定がおかしいとポップアップは出るのになにも変化がないという不思議な状況になるので注意。その際は、右クリックから「Debug Atom Beautify」から実行することでデバッグ出力がクリップボードに貼り付けられるので、そっから追いかけていく。

デフォルトのショートカットとしては「ctrl-alt-b」が割り振られているので適宜振り直すなり、なれるなりしておく。

php-cs-fixerの設定値

PHPの整形ルールなどは php-cs-fixer の設定に従う。Fixerに設定可能な値などについては、GitHubのReadmeを参照のこと。個人的には「align_double_arrow,short_echo_tag」を指定している。

また、Levelについては以下の様な値が準備されているので、用途にあったものを指定しておく。

  • psr0
  • psr1
  • psr2
  • ymfony

なんか上手く動かない 追記:2016-05-28

上記を設定してatom-beautifyを起動しても上手く動かない。なんとなく動いたようなエフェクトがあるんだけどコードが整形されない。という現象にぶち当たった。よくわからないのでDebug Atom Beautify を使ってログを見てみたところ、以下の様なエラーを吐いている様子。

Fixersを指定した時はこんなの

[Symfony\Component\Console\Exception\RuntimeException]
The “–fixers” option does not exist.

Levelを指定した時はこんなの

[Symfony\Component\Console\Exception\RuntimeException]
The “–level” option does not exist.

要するに設定画面で、FixersやLevelを指定するとphp-cs-fixerがコケてしまうらしい。なんぞ?試しにコマンドラインから実行してみても確かにそういうエラーが出てくる。Readme通りにやってもエラー出るしなぁ。

とりあえずは、FixersとLevelの指定を空にしてやると動くのでそのようにしておく。

なんか上手く動かない 追記:2016-05-28

あと、プログラムにsyntax errorがあるとうまく動かない。動かない時はその辺りも確認のこと。