可逆圧縮のWebP画像から幅と高さを取得する

公開

PowerCMS 6でWebP画像アップロードしたところ、画像にならない(classがimageにならない)ファイルがありましたので調査をしました。「WebP Container の仕様 | Google for Developers」を確認したのですが、WebPファイルには3つのレイアウトがあることを初めて知り、アップロードできないのは可逆圧縮のWebP(VP8L)であることが分かりました。

細かい説明は省くのですが、画像として扱われない原因はPowerCMS 6がWebPの画像幅・高さを取得するために利用しているImage::Sizeモジュールの処理に起因します。 そこで、「WebP ロスレス ビットストリームの仕様 | Google for Developers」を基にチャンクヘッダーがVP8Lである時に幅・高さが正しく取得できるように見直しました。

ドキュメントによると、VP8Lの場合は画像の最初にRIFFコンテナが21バイトあり、その後ろに続くビットストリームの最初の28ビットが幅・高さである、となっています。それを踏まえ、まず22バイト目から28ビットを取得したいのですが、Perlのseekreadはバイトで指定する必要があるため32ビット=4バイト取得します。

my $buf = $READ_IN->($img, 4, 21);

リトルエンディアン形式のバイナリデータを変換します。

my ($bits) = unpack('V', $buf);

バイナリデータの扱いに慣れていなかったのでChatGPTの力を借りたのですが、14ビットずつマスクしたり右シフトしたりすると、ビットフィールド図通りのデータが取れるようです。オリジナルのImage::Sizeでは別の書き方がしてありますが、ビット演算を利用して書く方が意図が直感的に分かりやすいと感じました。

$x = ($bits & 0x3FFF) + 1;
$y = (($bits >> 14) & 0x3FFF) + 1;

ビット演算は基本情報技術者試験で勉強すると思うのですが、「こういう場面で使うのか!」と楽しく感じました。まだ理解不足なところもありますが、これをきっかけにバイナリデータも上手く扱えるようになれたらなと思います。

おまけ:WebPの最大サイズ

ちなみに、VP8・VP8LのWebPでは幅・高さ情報がそれぞれ14ビットのデータなので、最大サイズは16,384 × 16,384ピクセルになることが理解できます。大きなWebP画像が生成できないのはPowerCMS Xの仕様ではなく、このような幅・高さ情報のビット数によるものなのです。

※拡張ファイル形式(VP8X)は24ビットなので大きくできるかと思います。