PHP旋转图像与剪裁的边缘

时间:2011-11-06 16:05:09

标签: php gd

使用PHP的GD库,您可以使用imagerotate功能旋转图像。这个功能的缺点是它不会剪切边缘,这正是我需要的。

以下是显示我的问题的示例图片:

Photoshop vs GD rotate

如您所见,在Photoshop中边缘被剪裁。在PHP中,由于旋转,图像的大小刚刚增加。我真的想得到与Photoshop中相同的结果。知道如何在PHP中执行此操作吗?

(我只能访问GD库。)

3 个答案:

答案 0 :(得分:6)

如果你懒得计算旋转图像的新尺寸,只需使用支持开箱即用的这些计算的基于GD的图像库。

一个这样的库是Wideimage。您加载原始图像,获取它的宽度和高度,然后旋转它,然后使用centermiddle中所谓的智能坐标裁剪它,并使用原始图像宽度和高度:

$image = WideImage::load('big.png');
$width = $image->getWidth();
$height = $image->getHeight();
$image->rotate(120)->crop("center", "middle", $width, $height);

答案 1 :(得分:2)

如果angle是旋转角度,则旋转图像的宽度和高度width′height′is given by

width′ = height * s + width * c
height′ = height * c + width * s

其中width是源图像宽度,height是源图像高度,并且:

s = abs(sin(angle))
c = abs(cos(angle))

请注意,由于三角恒等式sin( - θ)= -sin(θ)和cos( - θ)= cos (θ),如果在测量angle时正方向是顺时针方向或逆时针方向,则上述公式无关紧要。

您知道的一件事是源图像的中心点被映射到旋转图像的中心。因此,如果width′ ≥ widthheight′ ≥ height,则旋转图像中左上角的坐标为:

x = rotated_width / 2 - width / 2
y = rotated_height / 2 - height / 2

因此,如果width′ ≥ widthheight′ ≥ height,以下PHP代码将根据需要裁剪图像:

$cropped = imagecrop($rotated, array(
    'x' => $rotated_width / 2 - $width / 2,
    'y' => $rotated_height / 2 - $height / 2,
    'width' => $width,
    'height' => $height
));

但是,这仅适用于width′ ≥ widthheight′ ≥ height。例如,如果源图像的尺寸是正方形,则这是成立的,因为:

length′ = length * (abs(sin(angle)) + abs(cos(angle)))

abs(sin(angle)) + abs(cos(angle)) ≥ 1
请参阅"y = abs(sin(theta)) + abs(cos(theta)) minima" on WolframAlpha

如果width′ ≥ widthheight′ ≥ height不成立(例如,250×40图像顺时针旋转50°),则生成的图像将完全变黑(因为无效的裁剪矩形会传递给imagecrop ())。

可以使用以下代码修复这些问题:

$cropped = imagecrop($rotated, array(
    'x' => max(0, $rotated_width / 2 - $width / 2),
    'y' => max(0, $rotated_height / 2 - $height / 2),
    'width' => min($width, $rotated_width),
    'height'=> min($height, $rotated_height)
));

此代码的结果是下图中的蓝色区域:

diagram depicting cropping area

(有关SVG版本,请参阅http://fiddle.jshell.net/5jf3wqn4/show/。)
在图中,半透明的红色矩形表示原始的250×40图像。红色矩形表示图像的旋转。虚线矩形表示由imagerotate()创建的图像的边界。

将这些全部放在一起,这里是用于旋转和裁剪图像的PHP代码:

$filename = 'http://placehold.it/250x40';
$degrees = -50;

$source = imagecreatefrompng($filename);
$width = imagesx($source);
$height = imagesy($source);

$rotated = imagerotate($source, $degrees, 0);
imagedestroy($source);
$rotated_width = imagesx($rotated);
$rotated_height = imagesy($rotated);

$cropped = imagecrop($rotated, array(
    'x' => max(0, (int)(($rotated_width - $width) / 2)),
    'y' => max(0, (int)(($rotated_height - $height) / 2)),
    'width' => min($width, $rotated_width),
    'height'=> min($height, $rotated_height)
));
imagedestroy($rotated);

imagepng($cropped);

编辑:似乎有a bug in imagecrop(),其中1px黑线被添加到裁剪图像的底部。有关解决方法,请参阅imagecrop() alternative for PHP < 5.5

EDIT2:我发现imageaffine()可以比imagerotate()产生更好的质量。用户“abc at ed48 dot com”has commented,使用仿射变换,用于逆时针旋转给定角度。

以下是使用imageaffine()而不是imagerotate()的代码:

// Crops the $source image, avoiding the black line bug in imagecrop()
// See:
// - https://bugs.php.net/bug.php?id=67447
// - https://stackoverflow.com/questions/26722811/imagecrop-alternative-for-php-5-5
function fixedcrop($source, array $rect)
{
    $cropped = imagecreate($rect['width'], $rect['height']);
    imagecopyresized(
        $cropped,
        $source,
        0,
        0,
        $rect['x'],
        $rect['y'],
        $rect['width'],
        $rect['height'],
        $rect['width'],
        $rect['height']
    );
    return $cropped;
}

$filename = 'http://placehold.it/250x40';
$degrees = -50;

$source = imagecreatefrompng($filename);
$width = imagesx($source);
$height = imagesy($source);

$radians = deg2rad($degrees);
$cos = cos($radians);
$sin = sin($radians);
$affine = [ $cos, -$sin, $sin, $cos, 0, 0 ];
$rotated = imageaffine($source, $affine);
imagedestroy($source);
$rotated_width = imagesx($rotated);
$rotated_height = imagesy($rotated);

$cropped = fixedcrop($rotated, array(
    'x' => max(0, (int)(($rotated_width - $width) / 2)),
    'y' => max(0, (int)(($rotated_height - $height) / 2)),
    'width' => min($width, $rotated_width),
    'height'=> min($height, $rotated_height)
));
imagedestroy($rotated);

imagepng($cropped);

答案 2 :(得分:1)

  

目前的答案只是解决问题的一种方法。它没有讨论计算新盒子大小所需的数学。

旋转后,您需要使用imagecrop裁剪图像。为此,您可以使用以下公式使其居中。

rotated_dimension * (1 - source_dimension / rotated_dimension) * 0.5

这是一个有效的例子。 placehold.it URL可以替换为本地文件路径。

<?php
$filename = 'http://placehold.it/200x200';
$degrees = -45;

header('Content-type: image/png');

$source = imagecreatefrompng($filename);
$sw = imagesx($source);
$sh = imagesy($source);

$rotate = imagerotate($source, $degrees, 0);
$rw = imagesx($rotate);
$rh = imagesy($rotate);

$crop = imagecrop($rotate, array(
    'x' => $rw * (1 - $sw / $rw) * 0.5,
    'y' => $rh * (1 - $sh / $rh) * 0.5,
    'width' => $sw,
    'height'=> $sh
));

imagepng($crop);