imagerotate の小数から整数への変換仕様

imagerotate の結果を計算で出したいなと思い次のコードで検証してみました。

<?php
// 対象の画像のサイズ。
$sw = 1000;
$sh = 2000;
// 実測用に画像生成
$im = imagecreatetruecolor($sw,$sh);
for($deg = -360; $deg <= 360; $deg +=15){
   $rad = deg2rad($deg); // 三角関数はラジアンを取るので角度をラジアンに変換
   // 回転後のサイズ計算(四捨五入)
   $w = round(abs($sw * cos($rad)) + abs($sh * sin($rad)));
   $h = round(abs($sw * sin($rad)) + abs($sh * cos($rad)));
   // 実際に回転させた時のサイズ
   $im_t =imagerotate($im, $deg, -1);
   $rw = imagesx($im_t);
   $rh = imagesy($im_t);
   imagedestroy($im_t);
   // 比較
   if($w != $rw || $h != $rh){
     echo “$deg: calc($w, $h) != real($rw, $rh) “.”\n”;
   }
}
?>

結果:

-330: calc(1866, 2232) != real(1867, 2233)
-315: calc(2121, 2121) != real(2122, 2122)
-300: calc(2232, 1866) != real(2233, 1867)
-240: calc(2232, 1866) != real(2233, 1867)
-225: calc(2121, 2121) != real(2122, 2122)
-210: calc(1866, 2232) != real(1867, 2233)
-150: calc(1866, 2232) != real(1867, 2233)
-135: calc(2121, 2121) != real(2122, 2122)
-120: calc(2232, 1866) != real(2233, 1867)
-60: calc(2232, 1866) != real(2233, 1867)
-45: calc(2121, 2121) != real(2122, 2122)
-30: calc(1866, 2232) != real(1867, 2233)
30: calc(1866, 2232) != real(1867, 2233)
45: calc(2121, 2121) != real(2122, 2122)
60: calc(2232, 1866) != real(2233, 1867)
120: calc(2232, 1866) != real(2233, 1867)
135: calc(2121, 2121) != real(2122, 2122)
150: calc(1866, 2232) != real(1867, 2233)
210: calc(1866, 2232) != real(1867, 2233)
225: calc(2121, 2121) != real(2122, 2122)
240: calc(2232, 1866) != real(2233, 1867)
300: calc(2232, 1866) != real(2233, 1867)
315: calc(2121, 2121) != real(2122, 2122)
330: calc(1866, 2232) != real(1867, 2233)

この結果から判断して、
imagerotate では内部的に ceil で繰り上げにしているようなので
単純に round -> ceil にかえてみたところ、
結果は次のようになりました。

-360: calc(1001, 2001) != real(1000, 2000)
-270: calc(2001, 1001) != real(2000, 1000)
-180: calc(1001, 2001) != real(1000, 2000)
-90: calc(2000, 1001) != real(2000, 1000)

PHP の float の特性(*1)が悪さをしているのか、切り上げ処理で GD の結果と食い違いができてしまったようです。
ということで次のようにすれば完全に一致しました。

<?php
// 回転後のサイズ計算(imagerotate 互換の切り上げ)
$fw = abs($sw * cos($rad)) + abs($sh * sin($rad));
$w = ceil(“$fw”); // 一度文字列にする
$fh = abs($sw * sin($rad)) + abs($sh * cos($rad));
$h = ceil(“$fh”); // 同上

 
 *1: PHP の float は有効桁数を超えると表示上その部分が見えなくなります。

$f = 1 + 1E-13;
echo “$f\n”; // 1 が表示される
if($f > 1){
    echo “$f > 1\n”; // 1 > 1 が表示される。
}
echo sprintf(“%.15f”, $f).”\n”; // 1.000000000000100 が表示される