Page No.1

サイトの表示速度をよくするための画像の最適化というかサイズのダイエットは欠かせないこと。そして、画像を読み込むリクエストをへらすためのデータ化としての BASE64 も、またとても有効な手段。でも、BASE64 化するとそのデータサイズは、もとのサイズの1.3 倍になってしまうので、そういう意味でも元データのサイズを少しでも小さくしておくことは意味のあることだと思う。

そこで思いつくのは、より画像サイズを小さくできるはずの新しい画像フォーマットである webp ってわけだ。でも、せっかく webp でサイズを小さくしたのに、それをまた BASE64 で大きくして、というとなんだかちょっと妙というか疑問という感じもするのではあるのだけれど、それによって元のサイズとほとんど変わらないまま、リクエストは減らせるということになるのだから、これはこれで意味のあることなのではないかと思うわけなのである。

ところで、いざ webp を使うことにしたとして、各ブラウザのサポート状況というのはどうなっているのだろうか、というのが気になるというかとても重要なこと。
この記事を書いているのは、2021年ももうあとわずかという2021年12月のなかば。IE のことを考える必要がほとんどなくなって、主要なブラウザならほぼ大丈夫なのだろうと思いきや、Mac OS はちょっと遅れているよう。その webp が対応となったという Big Sur へのアップデート状況というのが今ひとつよくわからない。 実際に Mac を使用したことがないので状況がよくわからないのだけれど、Big Sur へのアップデートはほとんどのマシンが可能なのだろうか。その前の Catalina というバージョンは 80% ほどということではあるが。

で、じつは webp よりもサイズを小さくできるという規格で AVIF というのがすでに存在するよう。でも、webp においても Mac はやっとという感じなので、もうちょっと時間がかかりそうではある。

まぁ、よくわからないので、とりあえずブラウザで判断することにして、FirefoxChrome 系において webp を表示させることにした。
と、いうことでそれでは本題へと入ることにしませう。

php で jpg or png を webp or AVIF へ変換

webp を使うにはなにはともあれ、持っている画像を webp へとコンバートしないとはじまらない。
コンバートするには、Googleが開発した Squoosh というサイトを使うとか、Google Developers からダウンロードできる cwebp というコマンドツールを使うとかがあるけれど、php にもちゃんとそれ用の imagewebp という関数が備わっている。慣れ親しんでいる php にあるなら、それのほうが色々と応用がきくということで自ずとそれを使うことになる。ちなみに php においては、AVIF においても 8.1 から imageavif という関数を備えている。

phpimagewebp という関数は、引数に画像のファイル名を渡せばいいというわけではなく、GdImage オブジェクトを渡せとのことで、めんどうってほどでもないけれどちょっとひとてまが必要。
ネットを調べ回ってみてためしたところ、jpg は以下のような感じのものでうまくいった。
imagewebp の返り値として得られる $result の値は、成功した場合に true、失敗すると false であり、第二引数にファイル名を渡している下のような場合は、コンバートされた webp 画像ファイルが作成されていて、その引数を省略したり null にした場合は、画像ストリームを直接出力されるとある。そこで直に表示されるということである。

ちなみに逆 ( webp -> jpg )も同じようなもので、それは page2 にて。

<?php
    $filename = 'target_image.jpg';
    $extension = pathinfo( $filename )['extension'];
    $newName = str_replace( $extension, 'webp', $filename );
    $quality = 80;

    $img = imagecreatefromjpeg( $filename );

    $result = imagewebp( $img, $newName, $quality );

    // ちなみに( jpg -> AVIF )も同じで imagewebp を imageavif に変えるだけ
    $result = imageavif( $img, $newName, $quality );
?>
PHP
CopyExpand

png の方も同じようにできるかとおもいきや、そうは簡単にはいかなかった。png の場合はもうひとてま必要なようで、php: imagewebp – Manual を参考にしてやってみたところ、imagecreatefrompng に以下のように追加することでうまくいった。

