在我的上一篇教程中,我们讨论了使用PHP GD库进行基本的图像处理。在该教程中,我简要介绍了该库,并向大家展示了如何从文件中加载图像或在PHP中从头创建图像。之后,我们学习了如何使用GD来裁剪、旋转、缩放和翻转图像。我介绍了imagefilter() ,对脚本中加载的图像资源应用不同的过滤器。我还提到了GD中一些有用的函数,如imagesx() 和imagesy() ,以获得加载图像的宽度和高度。
在我的上一个GD教程结束时,你已经学会了如何使用该库来自动完成基本任务,如调整一个目录中所有图片的大小,或在保存最终结果前对它们应用灰度等过滤器。如果你以前从未使用过PHP GD库,我建议你在阅读本教程之前先阅读那篇GD介绍性文章。
在本教程中,我们将了解GD中更多有用的函数,以及如何用它们来自动完成更多的图像处理任务。
使用卷积矩阵处理图像
除了边缘的像素,图像中的每个像素都被其他八个像素所包围。像模糊或边缘检测的效果是根据每个像素的值和周围像素的值来计算的。例如,在边缘检测中,颜色的急剧变化意味着我们已经到达了图像中某个物体的边缘。例如,在下面的图像中,从白色到棕色的突然变化将标志着杯子和桌子的边界。
指定这种过滤器的一个简单方法是使用所谓的 "卷积矩阵"。GD提供了imageconvolution( $image, $matrix, $div, $offset) ,将3x3卷积矩阵应用于图像资源$image 。
$matrix 参数是一个由三个数组组成的数组,每个数组包含三个浮点值--也就是说,它是一个3x3矩阵。第一个数组的第一个元素被乘以左上角像素的颜色值。同样地,第一个数组的第二个元素乘以中央像素上面的像素的颜色值。像素的最终颜色是由所有这些乘法的结果相加得到的,然后除以$div ,进行归一化。归一化通常使最终的颜色值保持在255以下。
正如我们所看到的,$div 参数被用作卷积结果的除数,以使其数值正常化。另一方面,$offset 参数用于为所有颜色指定一个偏移值。你将在下面的例子中看到它是如何影响最终结果的。
卷积实例
下面是一些不同的卷积矩阵的列表,我们将其应用于桌子上的杯子的图像。
箱形模糊
$box_blur = array([1, 1, 1], [1, 1, 1], [1, 1, 1]);
imageconvolution($im_php, $box_blur, 9, 0);
箱形模糊的工作原理是将每个像素与它的邻居平均起来。我们将除数的值设置为9,因为三个数组中所有元素的总和是9。
锐化
$sharpen = array([0, -1, 0], [-1, 5, -1], [0, -1, 0]);
imageconvolution($im_php, $sharpen, 1, 0);
锐化的工作原理是夸大每个像素和它的邻居之间的差异。这使得边缘更清晰一些。在锐化的情况下,除数仍然是1,因为三个数组中所有元素的总和是1。
浮雕
$emboss = array([-2, -1, 0], [-1, 1, 1], [0, 1, 2]);
imageconvolution($im_php, $emboss, 1, 0);
浮雕矩阵与锐化矩阵类似,只是左上角的值是负的,右下角的值是正的,这就是浮雕效果的产生。在浮雕卷积矩阵的情况下,所有元素的总和是1,所以我们不必担心归一化或颜色偏移的问题。
边缘检测
$edge_detect = array([-1, -1, -1], [-1, 8, -1], [-1, -1, -1]);
imageconvolution($im_php, $edge_detect, 1, 0);
imageconvolution($im_php, $edge_detect, 1, 255);
边缘检测与锐化类似,但效果更强。而且,图像的原始值被赋予的权重并不比邻居的高--这意味着我们只关心边缘,而不是原始的纯色区域。
有了边缘检测,所有数组元素的总和为0。这意味着我们得到的图像将大部分是黑色的,除非颜色有急剧的变化,这一般发生在物体的边缘。通过将偏移量参数设置为255,可以将大部分黑色的图像变成白色。
下面的图片显示了所有这些卷积矩阵的结果:
图像复制函数
PHP GD有很多函数可以复制图像的一部分,然后调整其大小或合并。在使用这些函数时,一定要记住,PHP认为图像资源的左上角是其原点。一个正的x值会带你到图像的右边,一个正的y值会带你到更远的地方。
这些函数中最简单的是imagecopy( $dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h) 。它将把源图像复制到目标图像上。$dst_x 和$dst_y 参数决定了左上角的位置,被复制的图像将被粘贴在那里。$src_x,$src_y,$src_w, 和$src_h 参数决定了源图像的矩形部分,它将被复制到目的地。
你可以使用这个功能来裁剪图像,使用imagecreatetruecolor() 从头开始创建一个图像,并将源图像的裁剪矩形复制到其中。你也可以用它在图像上添加水印,但你必须记住,用这种方法,水印的大小不能根据我们图像的大小而改变。
解决这个问题的一个办法是使用imagecopyresized( $dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h) 函数。它接受imagecopy() 的所有参数和两个额外的参数,以确定源图像将被复制的目标区域的大小。
imagecopyresized() 函数并不完美,因为它不能很好地放大和缩小图像。然而,你可以使用imagecopyresampled() ,它接受所有相同的参数,可以获得更好的大小调整质量。
带透明度的复制
还有两个与复制图像有关的函数,你会发现它们非常有用:imagecopymerge() 和imagecopymergegray() 。
函数imagecopymerge( $dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct) 与imagecopy() 相似,其中附加的$pct 参数决定了复制的图像的透明度。值为0意味着没有透明度,值为100意味着完全透明。当你不想在水印后面完全隐藏主图像的内容时,这将有很大的帮助。
另一方面,imagecopymergegray() 函数使用最后一个参数将源图像转换为灰度。如果它被设置为0,源图像将失去所有的颜色。如果它被设置为100,源图像将保持不受影响。
图像复制实例
下面的例子使用imagecopy() 函数将一个图像的右半部分变成它的底片。我们已经在前面的教程中讨论了这个代码片段中使用的其他函数,如imagefilter() 和imagescale():
$im_php = imagecreatefromjpeg('fish-mosaic.jpg');
$im_php = imagescale($im_php, 800);
$im_php_inv = imagescale($im_php, 800);
$im_width = imagesx($im_php);
$im_height = imagesy($im_php);
imagefilter($im_php_inv, IMG_FILTER_NEGATE);
imagecopy($im_php, $im_php_inv, $im_width/2, 0, $im_width/2, 0, $im_width/2, $im_height);
$new_name = 'fish-mosaic-half-negate.jpg';
imagejpeg($im_php, $new_name);
在这里,我们创建了原始图像的两个副本,每个副本都被缩小到800像素宽。之后,我们使用imagefilter() 函数来创建$img_php_inv 图像资源的负片。然后用imagecopy() 函数将这个负像的右半部分复制到原始图像上。
这是对imagecopy() 函数的一个非常基本的使用。你可以看到下面的结果。你也可以把图像分成更小的部分或条纹来创造更有趣的图像效果。我们将在下面的代码片断中使用imagecopymergegray() 函数,在原鱼图像中创造出更多的条纹:
$im_php = imagecreatefromjpeg('fish-mosaic.jpg');
$im_php = imagescale($im_php, 800);
$im_php_bw = imagescale($im_php, 800);
$im_width = imagesx($im_php);
$im_height = imagesy($im_php);
$stripes = 200;
for($i = 0; $i < $stripes; $i++) {
if($i%2 == 0) {
imagecopymergegray($im_php, $im_php_bw, $i*$im_width/$stripes, 0, $i*$im_width/$stripes, 0, $im_width/$stripes, $im_height, 0);
} else {
imagecopymergegray($im_php, $im_php_bw, $i*$im_width/$stripes, 0, $i*$im_width/$stripes, 0, $im_width/$stripes, $im_height, 100);
}
}
imagefilter($im_php, IMG_FILTER_CONTRAST, -255);
imagefilter($im_php, IMG_FILTER_COLORIZE, 250, 0, 0, 100);
$new_name = 'fish-mosaic-stripes.jpg';
imagejpeg($im_php, $new_name);
上面的代码例子使用了与上一个例子类似的策略,但这次我们把图像分成了更小的条纹,根据变量$i 的值,这些条纹变成了灰度或保持不变。在完成所有的复制合并操作后,我们在图像上应用两个滤镜,使条纹更加突出。
下面的图片显示了这两个函数与不同的图像过滤器相结合的最终结果:
图像的Base64编码和解码
在处理图像时,你可能会遇到图像数据需要从Base64格式解码或编码成Base64格式的情况。GD库中没有直接的函数来完成这个任务。但是,你可以使用一些其他的PHP函数来轻松完成这个任务。
PHP GD中没有任何函数可以返回图像数据。然而,像imagejpeg(),imagepng(), 和imagegif() 这样的函数能够将图像输出到浏览器或文件。我们可以使用一些输出控制函数(如ob_get_contents() )来捕获它们的输出。 下面是一个将JPEG图像转换成Base64数据的例子:
<?php
$img = imagecreatefromjpeg('frog.jpg');
ob_start();
imagejpeg($img);
$image_data = ob_get_contents();
ob_end_clean();
$image_data_base64 = base64_encode($image_data);
// Outputs: /9j/4AAQSkZJRgABAQEAYABgAAD//gA+Q1JFQVR....
echo $image_data_base64;
?>
首先,我们使用imagecreatefromjpeg() 函数从我们的图像文件中获得一个新的图像对象。之后,我们使用ob_start() 函数来打开输出缓冲。然后,imagejpeg() 函数输出原始图像流,因为我们没有传递文件名作为第二个参数。然后使用ob_get_contents() ,将这些捕获的数据存储在$image_data 变量中,该变量返回输出缓冲区的内容。然后使用base64_encode() 函数将原始图像数据转换为Base64格式。
PHP GD自带了一个名为imagecreatefromstring() 的函数,它将从给定字符串中的图像流中创建一个新的图像。假设你有一个Base64编码的字符串中的图像数据。你可以使用下面的代码,用PHP把它保存为一个图像文件:
<?php
$image_data = base64_decode($image_data_base64);
$img = imagecreatefromstring($image_data);
imagejpeg($img, 'profile.jpg');
imagedestroy($img);
?>
我们首先在base64_decode() 的帮助下对Base64字符串进行解码,以得到原始图像数据流。然后将这个数据流传递给imagecreatefromstring() 函数,以得到我们的图像。之后,你可以简单地使用imagejpeg() 函数将图像保存为JPEG文件。
在图像中嵌入水印或其他信息
一些组织在他们的图像上添加水印,以明确他们拥有该图像。这也有助于品牌的识别,并阻止其他人公然复制图片的行为。由于有了PHP GD,给图像加水印是一项简单的工作:
$im_php = imagecreatefromjpeg('waterfall.jpg');
$watermark = imagecreatefrompng('watermark.png');
$im_width = imagesx($im_php);
$im_height = imagesy($im_php);
$watermark = imagescale($watermark, $im_width/5);
$wt_width = imagesx($watermark);
$wt_height = imagesy($watermark);
imagecopy($im_php, $watermark, 0.95*$im_width - $wt_width, 0.95*$im_height - $wt_height, 0, 0, $wt_width, $wt_height);
$new_name = 'waterfall-watermark.jpg';
imagejpeg($im_php, $new_name);
在上面的代码片段中,我们已经创建了两个不同的图像资源,分别使用imagecreatefromjpeg() 作为主图像和imagecreatefrompng() 作为水印。我们使用imagesx() 和imagesy() 函数确定主图像的宽度和高度。
并非所有你想做水印的图片都有相同的尺寸。如果你不根据主图像的尺寸来调整水印的大小,它可能看起来很奇怪。例如,200px的水印在1000px的图片上可能看起来不错,但对于600px宽的图片来说就太大了,而在2400px宽的图片上则可能看起来太小。
因此,我们使用imagescale() 函数,使水印始终保持在原始图像宽度的五分之一。然后我们使用imagecopy() 函数将水印放置在正确的位置。下面是上述代码片断的最终结果:
除了水印,你还可以添加其他信息,如照片的拍摄地点或照片的拍摄时间。
最后的思考
在前面的教程中介绍了图像处理的基础知识后,我们又了解了GD库中其他一些有用的功能。教程的第一部分讨论了我们如何在PHP中使用卷积矩阵来处理图像。我还展示了一些卷积矩阵操作的例子,以帮助大家了解PHP是如何得出不同像素的颜色值的。
教程的第二部分解释了如何复制和/或调整图像的一部分以粘贴到其他地方。当我们想在图像上添加一些东西如水印或时间戳时,这就很方便了。
试着使用所有这些功能来创造一些有趣的图像效果吧!