まだ重たいCMSをお使いですか?
毎秒1000リクエスト を捌く超高速CMS「adiary

2023/08/11(金)Perl DBI と prepare_cached() の罠

Webアプリケーションで、prepare_cached() を使うと色々問題があったのでメモ。

問題あり

prepare_cached() を使用すると、キャッシュヒット時(同じSQL文実行時)に$sthを再生成せずに再利用され、データ取得を含めた実行速度が220usが200usになるぐらいの効果があります。

しかし、prepare_cached() された状態でテーブルのカラムが変更されると問題が起こることが分かりました。

続きを読む

2020/04/22(水)Chrome/AndroidでVideo/Canvasタグのアスペクト比でハマる

Android の Chrome でカメラから画像取得を試みたところ、縦横比がおかしくなってしまいました。この問題の解決を試みたメモ。

縦画面限定で、画像がおかしくなる

この画像はHTML/JavaScriptを使いスマホで正方形の枠を映したものです。

canvas-SP00.jpg

videoタグに 1600x900 のサイズを指定したあとカメラ画像を取得して、同サイズのcanvasタグに画像をコピー(drawImage())しました。縦に圧縮された横長画像になっています。

これは Chrome 固有の問題ではなく、Chromeのスマホエミュレーションでは何も問題なく表示されます。実機のスマホのChrome(or Firefox)でのみ起こります。色々テストしていると以下のことがわかりました。

  • スマホを横画面にすると正常になる。
  • 取得画像を 1:1 に設定すると正常になる。

テストしていたときは Canvas タグのみ描写していたのですが、videoタグと同時に表示するとこんな感じになりました。

canvas-SP01.jpg

videoソースが縦長?

mediaDevices.getUserMedia() の罠

16:9の画像をこんな感じで取得しようとしていました。