<?php
    $img = imagecreatefrompng( $val );

    imagepalettetotruecolor( $img );
    imagealphablending( $img, true);
?>
PHP
CopyExpand

jpg or png -> webp or AVIf and BASE64 converter page application

と、いうことで作ったものが下。
このファイルがあるディレクトリ直下の targets フォルダ内、全ての jpgpngwebp もしくは AVIF に変換する php によるブラウザにて動作するページ。
文字列を指定することで、その文字列をファイル名に含むファイルだけに限定できるし、webp or AVIFのクォリティー指定が可能。あと、webp or AVIF にコンバートした後、それを BASE64 化することも可能。

仮にこのコードを使用する場合、このコードを使用しての一切の責任は負いませんから完全自己責任にて使用して下さい。

後日追記:実はこの下のコード、簡単なケアレスミスがあって、2023年3月24日に訂正しています。

と、いうのは 66 – 70 行目と 88 – 92 行目の条件式のところで、生成するファイルを webpavif かで判別させてるわけですが、これ、生成するものが逆でした。
$ope に 1 をビットアンドさせてるので、奇数の場合に true になるわけで、そのときは webp を生成しなくてはいけない。それが逆になっていたので、拡張子は .webp なのにファイルの実態は avif という、へんてこりんなことになっていたわけです。しかし、ブラウザでも windows10 のエクスプローラーでも普通に表示されていたから気が付かなかったと。ちなみにファイル名の拡張子を正式な .avif に変更してやっても、もちろん普通に表示されることになる。

で、これ、なにゆえに気がついたのかというと、その拡張子が .webpavif ファイルを、いざ WordPress にアップロードしようとしたら「権限がない」と拒否されてしまったから。下のプログラムでクオリティを変えたりしていろいろと試してみたけれど、全くだめ。疑問に思いつつ、WordPess のアップロードの工程における関数をじっくり見ていくと、アップロードファイルのチェックにおいて、wp-includes/functions.phpwp_get_image_mime() で、 phpexif_imagetype() 関数を使っている。試しに、その関数を使って例のファイルをチェックしてみたところ、19 を返してきた。webp ならば 18 じゃないとおかしい。この 19 という数字は php.net/exif_imagetype のページを見ても、2023年3月24日時点では記載されていないけれど、php 8.2.4 において image_type_to_mime_type() 関数の戻り地で試してみると image/avif である。と、いうことで、これはっ!と思い、プログラムを見直してみて気がついたというわけです。

しかし、画像ファイルの拡張子なんて、ただ人間が判別できるようになっているだけのもので、たいして重要ではないってことがわかった。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>image file to webp, avif ( & BASE64 ) converter</title>
</head>

