💥

【cesium】Width must be less than or equal to the maximum ...の解決策

2023/10/24に公開

初めに

cesiumで、画像Point of Interestを実装した際に、表題のDeveloperErrorエラーに遭遇し、
解決までにかなり時間を使ったので、その備忘録です📕
Error loading image for billboard: DeveloperError: Width must be less than or equal to the maximum texture size (16384). Check maximumTextureSize.

ビルボード用画像の読み込みエラー DeveloperError: Widthは最大テクスチャサイズ(16384)以下でなければなりません。maximumTextureSizeをチェックしてください。

画像をクライアント側でリサイズして解決

結論から言うと、下記のような画像リサイズ処理を追加し解消できました。
const maxWidth = 1000;で、最大幅をエラー文の16384などではなく、1000にしています。

APIからfetchしていた画像のサイズを全て調べても、エラー文にある通りのテクスチャサイズ16384pxのような画像は使用しておらず、そこが時間を溶かした理由でした。

この値はグラフィックハードウェアとグラフィックスAPI(たとえばWebGL)の制限によるものであり、実際の最大テクスチャサイズはハードウェアとブラウザの実装によって異なる可能性があるようです..。

今回の要件では1000px以上のサイズは過剰な大きさなので1000と設定しました。

// 画像をリサイズする関数
const resizeImage = async (imageUrl: string): Promise<string> => {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.crossOrigin = 'anonymous';
        img.onload = function () {
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            canvas.width = img.width;
            canvas.height = img.height;
            ctx?.drawImage(img, 0, 0);

            // 📌最大幅を超えるものは、幅を調整しcanvasに描画
            const maxWidth = 1000;
            if (img.width > maxWidth) {
                const scaleFactor = maxWidth / img.width;
                canvas.width = maxWidth;
                canvas.height = img.height * scaleFactor;
                ctx?.drawImage(img, 0, 0, canvas.width, canvas.height);
            }

            resolve(canvas.toDataURL());
        };
        img.onerror = function () {
            reject(new Error('Failed to load image'));
        };
        img.src = imageUrl;
    });
};

リサイズ処理にかけた画像をエンティティに追加

 viewer?.entities.add({
            position: ...,
            billboard: new BillboardGraphics({
                image: new ConstantProperty(await resizeImage(imageUrl)),
                ...
            })
        });

行ったこと

エラー内容から、最大幅16384を超過する画像があるのだと思って、すぐにリサイズ関数作成し、
const maxWidth = 16300;などにしていましたが、
if (img.width > maxWidth)でtrueブロックが実行されないことから最大幅を超過しているから
本エラーに遭遇しているわけではないと仮定してしまったことが沼の始まりでした..。

最大テクスチャサイズをconsoleしてみる

下記のようなコードで算出でき、出力結果は16384でした。
なので最大テクスチャサイズもこの辺りの数値なのだと認識しました。

const gl = document.createElement('canvas').getContext('webgl');
const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
console.log(maxTextureSize);

ちなみにWebGL Reportにアクセスしても、わかりました。
Max Texture Sizeという項目に書いてあります。

全ての画像のサイズをconsoleしてみる

下記のような関数を作成し、全ての画像のサイズをconsoleしました。
結果は大きくても4000などで、やはり最大テクスチャサイズを超えうるサイズはなさそうでした。
実際にconsole.error('最大サイズを超過:', imageUrl);を実行されていません。
しかし、表題のエラーは表示されたままで、他のエラーが発生しませんでした。

// 画像のサイズを確認する関数
async function checkImageSize(imageUrl: string): Promise<void> {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.crossOrigin = 'anonymous';
        img.onload = function () {
         console.log(`${img.width} × ${img.height}`);
            if (img.width > 16300 || img.height > 16300) {
                console.error('最大サイズを超過:', imageUrl);
            }
            resolve();
        };
        img.onerror = function () {
            console.error('エラー:', imageUrl);
            resolve();
        };
        img.src = imageUrl;
    });

画像キャッシュやユーザーの画面内にあるものだけ描画するなどメモリリーク改善

最大テクスチャを超過しているわけではないとなると、メモリ関連なのかなと想定して、
キャッシュ利用や、ユーザー画面内のものだけ描画、ビルボードに追加するfor文に負荷のありそうな算出処理などを、最適化したりしましたが、どれも解決するには至りませんでした。

まとめ

エラー文のWidthは最大テクスチャサイズ(16384)以下でなければなりません。に惑わされて時間を溶かしましたが、要は大きすぎる画像をリサイズすればよかっただけなのかなと認識しております。
今回はフロント側でリサイズ処理していますが、可能なら、元の画像をそもそも適切なサイズにするなどで解消できそうです!

Discussion