随机函数的“特征”之一是它是伪随机的,即,给定相同的种子,它将始终输出相同的序列。
所以你可以为每个“图像”存储:
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
and $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()
using mt_rand
如手册页中所述(请参阅 leethost dot com 的 tim 的示例):
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
and $src
和调整$position
。我还没有真正彻底地测试过这段代码;这就是我留下评论的原因。但欢迎您尝试一下。
执行
使用 PRNG 的优点是你不需要保存任何图像, 但只有seed
and position
- 通常总共八个字节。
如果人 A 接收位置 1,并要求接收最多 5 个位置(即图像可见的 5%),并且您保存种子和这个值 5,并将其用于人 B,那么人 B 将看到相同的 5 A 得到的百分比。
除原始图像外,所有图像均未保存或加载。
如果您可以在 $_GET 参数中传递种子和位置,则可以在浏览器中显示不同阶段的图像(例如image.php?seed=12345678&position=5
将显示设置了 5% 像素的图像。当然,您还可以指定像素数而不是百分比)。
这有效只要像素是随机选择的:如果 A 到达choose他或她想要的确切像素,然后这种方法无效并且您需要保存各个像素位置,这可以通过多种方式完成:使用以二进制格式保存一对 (x,y) 的平面文件,或保存整个图像。后一种方法更容易理解,并且每一步都需要存储整个图像,因此如果这是一个游戏并且您想“重玩”它,则可能需要巨大的磁盘空间。第一种方法可能合理地需要每个像素 6 个字节,即相当于具有相同高度且宽度是原始图像两倍的图像,或者每个像素少至 4 个字节。