navigator.mediaDevices.getUserMedia({
	audio: false,
	video: {
		width:	1600,
		height:	 900,
		facingMode: "environment"
	}
}).then( (stream) => {

mediaDevices.getUserMediaの説明を隅々まで読むと書いてあるのですが、widthとheightの指定は要望であって強制ではありません

ピッタリとマッチする解像度がカメラ(ビデオソース)に存在しない場合、ブラウザはそれに近い画像サイズを設定します。スマホにおいては、そのサイズが 900x1600 なのです……。

ブラウザにおいて縦画面・横画面という区別はありません。ブラウザの media stream は状況(縦横)に応じてサイズが変化することを想定していないわけです。ですので、スマホのブラウザは縦横設定を無視して近い画像サイズを選定します

このvideoタグの表示領域は(白い部分以外)、論理的には 1600x900 の解像度の持ちますので、その部分を 1600x900 に引き伸ばすと横長縦圧縮画像が出来上がるというわけです。

縦画面、横画面両対応の画像を取得する

横画面専用のWebアプリなんて想定したくはないので、縦横兼用で使うにはどうしたら良いでしょうか? 正解はvideoソースを正方形にしてしまうことです。こうすれば、縦横回転しても、表示領域もサイズも変化がありません。

正方形映像の中から、必要な部分をクロップして canvas に転送すればよいわけです。

少し長くなりますが、以下がソースです。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width">
	<title>Reader test</title>
<style>
video, canvas {
	max-width:	min(480px, 100%);
	display:	block;
	border:		1px solid red;
}
video {
	height:		calc(min(100vw,480px) * 9 / 16);
	margin-bottom:	4px;
}
</style>
</head>
<body>
<video  id="video" muted playsinline></video>
<canvas id="canvas"></canvas>

<script>
const videoWidth  = 1600;
const videoHeight =  900;
const offset      = (videoWidth - videoHeight)/2;

const video  = document.getElementById('video');
video.width  = videoWidth;
video.height = videoWidth;
//video.height = videoHeight;

const canvas  = document.getElementById('canvas');
canvas.width  = videoWidth;
canvas.height = videoHeight;
const ctx     = canvas.getContext("2d")

navigator.mediaDevices.getUserMedia({
	audio: false,
	video: {
		width:	videoWidth,
		height:	videoWidth,
		// height: videoHeight,
      		facingMode: "environment"
	}
}).then( (stream) => {
	video.srcObject = stream;
	video.play();
	video.onloadedmetadata = (e) => {
		video.play();
		console.log(e);
	}
	setInterval(function(){
		// ctx.drawImage(video, 0, 0, videoWidth, videoHeight);
		ctx.drawImage(video,
			0, offset, videoWidth, videoHeight,
			0,      0, videoWidth, videoHeight
		);
	}, 100);
}).catch(function(e) {
	alert(e);
});
</script>
</body>
</html>

コメントアウト部が元々のソース(最初の画像を得たソース)との変化部分です。このソースで実際に画像を取得すると、以下のようになります。

canvas-SP02.jpg

無事、正しいアスペクト比の画像を得られました。*1

*1 : Androidアプリでも、写真撮影モードになるとなぜか正方形表示になるアプリが多くて気になっていたのですが、こういうややこしい問題を避けるためなのかもしれませんね……。

問題点

逆に、PCでは正方形の映像ソースを取れることは少ないようです*2

canvas-PC.jpg

上のように正方形を指定したのに横長のVideoソースになってしまいました……。

	video: {
		width:	{min: videoWidth, max: videoWidth},
		height:	{min: videoWidth, max: videoWidth},
   		facingMode: "environment"
	}

として強制的に正方形ビデオソースを要求すると、そんなの無いから!と怒られて(throw()されて)しまいます……。正方形ビデオソースは、スマホ限定の措置にして、PC等では普通に必要なピクセルサイズを指定するほうが問題は少ないようです。

もう面倒くさすぎるので、対応解像度をリストアップするAPIか、実ソースサイズを教えてくれるAPIを追加してくれないかな……。

*2 : 少なくとも自分の環境では

まとめ

  • スマホで縦横両対応にする場合は、正方形ビデオソースを使用し、canvas転送時にクロップすると良い。
  • ただし、PCでは正方形ビデオソースなんて使えない。

この辺 Mac/iPhone はどうなんでしょう?

2019/07/05(金)JavaScriptから .png 画像を加工してCSSに適用する

png画像をJavaScript上で加工して、CSSに適用する方法。

背景

adiaryでは、UIアイコン(このサイトだとページ送りやタグ一覧に出ている▼等)を色指定に基づいて動的にロードしています。

仕組みとしては、予め128種類の色の異なる画像アイコンを用意して、指定色にもっとも近い色をロードするという感じになっていますが、いくつか問題があります。

  • 画像ファイルを増やすには限界があり近似色になってしまう。
  • 1KBにも満たないような小さなファイルをわざわざ別ファイルとしてロードさせるのは嫌。*1

色が固定で良いなら、base64で埋め込むだけの話なのですが……。

.ui-icon {
	background-image:	url('data:image/png;base64,XXXX----XXX=');
}

adiaryはテーマの色をユーザーが自由に変更できるという素敵な機能が付いておりまして、そういうわけにも行きません……。

*1 : Webの表示速度を上げるためにも、ロードファイル数は極力少ないほうが良いわけです。

正攻法

続きを読む

2019/07/02(火)ES2015(ES6)をminifyしたいけど、Node.jsに興味はない場合

ES2015(ES6)のJavaScriptを minify(compress) したかったときに苦戦したメモ。

JavaScript Compressor

古くから有名でお手軽なYUI Compressorをインストールして、圧縮してみたのですが、謎のエラーが出て困ってしまいました。

Exception in thread "main" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at com.yahoo.platform.yui.compressor.Bootstrap.main(Bootstrap.java:21)
Caused by: java.util.MissingResourceException: Can't find bundle for base name org.mozilla.javascript.resources.Messages, locale ja_JP
(以下略)

どうやら調べてみると、YUI CompressorはES6(let/const)に対応していないようです。

2019年にJavaScriptを書くのに「ES2015ではないスクリプトを書く」のは考えられないので、ES2015対応のJavaScript Compressorを探してみました。

Node.js に用はない

色々調べてみてもNode.js関連情報しか出てきません。

「Node.js」に興味はなく、純粋にJavaScriptを圧縮したいだけなのに……。

路頭に迷っていたのですが、Uglify-jsというNode.js関連ツールを使うことで、Node.jsとは関係なく単にJavaScriptを圧縮できることがわかりました。

Wabpackというツールもあるのですが、軽く調べた感じではNode.js専用ツールっぽい感じで、単なるJavaScriptの圧縮ツールとしては使いにくそうでした。

Uglify-es

残念ながらUglify-jsは「Uglifyjs 3 (Ver3)」でもES6には対応していません。超注意点です。

Uglify-jsからフォークされた「Uglify-es」というツールを使います。まずこのツールを入れるために、Node.js用のパッケージマネージャー npm をインストールする必要があります。

今回インストールしたサーバはDebainですが、普通にapt-getすると古いツールが入るため、「Debian9 (stretch) に Node.js をインストールする」を参考にVer10系の Node.js を導入しました。

curl -sL https://deb.nodesource.com/setup_10.x | bash -
apt-get install -y nodejs

これで npm が使用できるようになったので、Uglify-esを導入します。

npm install uglify-es

Makefile

Uglify-js@2、Uglify-js@3、Uglify-esでみんなコマンドラインオプションが違うので、情報を探す際はご注意ください。

参考までに作成したMakefileを晒しておきます。

#
# Makefile for adiary.min.js
#
COMPRESSOR = uglifyjs
OPTIONS    = -c -m --comments '/^!/' --source-map includeSources,filename

OUTPUT	   = ../adiary.min.js
MAP_FILE   = ../adiary.min.map

FILES = \
	01_global-variables.js	\
	10_jquery-ext.js	\
	20_init.js		\
	30_css.js		\
	90_PrefixStorage.js	\
	91_jquery-storage.js

all:  $(OUTPUT)

$(OUTPUT): $(FILES) Makefile
	$(COMPRESSOR) $(OPTIONS) $(FILES) -o $(OUTPUT)
	mv $(OUTPUT).map $(MAP_FILE)
-c
ソースファイルを圧縮します
-m
Mangleします。これをつけたほうが圧縮率が高くなります。
--source-map includeSources,filename
mapファイルを作成します。
--comments '/^!/'
「!」マークで始まるコメント(ライセンスコメント)を残します。

terserになりました

uglify-esは更新が停止し、uglify-esをフォークした terser に乗り換えました。

インストール方法。

# apt-get install npm
# npm install terser -g

使い方は uglifyjs と一緒です。

2019/02/11(月)Linux/Windowsで動く本格的なWebServerをPerlで作る