随机函数的“特征”之一是它是伪随机的,即,在给定相同种子的情况下,它总是会输出相同的序列。
因此,您可以为每个“图像”存储:
sourcefile - the name of the source image
seed - integer, maybe the start time of this sequence
position - number of pixels that need to be shown, or maybe % completion
所以说你想输出$sourcefile
带有种子$seed
和$position
可见像素百分比的图像。你甚至不需要使用 alpha:
// Load image
$src = imageCreateFromPNG($sourcefile); // Assume image is PNG
// Create work image
$new = imageCreateTrueColor(ImageSX($src), ImageSY($src)); // new image of same size
mt_srand($seed); // Seed the Mersenne Twister generator
// Number of pixels to set: $position = 0: NONE, $position = 100: ALL
$pixels = round($position*imageSX($src)*imageSY($src)/100);
// Now we have a problem: if we do $pixels attempts, mt_rand might sometimes
// return the same pixel again. So we end up setting less than $pixels pixels.
// So we do this the expensive way, saving an array of yet-to-be-used pixels.
$max = ImageSX($src)*ImageSY($src);
$pixelid = array();
for ($i = 0; $i < $max; $i++)
$pixelid[] = $i;
$W = ImageSX($src);
while($pixels--)
{
// Extract one pixel
$chosen = $pixelid[$idx = mt_rand(0, $pixels)];
array_splice ($pixelid, $idx, 1); // Remove extracted pixel from array
$x = $chosen % $W;
$y = ($chosen - $x)/ $W;
$rgb = imagecolorat($src, $x, $y);
$pix = imagecolorsforindex($src, $rgb);
$rgb = imageColorAllocate($new, $pix['red'], $pix['green'], $pix['blue']);
imageSetPixel($new, $x, $y, $rgb);
}
ImageDestroy($src);
// $new has now exactly $pixels set to the same pixels of $src,
// the rest are undefined (you can fill $new with white beforehand...)
Header("Content-Type: image/png");
ImagePNG($new);
变化
无需拼接数组,您可以涂抹“使用过的”像素,即使这不会给出均匀分布:
$cnt = count($pixelid);
while($pixels--)
{
// Extract one pixel
$idx = mt_rand(0, $cnt);
// If the extracted pixel is null, find next pixel that is unextracted
// and if there are none, restart from the beginning of the array.
while (-1 == ($chosen = $pixelid[$idx]))
if ($cnt == ++$idx)
$idx = 0;
$chosen = $pixelid[$idx];
$pixelid[$idx] = -1;
或者你可以……重试。但是当图像几乎完成时,这可能会很昂贵。
$cnt = count($pixelid);
while($pixels--)
{
// Extract one pixel
for ($idx = mt_rand(0, $cnt); $pixelid[$idx] != -1; $idx = mt_rand(0, $cnt))
;
$chosen = $pixelid[$idx];
$pixelid[$idx] = -1;
如果您不关心图像总是以不同的方式重建,您可以使用array_shuffle()
代替mt_rand()
,并且在每次迭代中我从 中提取第 i 个像素$pixelid
。
最后一个选项是重新实现array_shuffle()
使用mt_rand
手册页中的详细说明(参见 tim at leethost dot com 的示例):
function array_new_shuffle(&$items, $seed)
{
mt_srand($seed);
for ($i = count($items) - 1; $i > 0; $i--)
{
$j = @mt_rand(0, $i);
list($items[$i], $items[$j]) = array($items[$j], $items[$i]);
}
}
所以你会array_new_shuffle()
反对$pixelid
using $seed
,然后按顺序从洗牌数组中提取元素:
for ($idx = 0; $idx < $pixels; $idx++)
{
$chosen = $pixelid[$idx];
...
大图像
对于大图像,处理数组的成本太高,而且内存不足。因此,为了避免mt_rand()
重复命中相同的像素(当图像完成 99% 时这可能会出现问题,因此随机命中仍然可行的 1% 像素之一的概率当然是 1%),这个 hack 使用另一个图像作为索引。
这将“数组”限制为 2^24 个条目,即边长为 2^12 或 4096 像素的图像。
内存节省是巨大的:每个图像像素现在需要 16 个字节,而不是大约 176 个字节(这在我的 Linux 64 位机器上)。这意味着一张 1024x1024 像素的图像只需要大约 17M 的 RAM。
在我的系统上,这个脚本每秒处理大约 180k 像素(1024x1024 图像在 7.4 秒内被 100% 处理,其中大约需要 2 个用于图像加载和设置)。
$seed = 0;
$position = 2;
$sourcefile = '/home/lserni/Lena19721024-filtered.png';
mt_srand($seed); // Seed the Mersenne Twister generator
// Load image
$src = ImageCreateTrueColor(512,512);
// $src = imageCreateFromPNG($sourcefile); // Assume image is PNG
$W = ImageSX($src);
$H = ImageSY($src);
// Total number of pixels
$size = $W*$H;
if (($W > 4095) || ($H > 4095))
die("Image too big");
// Create work image
$new = imageCreateTrueColor($W, $H); // new image of same size
/*
if ($position > 50)
{
$position = 100-$position;
$tmp = $src;
$src = $new;
$new = $tmp;
}
*/
// Number of pixels to set: $position = 0: NONE, $position = 100: ALL
$pixels = round($position*$size/100.0);
// Create a temporary buffer image of the same size
$fix = imageCreateTrueColor($W, $H);
for ($i = 0; $i < $size; $i++)
{
$b = $i & 0xFF;
$g = ($i >> 8) & 0xFF;
$r = ($i >> 16) & 0xFF;
imageSetPixel($fix, $i % $W, floor($i / $W), imageColorAllocate($fix, $r, $g, $b));
}
while($pixels--)
{
// Recover one of the available pixel indexes
$idx = mt_rand(0, $size--);
// Recover index from image
$y = floor($idx / $W);
$x = $idx % $W;
$idx = imageColorAt($fix, $x, $y);
$lst = imageColorAt($fix, $size % $W, floor($size / $W));
$b = $lst & 0xff; $lst >>= 8;
$g = $lst & 0xff; $lst >>= 8;
$r = $lst & 0xff;
imageSetPixel($fix, $x, $y, imageColorAllocate($fix, $r, $g, $b));
// Whew. Now recover true x and y from new $idx
$y = floor($idx / $W);
$x = $idx % $W;
$rgb = imagecolorat($src, $x, $y);
$pix = imagecolorsforindex($src, $rgb);
$rgb = imageColorAllocate($new, $pix['red'], $pix['green'], $pix['blue']);
imageSetPixel($new, $x, $y, $rgb);
}
ImageDestroy($src);
// $new has now exactly $pixels set to the same pixels of $src,
// the rest are undefined (you can fill $new with white beforehand...)
// die("Memory: " . memory_get_peak_usage());
Header("Content-Type: image/png");
ImagePNG($new);
优化
您会注意到上述代码中有一个注释部分。如果碰巧$position
超过 50%,比如 70%,那么创建一个空图像并将 70% 的像素从好图像复制到空图像是没有意义的。将 30% 的空像素从空图像复制到好图像,“涂黑”它更有意义。这可以通过简单地交换$new
和$src
调整来完成$position
。我还没有真正彻底测试过这段代码。这就是我留下评论的原因。但欢迎您试一试。
执行
使用 PRNG 的优点是您不需要保存任何图像seed
,而只需position
- 通常总共 8 个字节。
如果 A 收到位置 1,并要求接收最多 5 个位置(即 5% 的图像可见),并且您保存种子和这个值 5,并将其用于 B,那么 B 将看到相同的 5 % 那个人 A 得到了。
除原始图像外,所有图像均未保存或加载。
如果您可以在 $_GET 参数中传递种子和位置,您可以在浏览器中显示不同阶段的图像(例如image.php?seed=12345678&position=5
,将显示设置为 5% 像素的图像。您还可以指定像素数而不是百分比,当然)。
只要像素是随机选择的,这就是有效的:如果人 A 可以选择他或她想要的确切像素,那么这种方法是无效的,您需要保存各个像素位置,这可以通过多种方式完成:使用以二进制格式保存一对 (x,y) 的平面文件,或保存整个图像。后一种方法更容易理解,并且在每一步都需要存储一个完整的图像,所以如果这是一个游戏并且你想“重播”它,你可能需要巨大的磁盘空间。第一种方法可能合理地要求每个像素六个字节,即相当于一个具有相同高度且宽度是原始图像的两倍的图像,或者每个像素只有四个字节。