使用GD获取图像所有像素的更好方法

8
以下代码使用SimpleImage库
我有一个从图像中获取像素的函数:
function getPixel($x, $y) {

    $colors = imagecolorsforindex($this->image, imagecolorat($this->image, $x, $y));
    $n[0] = $colors['red'];
    $n[1] = $colors['green'];
    $n[2] = $colors['blue'];
    $str = "#";
    for($x=0;$x < 3; $x++){
        $n[$x] = intval($n[$x],10);
        if (is_nan($n[$x])) return "00";
        $n[$x] = max(0, min($n[$x],255));
        $bam = "0123456789ABCDEF";
        $str .= $bam{($n[$x]-$n[$x]%16)/16} . $bam{$n[$x]%16};
    }
    return $str; 
}

为了获取图像中的每个像素,我使用这个循环:

$arr = [];
for($y = 0;$y < $image->getHeight(); $y++){
    $arr[$y] = [];
    for($x = 0; $x < $image->getWidth(); $x++){
        $arr[$y][$x] = $image->getPixel($x, $y);
    }
}

我想知道是否有一种使用GD和PHP更快的方法来完成这个任务?
编辑
我的想法是以某种方式获取GD中每个像素的值,而不需要使用PHP循环。

$bam 移到循环外面? - Cole Tobin
你到底是用像素信息做什么?了解这一点将推动答案。 - Charles
@charles - 我正在使用它来设置表格单元格的背景颜色。 - PitaJ
FYI,所有的GD都会调用一个循环来获取信息。只是说一下。 - Cole Tobin
2个回答

5

这是一种丑陋的暴力方法,需要对每个像素使用imagecolorat()函数。在我测试的大型(6.5百万像素)图像中,它大约需要3.6秒,比@cleong提出的更优雅的GD hack快了约1.1秒。它还具有残酷的简单性,并且不依赖于未经记录的内部GD2格式。

$alphaLookup = array(
    0x00000000=>"\xff",0x01000000=>"\xfd",0x02000000=>"\xfb",0x03000000=>"\xf9",
    0x04000000=>"\xf7",0x05000000=>"\xf5",0x06000000=>"\xf3",0x07000000=>"\xf1",
    0x08000000=>"\xef",0x09000000=>"\xed",0x0a000000=>"\xeb",0x0b000000=>"\xe9",
    0x0c000000=>"\xe7",0x0d000000=>"\xe5",0x0e000000=>"\xe3",0x0f000000=>"\xe1",
    0x10000000=>"\xdf",0x11000000=>"\xdd",0x12000000=>"\xdb",0x13000000=>"\xd9",
    0x14000000=>"\xd7",0x15000000=>"\xd5",0x16000000=>"\xd3",0x17000000=>"\xd1",
    0x18000000=>"\xcf",0x19000000=>"\xcd",0x1a000000=>"\xcb",0x1b000000=>"\xc9",
    0x1c000000=>"\xc7",0x1d000000=>"\xc5",0x1e000000=>"\xc3",0x1f000000=>"\xc1",
    0x20000000=>"\xbf",0x21000000=>"\xbd",0x22000000=>"\xbb",0x23000000=>"\xb9",
    0x24000000=>"\xb7",0x25000000=>"\xb5",0x26000000=>"\xb3",0x27000000=>"\xb1",
    0x28000000=>"\xaf",0x29000000=>"\xad",0x2a000000=>"\xab",0x2b000000=>"\xa9",
    0x2c000000=>"\xa7",0x2d000000=>"\xa5",0x2e000000=>"\xa3",0x2f000000=>"\xa1",
    0x30000000=>"\x9f",0x31000000=>"\x9d",0x32000000=>"\x9b",0x33000000=>"\x99",
    0x34000000=>"\x97",0x35000000=>"\x95",0x36000000=>"\x93",0x37000000=>"\x91",
    0x38000000=>"\x8f",0x39000000=>"\x8d",0x3a000000=>"\x8b",0x3b000000=>"\x89",
    0x3c000000=>"\x87",0x3d000000=>"\x85",0x3e000000=>"\x83",0x3f000000=>"\x81",
    0x40000000=>"\x7f",0x41000000=>"\x7d",0x42000000=>"\x7b",0x43000000=>"\x79",
    0x44000000=>"\x77",0x45000000=>"\x75",0x46000000=>"\x73",0x47000000=>"\x71",
    0x48000000=>"\x6f",0x49000000=>"\x6d",0x4a000000=>"\x6b",0x4b000000=>"\x69",
    0x4c000000=>"\x67",0x4d000000=>"\x65",0x4e000000=>"\x63",0x4f000000=>"\x61",
    0x50000000=>"\x5f",0x51000000=>"\x5d",0x52000000=>"\x5b",0x53000000=>"\x59",
    0x54000000=>"\x57",0x55000000=>"\x55",0x56000000=>"\x53",0x57000000=>"\x51",
    0x58000000=>"\x4f",0x59000000=>"\x4d",0x5a000000=>"\x4b",0x5b000000=>"\x49",
    0x5c000000=>"\x47",0x5d000000=>"\x45",0x5e000000=>"\x43",0x5f000000=>"\x41",
    0x60000000=>"\x3f",0x61000000=>"\x3d",0x62000000=>"\x3b",0x63000000=>"\x39",
    0x64000000=>"\x37",0x65000000=>"\x35",0x66000000=>"\x33",0x67000000=>"\x31",
    0x68000000=>"\x2f",0x69000000=>"\x2d",0x6a000000=>"\x2b",0x6b000000=>"\x29",
    0x6c000000=>"\x27",0x6d000000=>"\x25",0x6e000000=>"\x23",0x6f000000=>"\x21",
    0x70000000=>"\x1f",0x71000000=>"\x1d",0x72000000=>"\x1b",0x73000000=>"\x19",
    0x74000000=>"\x17",0x75000000=>"\x15",0x76000000=>"\x13",0x77000000=>"\x11",
    0x78000000=>"\x0f",0x79000000=>"\x0d",0x7a000000=>"\x0b",0x7b000000=>"\x09",
    0x7c000000=>"\x07",0x7d000000=>"\x05",0x7e000000=>"\x03",0x7f000000=>"\x00"
); // Lookup table for chr(255-(($x >> 23) & 0x7f)).