<body>
<div id="mainframe">
	<div id="header"></div>
	<div id="maincontents">
		<div id="innerframe">
            <?php
                if ( isset( $_GET['condition'] ) ) {// file を絞り込むための指定された文字列
                    $condition = 'all' === $_GET['condition'] ? '' : $_GET['condition'];
                    $ope = ( int ) $_GET['ope'];
                    $quality = ( int ) $_GET['quality'];
                    $extary = array( 'jpg', 'png' );
                    $ex_ary = array( 'avif', 'webp', );
                    $sel_ex = $ex_ary[ ( $ope & 1 ) ];

                    $target = array();

                    $files = scandir( './targets/' );

                    foreach ( $files as $val ) {

                        $extension = pathinfo( $val )['extension'];

                        if ( in_array( $extension, $extary ) ) {
                            if ( str_contains( $val, $condition ) ) {
                                $target[] = $val;
                                echo '<p>' . $val . '</p>';
                            }
                        }
                    }
                    echo '<p>target file count : ' . count( $target ) . '</p>';
                    echo '<br><div style="position:relative;width:100%;height:5px;border-top:dotted 5px red;border-bottom:dotted 5px blue;"><br>';  

                    foreach ( $target as $key => $val ) {

                        $extension = pathinfo( $val )['extension'];
                        $tarfile = './targets/' . $val;

                        if ( is_file( $tarfile ) ) {
                            $fileinfo = getimagesize( $tarfile );

                            $newName = str_replace( $extension, $sel_ex, $tarfile );

                            if ( 'jpg' === $extension ) {

                                $img = imagecreatefromjpeg( $tarfile );

                            } elseif ( 'png' === $extension ) {

                                $img = imagecreatefrompng( $tarfile );
                                imagepalettetotruecolor( $img );
                                imagealphablending( $img, true);    
                            } else {                   
                                continue;
                            }
                    
                            // $ope -> 1:webp出力, 2:AVIF出力, 3:webp->base64出力, 4AVIF->base64
                            if ( $ope > 2 ) {
                                ob_start();// 出力データの溜め置き開始
                                if ( $ope & 1 ) {
                                    $result = imagewebp( $img, null, $quality );
                                } else {
                                    $result = imageavif( $img, null, $quality );
                                }
                                $newimg = ob_get_contents();// 溜め置いたデータを変数に代入
                                ob_end_clean();// 溜め置き終わり

                                $fileinfo = getimagesize( $tarfile );
                                $encoded_file = base64_encode( $newimg );
                                
                                echo '<p>' . $tarfile . ' : ' . $fileinfo[3] . ' : ' . ( string ) ( floor( filesize( $tarfile ) / 100 ) / 10 ) . 'kb  <a href="img_to_webp_dir.php">back top</a></p>';
                                echo '<p>changed after size ( string length ) : ' . ( string ) ( floor( strlen( $encoded_file ) / 100 ) / 10 ) . ' kb, ' . $sel_ex . ' quality : ' . $quality . '</p>';
                                echo '<table><tr><td>original</td><td>BASE64</td></tr>';
                                echo '<tr><td><img src="' . $tarfile . '" style="margin-right:20px;"></td>';
                                echo '<td><img src="data:image/webp;base64,' . $encoded_file . '"></td></tr>';
                                echo '</table>';
                                echo '<p class="copystr" data-tar="enc' . ( string ) $key . '" title="copy data to Clip-Board" style="width:50px;padding:3px;background:linear-gradient(silver,white,lightgrey);border-radius:3px;" onMouseLeave="this.style.cursor=\'default\'" onMouseOver="this.style.cursor=\'pointer\'">copy</p>';
                                echo '<p><textarea id="enc' . ( string ) $key . '" cols="100" rows="20">' . $encoded_file . '</textarea></p>'; 
                                echo '<br><div style="position:relative;width:100%;height:5px;border-top:dotted 5px red;border-bottom:dotted 5px blue;"><br>';  

                            } else {
                                if ( $ope & 1 ) {
                                    $result = imagewebp( $img, $newName, $quality );
                                } else {
                                    $result = imageavif( $img, $newName, $quality );
                                }
                                
                                echo '<p>' . $tarfile . ' : ' . $fileinfo[3] . ' : ' . floor( filesize( $tarfile ) / 100 ) / 10 . 'kb  <a href="img_to_webp_dir.php">back top</a></p>';
                                if ( $result ) {
                                    $fileinfow = getimagesize( $newName );

                                    echo '<p>changed after size ( string length ) : ' .  floor( filesize( $newName ) / 100 ) / 10 . 'kb, ' . $sel_ex . ' quality : ' . $quality . '</p>';
                                    echo '<table><tr><td>original</td><td>' . $sel_ex . '</td></tr>';
                                    echo '<tr><td><img src="' . $tarfile . '" style="margin-right:20px;"></td>';
                                    echo '<td><img src="' . $newName . '"></td></tr>';
                                    echo '</table>';
                                } else {
                                    echo '<td>Failed to convert to webp.</td></tr></table>';
                                }

                                echo '<br><div style="position:relative;width:100%;height:5px;border-top:dotted 5px red;border-bottom:dotted 5px blue;"></div>';  
                            }
                        } else {
                            echo '<p>Can\'t detect target file : ' . $val . ' <a href="img_to_webp_dir.php">back top</a></p>';
                        }
                    }
                } else {
            ?>
                    <form action="" method="get">
                        <p>get child image-file in this path. if limit, into condition.</p>
                        <p><input type="text" value="all" name="condition" size="80"></p>
                        <p>Quality : <input type="number" name="quality" min="50" max="100" value="75"></p>
                        <p>Operation : <select name="ope">
                                <option selected value="1">to webp</option>
                                <option value="3">to BASE64 after convert to webp</option>
                                <option value="2">to AVIF</option>
                                <option value="4">to BASE64 after convert to AVIF</option>
                            </select>
                        </p>
                        <p><input type="submit" name="submit"></p>
                    </form>
            <?php
                }
            ?>
		</div>
	</div>
