【问题标题】:PHP rotate image WITH clipped edgesPHP旋转带有剪裁边缘的图像
【发布时间】:2011-11-06 16:05:08
【问题描述】:

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

这是显示我的问题的示例图像:

如您所见,在 Photoshop 中,边缘被剪裁了。在 PHP 中,图像的大小只是因为旋转而增加。我真的很想得到和 Photoshop 一样的结果。知道如何在 PHP 中执行此操作吗?

(我只能访问 GD 库。)

【问题讨论】:

  • 这只是一个简单的数学问题:您需要剪出一个与原始图像大小相同的矩形。矩形的中心将是新图像的中心
  • 您是否考虑过使用浏览器的 CSS 旋转来代替? (是的,它可以在所有浏览器中完成,甚至是旧版本的 IE)
  • 使用 CSS 进行旋转;将图像放入容器中;给容器一个固定的宽度,并将其 CSS 属性“溢出”设置为“隐藏”;这样就可以解决问题了。

标签: php gd


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

    宽度′ = 高度 * s + 宽度 * c 高度′ = 高度 * c + 宽度 * s

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

    s = abs(sin(角度)) c = abs(cos(角度))

    请注意,由于三角恒等式 sin(-θ) = -sin(θ) 和 cos(-θ) = cos (θ),在上面的公式中,测量angle时,正方向是顺时针还是逆时针都没有关系。

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

    x = 旋转宽度 / 2 - 宽度 / 2 y = 旋转高度 / 2 - 高度 / 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。例如,如果源图像的尺寸是方形的,则这成立,因为:

    长度′ = 长度 * (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)
    ));
    

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

    (有关 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() 在裁剪后的图像底部添加了一条 1 像素的黑线。请参阅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);
    

    【讨论】:

      【解决方案3】:

      当前答案仅作为解决问题的一种方式。它没有讨论计算新框大小所需的数学。

      您需要在旋转后使用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);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-02-09
        • 2015-02-18
        • 1970-01-01
        • 1970-01-01
        • 2017-10-09
        • 2013-05-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多