$chr = array(
    "\x00","\x01","\x02","\x03","\x04","\x05","\x06","\x07","\x08","\x09","\x0A","\x0B","\x0C","\x0D","\x0E","\x0F",
    "\x10","\x11","\x12","\x13","\x14","\x15","\x16","\x17","\x18","\x19","\x1A","\x1B","\x1C","\x1D","\x1E","\x1F",
    "\x20","\x21","\x22","\x23","\x24","\x25","\x26","\x27","\x28","\x29","\x2A","\x2B","\x2C","\x2D","\x2E","\x2F",
    "\x30","\x31","\x32","\x33","\x34","\x35","\x36","\x37","\x38","\x39","\x3A","\x3B","\x3C","\x3D","\x3E","\x3F",
    "\x40","\x41","\x42","\x43","\x44","\x45","\x46","\x47","\x48","\x49","\x4A","\x4B","\x4C","\x4D","\x4E","\x4F",
    "\x50","\x51","\x52","\x53","\x54","\x55","\x56","\x57","\x58","\x59","\x5A","\x5B","\x5C","\x5D","\x5E","\x5F",
    "\x60","\x61","\x62","\x63","\x64","\x65","\x66","\x67","\x68","\x69","\x6A","\x6B","\x6C","\x6D","\x6E","\x6F",
    "\x70","\x71","\x72","\x73","\x74","\x75","\x76","\x77","\x78","\x79","\x7A","\x7B","\x7C","\x7D","\x7E","\x7F",
    "\x80","\x81","\x82","\x83","\x84","\x85","\x86","\x87","\x88","\x89","\x8A","\x8B","\x8C","\x8D","\x8E","\x8F",
    "\x90","\x91","\x92","\x93","\x94","\x95","\x96","\x97","\x98","\x99","\x9A","\x9B","\x9C","\x9D","\x9E","\x9F",
    "\xA0","\xA1","\xA2","\xA3","\xA4","\xA5","\xA6","\xA7","\xA8","\xA9","\xAA","\xAB","\xAC","\xAD","\xAE","\xAF",
    "\xB0","\xB1","\xB2","\xB3","\xB4","\xB5","\xB6","\xB7","\xB8","\xB9","\xBA","\xBB","\xBC","\xBD","\xBE","\xBF",
    "\xC0","\xC1","\xC2","\xC3","\xC4","\xC5","\xC6","\xC7","\xC8","\xC9","\xCA","\xCB","\xCC","\xCD","\xCE","\xCF",
    "\xD0","\xD1","\xD2","\xD3","\xD4","\xD5","\xD6","\xD7","\xD8","\xD9","\xDA","\xDB","\xDC","\xDD","\xDE","\xDF",
    "\xE0","\xE1","\xE2","\xE3","\xE4","\xE5","\xE6","\xE7","\xE8","\xE9","\xEA","\xEB","\xEC","\xED","\xEE","\xEF",
    "\xF0","\xF1","\xF2","\xF3","\xF4","\xF5","\xF6","\xF7","\xF8","\xF9","\xFA","\xFB","\xFC","\xFD","\xFE","\xFF",
); // Lookup for chr($x): much faster.



