iOS6でメガピクセル画像をCanvasに描画するとおかしくなってしまう件と、その対処

iOS6によるアップデート

まず最初に、iOS6において、Safari上のWebアプリから簡単にカメラ&フォトライブラリの写真にアクセスできるようになりました。いままでカメラにアクセスするにはPhoneGapなり何なりでネイティブ化する必要があったので、写真共有サービスなどにはかなり有用なアップデートです。

File API、HTML Media Capture への対応

HTML Media Capture に対応し、Safari から カメラを起動してのファイルアップロードが可能になりました。

type 属性値に file を指定すれば単体ファイルのアップロードが簡単に。
(略)
ファイルを選択したあとは何ができるか...

HTML5 開発者向け iPhone 5 / iOS 6 での変更点等まとめ | WWW WATCH

発生する問題

さて、フォトライブラリ内のカメラ画像にアクセス可能になりましたが、iPhone5などの高解像度カメラを備えた端末では、写真として保存されるファイルは数メガピクセルに軽く達してしまいます。そして、iOSの制限として、2Mピクセルを超えるJPEG画像はサブサンプルの対象となり、情報が間引かれてしまいます。

JPEG images can be up to 32 megapixels due to subsampling, which allows JPEG images to decode to a size that has one sixteenth the number of pixels. JPEG images larger than 2 megapixels are subsampled―that is, decoded to a reduced size. JPEG subsampling allows the user to view images from the latest digital cameras.

Know iOS Resource Limits

情報が間引かれて表示されるだけならまだいいですが、この画像をCanvasに対してdrawImageなどしてリサイズする場合、不都合が起きます。サブサンプルされる場合とされない画像で描画の結果が異なってしまいます。

例)iOS6Safariで2Mピクセル以下の写真をdrawImageで描画した時:

例)iOS6Safariで2Mピクセル以上の写真をdrawImageで描画した時

テスト環境:
http://jsbin.com/upivad/4/

回避方法

いくつかのテストの結果、メガピクセル画像をCanvasにdrawImageする際にリサイズを伴う場合、つまりContext#drawImage(img, x, y, width, height) のwidth, height に元画像と異なる値を設定していると発生することがわかりました。そのため、回避策として、一旦小さなCanvasに元画像の一部をコピーし、それを張り合わせて対象のCanvasにdrawImageすることで対応しました。またその際、サブサンプルされている画像とそうでないものを描画結果から判別し、サブサンプルされているものには縮尺を補正してあげるようにしています。

ライブラリ化しましたので、ご参考まで。

ソース:
http://github.com/stomita/ios-imagefile-megapixel

テスト環境:
http://jsbin.com/ovupil/1/

上記ソースでは、画像ファイルにEXIFのOrientation(画像の向き)が90度回転方向に設定されている際、縦方向にひしゃげて表示されるバグについても対処しています(これは画像サイズに関係なく発生する模様)。

なお、こちらはiOSの問題の解決のために作成しましたが、同じソースをそれ以外のブラウザで利用しても特に挙動は変わりません。PCのChrome, Safari, Firefoxなどで正常に動作します。