</div>

<script type="text/javascript">
    ( function () {
        const doc = document,
            copystr = doc.getElementsByClassName( 'copystr' ),
            copystr_len = copystr.length;
        
        for ( let i = 0; i < copystr_len; i++ ) {
            copystr[ i ].onclick = function( e ) {
                const tarp = this.getAttribute( 'data-tar' ),
                    imgbs = doc.getElementById( tarp ).innerText,
                    tartxtarea = doc.getElementById( tarp );

                tartxtarea.select();

                doc.execCommand( 'copy' );
            }
        }
    }());
</script>
</body>
</html>
PHP
CopyExpand

なお、BASE64 化した場合の webp or AVIFを表示させる場合も、jpgpng と同様。jpg の部分を webp or AVIF に変えるだけのこと。たとえば css なら。

#outerframe{ background:url( data:image/webp;base64,UklGRtAgAABXRUJQV・・・) no-repeat center / contain; }
PHP
CopyExpand

phpimagewebp でやってみたり、cwebp でやってみたり、あるいは Squoosh でと、色々な画像であれこれ試してみたが、クォリティーが同じでも全く同じデータサイズになることはなかったがそんなに違いもなかった。全く同じにならないのは、細かいところのオプションのデフォルトの違いによるものだろうか。

あと、png においては、元画像よりもサイズが大きくなってしまうものもあった。これはすでにサイズが圧縮されていて小さくなっているもので、透明部分が多いものの傾向に思えた。これは逆にいえば、png において、透明部分が多い画像によっては、TinyPNG などのサイトによってダイエットしたもののほうが、webp にするよりもサイズは小さくできるということなのかもしれない。というか、それほど TinyPNG での圧縮はクォリティーが下がるということなのだろうか。
png においては、そのあたりの事を確認しつつ、どちらを使用するかを決めていく必要があるようだ。

そして次ページでは、逆のコンバート。webp or AVIFjpg へと戻す方の変換を。

Leave a Reply!

JavaScript is necessary to send a comment.
You can edit and delete your comment if you input a edit key.
Edit key is necessary for attesting you when you edit and delete it.
The tag of HTML cannot be used in comment.
When you comment for the first time, it is displayed after the approval of the administrator.
Because I cannot speak English so much, it takes time to answer.
Required fields are marked *.

※Please enter more than 5 characters only alphabets.
※Edit or delete are possible for 2000 days after approval.

*

♠Simplistic Comment User Editable v4.0

♠When visitors leave comments on the site this site collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.
♠This site does not use cookie when visitors leave comments and commenter edit comment.
♠This site uses Akismet to reduce spam. Learn how your comment data is processed.

Comments feed

Trackback URL : https://strix.main.jp/wp-trackback.php?p=169324

Sanbanse Funabashi

Top

スクロールさせるか画像をクリックすると元に戻ります。