function lookUpImageBytes($canvas) {
    global $alphaLookup, $chr;
    $imageWidth = imagesx($canvas);
    $imageHeight = imagesy($canvas);
    // Faster than append, and >500mb more memory efficient for a 6.5MPx image!
    $imageData = str_repeat("\x00\x00\x00\x00", $imageWidth * $imageHeight);

    // Loop over each single pixel.
    $j = 0;
    for ($y = 0; $y < $imageHeight; $y++) {
        for ($x = 0; $x < $imageWidth; $x++) {
            // Grab the pixel data.
            $argb = imagecolorat($canvas, $x, $y);
            $imageData[$j++] = $alphaLookup[$argb & 0x7f000000]; // A (convert from GD).
            // NB: three $chr arrays to replace the shifts WON'T improve speed.
            $imageData[$j++] = $chr[($argb >> 16) & 0xFF]; // R
            $imageData[$j++] = $chr[($argb >> 8) & 0xFF]; // G
            $imageData[$j++] = $chr[$argb & 0xFF]; // B
        }
    }
    return $imageData;
}

2
感谢您抽出时间回答这么老的问题。 - PitaJ
2
好的,我需要答案!但是在其他地方找不到,所以……花了一整天时间编写各种测试。不能让那些工作白费! :) - Dewi Morgan
我注意到一个非常奇怪的事情:我试图通过制作$chr数组的三个副本来进一步优化,每个字节对应R、G和B中的一个,因此一个键入了0x01=>"\x01",一个键入了0x0100=>"\x01",另一个键入了0x010000=>"\x01"。我认为这会使它更快,因为我可以省略移位操作。但实际上,时间从3.6秒增加到了18秒!即使不使用查找表,只调用chr()函数,速度也比这快大约7秒!我想稀疏数组查找比函数调用慢得多... - Dewi Morgan

