在PHP中将图像转换为《黑客帝国》的调色板风格
你有没有注意到电影《黑客帝国》里那微微泛绿的色彩?这部电影运用了各种各样的调色板,不过在《黑客帝国》的虚拟世界里,所有画面都带上了淡淡的绿色调。
这种调色板效果是通过色彩分级算法在后期处理中达成的。影片的原始色彩会经过一个滤镜处理,对模拟场景里的颜色做细微调整。
在本文中,我们就来探讨如何把像素转变为《黑客帝国》里那种绿色调效果,以及怎样在PHP里对图像运用这一系统。其实,在Drupal开发过程中,有时候也会涉及到对图像风格的调整,与我们这里要做的图像调色转变有一定的相通之处。
滤镜算法的原理是,从影片中提取红、绿、蓝三种颜色,并且把它们的数值范围转换为0到1。接着,将红色通道的值提升到7/5次方,蓝色通道的值提升到8/5次方。在这个过程中,绿色通道的值保持不变。
我们可以用下面这种方式来表示这个过程:
(r, g, b) => (r^7/5, g, b^8/5)
用十进制格式表示就是:
r, g, b => (r^1.4, g, b^1.6)
由于我们是把0到1之间的数值提升到大于1的幂,这会让这些通道的值稍微降低。我们只要对图像中的每个像素都应用这个算法就好了。
在PHP中,我们能够使用imagecolorat()函数提取像素的颜色值。这个函数会返回一个整数值,我们可以从这个值中提取出红、绿、蓝三种颜色的值。
$colour = imagecolorat($img, $x, $y);
// 提取红、绿、蓝值。
$r = ($colour >> 16) & 0xFF;
$g = ($colour >> 8) & 0xFF;
$b = $colour & 0xFF;
这里的红、绿、蓝值范围是0到255。这意味着在进行计算之前,我们得把它们转换为0到1的范围,这可以通过将数值除以255来实现。
// 将红色值从0 - 255的范围转换到0 - 1的范围。
$r = ($r / 255);
// 将值提升到1.4次方。
$r = min(pow($r, 1.4), 1);
// 将值转换回0 - 255的范围。
$r = 255 * ($r);
// 将蓝色值从0 - 255的范围转换到0 - 1的范围。
$b = ($b / 255);
// 将值提升到1.6次方。
$b = min(pow($b, 1.6), 1);
// 将值转换回0 - 255的范围。
$b = 255 * ($b);
然后,我们可以使用imagecolorallocate()函数创建新的颜色,再使用imagesetpixel()函数将新图像中相同位置的像素设置为该颜色。
// 创建新颜色。
$newColour = imagecolorallocate($newImage, $r, $g, $b);
// 设置新图像中的像素。
imagesetpixel($newImage, $x, $y, $newColour);
与其展示这个算法所产生的不同颜色,不如我们直接在一张图像上运行这个算法,这样就能更清楚地看到效果。我公司有一张在2024年苏格兰DrupalCamp上演讲的照片,这张照片目前是作者简介配图,所以很容易就能拿到。
将这张图像通过颜色转换算法处理后,现在照片里就像是在《黑客帝国》的虚拟世界里演讲一样。
画面确实变绿了。
通过降低红色和蓝色的数值,绿色以一种特殊的方式得到了增强,而且在转换过程中并没有损失太多信息。我们仍然保留了所有的基本颜色,唯一损失的信息是那些红色或蓝色值非常接近零的部分,因为这些值会被处理为零。
把上述所有内容整合起来,我们得到以下函数:
/**
* 将JPEG图像文件转换为《黑客帝国》的色彩方案。
*
* 《黑客帝国》的色彩方案是通过从图像中提取红、绿、蓝值,
* 并将它们转换到0 - 1的范围来计算的。然后使用公式r^7/5, g, b^8/5
* (十进制表示为r^1.4, g, b^1.6)对这些值进行处理。
* 在使用新颜色创建图像之前,我们再将这些值转换回0 - 255的范围。
*
* 生成的图像文件会以 "matrix_" 为前缀并保存到磁盘。原始文件不会被修改。
*
* @param string $imgfile
* 要转换的文件。
*/
function matrixFilter(string $imgfile) {
// 将图像加载到内存中。
$img = imagecreatefromjpeg($imgfile);
// 获取图像的尺寸。
$imagex = imagesx($img);
$imagey = imagesy($img);
// 创建一个具有相同尺寸的新图像。
$newImage = imagecreatetruecolor($imagex, $imagey);
// 遍历当前图像中的所有像素。
for ($x = 0; $x < $imagex; $x++) {
for ($y = 0; $y < $imagey; $y++) {
$colour = imagecolorat($img, $x, $y);
// 提取红、绿、蓝值。
$r = ($colour >> 16) & 0xFF;
$g = ($colour >> 8) & 0xFF;
$b = $colour & 0xFF;
// 将红色值从0 - 255的范围转换到0 - 1的范围。
$r = ($r / 255);
// 将值提升到1.4次方。
$r = min(pow($r, 1.4), 1);
// 将值转换回0 - 255的范围。
$r = 255 * ($r);
// 将蓝色值从0 - 255的范围转换到0 - 1的范围。
$b = ($b / 255);
// 将值提升到1.6次方。
$b = min(pow($b, 1.6), 1);
// 将值转换回0 - 255的范围。
$b = 255 * ($b);
// 创建新颜色。
$newColour = imagecolorallocate($newImage, $r, $g, $b);
// 设置新图像中的像素。
imagesetpixel($newImage, $x, $y, $newColour);
}
}
// 将新图像写入磁盘。
imagejpeg($newImage, 'matrix_' . $imgfile);
// 释放内存。
imagedestroy($img);
imagedestroy($newImage);
}
这个函数只接受JPEG图像,所以如果你尝试传入其他格式的图像,将会返回错误。在Drupal模块开发过程中,我们也需要注意函数对输入格式的要求,这有利于保证系统的稳定性。
可以用下面的方式使用这个函数:
matrixFilter('someimage.jpg');
你可以随意将此函数应用到自己的JPEG格式图像上进行颜色转换。
对这段代码的一个改进方向是让这个函数更通用,不再硬编码颜色缩放值,而是将它们作为参数传入,然后根据这些参数来调整颜色。随着Drupal11的逐步推进,代码的通用性和灵活性显得更加重要,这样的改进思路在Drupal开发中也同样适用。


