🌈

PHP(GD)でグラデーションを描画する

2024/01/17に公開

PHPのGDでグラデーションを作る

今開発しているアプリでサイズ可変なグラデーション画像が必要だったので、PHPの画像処理ライブラリであるGDを用いて簡単なWebAPI的な感じにしようと思い作り始めました。
グラデーションを作る関数だったりが既にあるものだと思っていたのですが、GDにはそれが存在せず、少し考える必要があったのでここにまとめます。

ちなみに完成品はhttps://api.pocopota.com/hue-makerにアクセスすると確認できます。

その他仕様

出力はpng

name required description
width 画像の横幅. 初期は400
height 画像の縦幅. 初期は300
colors グラデーションに使用する色をコンマ「,」区切りで指定. カラーコードで#は除く. 複数色指定可

https://github.com/PocoPota/apis

実装したいこと

  • サイズ可変
  • グラデーションカラー指定可
  • グラデーションカラー任意の数指定可
  • グラデーション角度指定可
  • WebAPI形式で提供、パラメータに情報埋め込む

サイズ可変は容易です。GDにグラデーションの関数等が存在しないという基礎的な問題を除けばカラーも可能でしょう。
角度は結論からいうとちょっと無理そうでした。(どうにかすれば出来るとは思う。そこまで必要な要件でもないのでギブアップ)
あとグラデーションの方向は横に色が変化していく向きで作っていきます。

ロジック概要

GDには1pxの直線を引くという関数が存在するので、1pxごとに色を計算し、描画する方法が一番シンプルかと思います。
直線を引く関数imageline()の色の指定はRGB各色要素を0~255までの256段階の整数値で指定します。そのためRGBの各数値をグラデーションの片方の色からもう一方の色へ徐々に近づければ色の変化は作れそうです。

実装!

最初の諸々記述

パラメータの取得や色の定義、後々使う関数を定義しておきます。
colorsパラメータは「,」区切りなのでexplodeを用いて配列形式にしておきます。

色がパラメータで指定されていない場合、事前に作っておいたカラープリセットからランダムに選びます。
関数hexToRgbはカラーコード(例;#3ea8ff)形式をRGB各色(例;r:62, g:168, b:255))へ変換し、配列にして返す関数です。後から使います。

// パラメータ取得
$width = isset($_GET['w']) ? intval($_GET['w']) : 400;
$height = isset($_GET['h']) ? intval($_GET['h']) : 300;
$colors = isset($_GET['colors']) ? explode(',', $_GET['colors']) : null;

// 使用色定義
$presetColors = [
    ['FC5C7D', '6A82FB'],
    ['6DD5FA', '2980B9'],
    ['00b09b','96c93d'],
    ['f7ff00','db36a4']
];

// 色未指定の場合プリセットから定義
if(!$colors){
    $colors = $presetColors[rand(0, count($presetColors) - 1)];
}

// 画像生成
$image = imagecreatetruecolor($width, $height);

// 関数定義
function hexToRgb($hex){
    $red = hexdec(substr($hex, 0, 2));
    $green = hexdec(substr($hex, 2, 2));
    $blue = hexdec(substr($hex, 4, 2));
    return [$red, $green, $blue];
}

画像分割&塗り

colorsに含まれる色の数には制限が無いので、複数色指定された場合にも動くように実装します。1色指定の場合は以下のコード外で塗りつぶし関数が動いています。
stepsでRGB各色が1pxズレるごとにどれだけ値を変更すればいいかを計算しています。
隣り合う色の各色の値の差を求め、(画像横幅*分割する数)で割っています。分割する数とは(色数 - 1)です。

次に実際に描画していきます。
以下の図は3色指定された場合です。for文でi=0のときに黒から徐々に変化させ中央で一度処理が終わります。その後i=1のときに中央から白を起点とし、徐々に変化させるようなプログラムです。
常に2色間のグラデーションを考えます。
i=1以降のときにどの座標から描画開始すればいいかを考慮する必要があります。
この時、色も先程計算したstepsを用いて1pxごとに計算しています。

for($i = 0; $i < count($colors) - 1; $i++){
    // 1pxごとにどのくらい色を変えるか計算
    $steps = [
        (hexToRgb($colors[$i + 1])[0] - hexToRgb($colors[$i])[0]) / ($width / (count($colors) - 1)),
        (hexToRgb($colors[$i + 1])[1] - hexToRgb($colors[$i])[1]) / ($width / (count($colors) - 1)),
        (hexToRgb($colors[$i + 1])[2] - hexToRgb($colors[$i])[2]) / ($width / (count($colors) - 1))
    ];
    // 1pxごとに線を描画
    $startColor = hexToRgb($colors[$i]);
    for($j = 0; $j < $width / (count($colors) - 1); $j++){
        $color = imagecolorallocate($image, $startColor[0] + $steps[0] * $j, $startColor[1] + $steps[1] * $j, $startColor[2] + $steps[2] * $j);
        imageline($image, $j + $i * ($width / (count($colors) - 1)), 0, $j + $i * ($width / (count($colors) - 1)), $height, $color);
    }
}

出力

あとは$imageを出力して完成です。

おわりに

グラデーションで使用する色を3色以上でも可能にしたのでプログラムが少し複雑になりました。PHP以外で実装したら1行で済むとかできそうですね。
今回の場合、特性上1pxずつ計算を行う必要があるのである程度の大きさの画像になると少し処理に時間がかかるおそれがあります。
また回転も実装しようとimagerotateでサクッと回してみましたが、以下のように望まぬ形式になったのでいつかきちんと実装したいと思います。

プログラム全文は以下リポジトリのhue-maker.phpにあります。
https://github.com/PocoPota/apis

Discussion