2
有一种更快的方法,但是非常棘手。它涉及将图像导出为GD2格式,然后直接访问字节。GD以块形式存储像素,因此循环遍历像素并不直观。如果正确执行,它确实可以提供更好的性能,因为您不再需要在每个像素上调用函数的开销。
这是一些我编写的代码,可以从图像中捕获alpha通道。正如您所看到的,由于块处理,循环并不那么明显:
    imagesavealpha($image, true);
    ob_start();
    imagegd2($image);
    $gdRawData = ob_get_clean();

    $array = unpack("Nheader/nversion/nimageWidth/nimageHeight/nchunkSize/ndataFormat/ncolumnCount/nrowCount", $gdRawData);
    $imageWidth = $array['imageWidth'];
    $imageHeight = $array['imageHeight'];

    $chunkWidth = $chunkHeight = $array['chunkSize'];
    $columnCount = $array['columnCount'];
    $rowCount = $array['rowCount'];
    $lastColumnWidth = $imageWidth - ($columnCount - 1) * $chunkWidth;
    $lastRowHeight = $imageHeight - ($rowCount - 1) * $chunkHeight;

    static $transparencyToAlpha = array("\x7f" => "\x00", "\x7f" => "\x01", "\x7e" => "\x02", "\x7e" => "\x03", "\x7d" => "\x04", "\x7d" => "\x05", "\x7c" => "\x06", "\x7c" => "\x07", "\x7b" => "\x08", "\x7b" => "\x09", "\x7a" => "\x0a", "\x7a" => "\x0b", "\x79" => "\x0c", "\x79" => "\x0d", "\x78" => "\x0e", "\x78" => "\x0f", "\x77" => "\x10", "\x77" => "\x11", "\x76" => "\x12", "\x76" => "\x13", "\x75" => "\x14", "\x75" => "\x15", "\x74" => "\x16", "\x74" => "\x17", "\x73" => "\x18", "\x73" => "\x19", "\x72" => "\x1a", "\x72" => "\x1b", "\x71" => "\x1c", "\x71" => "\x1d", "\x70" => "\x1e", "\x70" => "\x1f", "\x6f" => "\x20", "\x6f" => "\x21", "\x6e" => "\x22", "\x6e" => "\x23", "\x6d" => "\x24", "\x6d" => "\x25", "\x6c" => "\x26", "\x6c" => "\x27", "\x6b" => "\x28", "\x6b" => "\x29", "\x6a" => "\x2a", "\x6a" => "\x2b", "\x69" => "\x2c", "\x69" => "\x2d", "\x68" => "\x2e", "\x68" => "\x2f", "\x67" => "\x30", "\x67" => "\x31", "\x66" => "\x32", "\x66" => "\x33", "\x65" => "\x34", "\x65" => "\x35", "\x64" => "\x36", "\x64" => "\x37", "\x63" => "\x38", "\x63" => "\x39", "\x62" => "\x3a", "\x62" => "\x3b", "\x61" => "\x3c", "\x61" => "\x3d", "\x60" => "\x3e", "\x60" => "\x3f", "\x5f" => "\x40", "\x5f" => "\x41", "\x5e" => "\x42", "\x5e" => "\x43", "\x5d" => "\x44", "\x5d" => "\x45", "\x5c" => "\x46", "\x5c" => "\x47", "\x5b" => "\x48", "\x5b" => "\x49", "\x5a" => "\x4a", "\x5a" => "\x4b", "\x59" => "\x4c", "\x59" => "\x4d", "\x58" => "\x4e", "\x58" => "\x4f", "\x57" => "\x50", "\x57" => "\x51", "\x56" => "\x52", "\x56" => "\x53", "\x55" => "\x54", "\x55" => "\x55", "\x54" => "\x56", "\x54" => "\x57", "\x53" => "\x58", "\x53" => "\x59", "\x52" => "\x5a", "\x52" => "\x5b", "\x51" => "\x5c", "\x51" => "\x5d", "\x50" => "\x5e", "\x50" => "\x5f", "\x4f" => "\x60", "\x4f" => "\x61", "\x4e" => "\x62", "\x4e" => "\x63", "\x4d" => "\x64", "\x4d" => "\x65", "\x4c" => "\x66", "\x4c" => "\x67", "\x4b" => "\x68", "\x4b" => "\x69", "\x4a" => "\x6a", "\x4a" => "\x6b", "\x49" => "\x6c", "\x49" => "\x6d", "\x48" => "\x6e", "\x48" => "\x6f", "\x47" => "\x70", "\x47" => "\x71", "\x46" => "\x72", "\x46" => "\x73", "\x45" => "\x74", "\x45" => "\x75", "\x44" => "\x76", "\x44" => "\x77", "\x43" => "\x78", "\x43" => "\x79", "\x42" => "\x7a", "\x42" => "\x7b", "\x41" => "\x7c", "\x41" => "\x7d", "\x40" => "\x7e", "\x40" => "\x7f", "\x3f" => "\x80", "\x3f" => "\x81", "\x3e" => "\x82", "\x3e" => "\x83", "\x3d" => "\x84", "\x3d" => "\x85", "\x3c" => "\x86", "\x3c" => "\x87", "\x3b" => "\x88", "\x3b" => "\x89", "\x3a" => "\x8a", "\x3a" => "\x8b", "\x39" => "\x8c", "\x39" => "\x8d", "\x38" => "\x8e", "\x38" => "\x8f", "\x37" => "\x90", "\x37" => "\x91", "\x36" => "\x92", "\x36" => "\x93", "\x35" => "\x94", "\x35" => "\x95", "\x34" => "\x96", "\x34" => "\x97", "\x33" => "\x98", "\x33" => "\x99", "\x32" => "\x9a", "\x32" => "\x9b", "\x31" => "\x9c", "\x31" => "\x9d", "\x30" => "\x9e", "\x30" => "\x9f", "\x2f" => "\xa0", "\x2f" => "\xa1", "\x2e" => "\xa2", "\x2e" => "\xa3", "\x2d" => "\xa4", "\x2d" => "\xa5", "\x2c" => "\xa6", "\x2c" => "\xa7", "\x2b" => "\xa8", "\x2b" => "\xa9", "\x2a" => "\xaa", "\x2a" => "\xab", "\x29" => "\xac", "\x29" => "\xad", "\x28" => "\xae", "\x28" => "\xaf", "\x27" => "\xb0", "\x27" => "\xb1", "\x26" => "\xb2", "\x26" => "\xb3", "\x25" => "\xb4", "\x25" => "\xb5", "\x24" => "\xb6", "\x24" => "\xb7", "\x23" => "\xb8", "\x23" => "\xb9", "\x22" => "\xba", "\x22" => "\xbb", "\x21" => "\xbc", "\x21" => "\xbd", "\x20" => "\xbe", "\x20" => "\xbf", "\x1f" => "\xc0", "\x1f" => "\xc1", "\x1e" => "\xc2", "\x1e" => "\xc3", "\x1d" => "\xc4", "\x1d" => "\xc5", "\x1c" => "\xc6", "\x1c" => "\xc7", "\x1b" => "\xc8", "\x1b" => "\xc9", "\x1a" => "\xca", "\x1a" => "\xcb", "\x19" => "\xcc", "\x19" => "\xcd", "\x18" => "\xce", "\x18" => "\xcf", "\x17" => "\xd0", "\x17" => "\xd1", "\x16" => "\xd2", "\x16" => "\xd3", "\x15" => "\xd4", "\x15" => "\xd5", "\x14" => "\xd6", "\x14" => "\xd7", "\x13" => "\xd8", "\x13" => "\xd9", "\x12" => "\xda", "\x12" => "\xdb", "\x11" => "\xdc", "\x11" => "\xdd", "\x10" => "\xde", "\x10" => "\xdf", "\x0f" => "\xe0", "\x0f" => "\xe1", "\x0e" => "\xe2", "\x0e" => "\xe3", "\x0d" => "\xe4", "\x0d" => "\xe5", "\x0c" => "\xe6", "\x0c" => "\xe7", "\x0b" => "\xe8", "\x0b" => "\xe9", "\x0a" => "\xea", "\x0a" => "\xeb", "\x09" => "\xec", "\x09" => "\xed", "\x08" => "\xee", "\x08" => "\xef", "\x07" => "\xf0", "\x07" => "\xf1", "\x06" => "\xf2", "\x06" => "\xf3", "\x05" => "\xf4", "\x05" => "\xf5", "\x04" => "\xf6", "\x04" => "\xf7", "\x03" => "\xf8", "\x03" => "\xf9", "\x02" => "\xfa", "\x02" => "\xfb", "\x01" => "\xfc", "\x01" => "\xfd", "\x00" => "\xfe", "\x00" => "\xff");

    $alphaDataSize = $imageWidth * $imageHeight;
    $alphaData = str_repeat("\x00", $alphaDataSize);

    // loop through all rows 
    for($r = 0, $j = 23; $r < $rowCount; $r++) {
        $rowHeight = ($r == $rowCount - 1) ? $lastRowHeight : $chunkHeight;
        // loop through chunks in each row
        for($c = 0; $c < $columnCount; $c++) {
            $columnWidth = ($c == $columnCount - 1) ? $lastColumnWidth : $chunkWidth;
            $firstY = $r * $chunkHeight;
            $lastY = $firstY + $rowHeight;
            // loop through scanlines in each chunk
            for($y = $firstY; $y < $lastY; $y++) {
                $firstX = $c * $chunkWidth;
                $firstAlphaPosition = $y * $imageWidth + $firstX;
                $lastAlphaPosition = $firstAlphaPosition + $columnWidth;
                // loop through each pixel in each chunk scanline
                for($i = $firstAlphaPosition; $i < $lastAlphaPosition; $i++, $j+= 4) {
                    $alphaData[$i] = $transparencyToAlpha[$gdRawData[$j]];
                }
            }
        }
    }

每个像素由四个字节组成:alpha、红色、绿色和蓝色。这里,我只捕捉第一个字节。

天啊!有没有一个网站可以教我如何自己做这个? - PitaJ
你能告诉我去哪里了解这个吗? - PitaJ
对于使用此代码的人,你可能会感兴趣了解GD2的内部格式是ARGB,因此你想要的索引将是紧随'a'值之后的那些。 'A'的高位是保留位,因此需要查找表。 上面的查找表可以缩小一半,因为每个键有两个值:第二个值将覆盖第一个值。 为了更快一点,你可以将firstY和firstX的计算向外移动一个循环级别。实际上...我将编辑这篇文章来回答问题并进行这些更改。 - Dewi Morgan
注意:即使进行了速度调整,对于我在Windows上使用查找表的暴力方法而言,这种方法的测试速度也要慢大约25%(请参见我的答案)。如果仅提取字母,则可能更快,或者在某些操作系统上更快?在目标机器上进行测试,并选择最适合您的方法。 - Dewi Morgan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接