@@ -21,6 +21,60 @@ class Image
2121{
2222 public static AbstractImagine $ imagine ;
2323
24+ public const CHR = [
25+ "\x00" , "\x01" , "\x02" , "\x03" , "\x04" , "\x05" , "\x06" , "\x07" , "\x08" , "\x09" , "\x0A" , "\x0B" , "\x0C" , "\x0D" , "\x0E" , "\x0F" ,
26+ "\x10" , "\x11" , "\x12" , "\x13" , "\x14" , "\x15" , "\x16" , "\x17" , "\x18" , "\x19" , "\x1A" , "\x1B" , "\x1C" , "\x1D" , "\x1E" , "\x1F" ,
27+ "\x20" , "\x21" , "\x22" , "\x23" , "\x24" , "\x25" , "\x26" , "\x27" , "\x28" , "\x29" , "\x2A" , "\x2B" , "\x2C" , "\x2D" , "\x2E" , "\x2F" ,
28+ "\x30" , "\x31" , "\x32" , "\x33" , "\x34" , "\x35" , "\x36" , "\x37" , "\x38" , "\x39" , "\x3A" , "\x3B" , "\x3C" , "\x3D" , "\x3E" , "\x3F" ,
29+ "\x40" , "\x41" , "\x42" , "\x43" , "\x44" , "\x45" , "\x46" , "\x47" , "\x48" , "\x49" , "\x4A" , "\x4B" , "\x4C" , "\x4D" , "\x4E" , "\x4F" ,
30+ "\x50" , "\x51" , "\x52" , "\x53" , "\x54" , "\x55" , "\x56" , "\x57" , "\x58" , "\x59" , "\x5A" , "\x5B" , "\x5C" , "\x5D" , "\x5E" , "\x5F" ,
31+ "\x60" , "\x61" , "\x62" , "\x63" , "\x64" , "\x65" , "\x66" , "\x67" , "\x68" , "\x69" , "\x6A" , "\x6B" , "\x6C" , "\x6D" , "\x6E" , "\x6F" ,
32+ "\x70" , "\x71" , "\x72" , "\x73" , "\x74" , "\x75" , "\x76" , "\x77" , "\x78" , "\x79" , "\x7A" , "\x7B" , "\x7C" , "\x7D" , "\x7E" , "\x7F" ,
33+ "\x80" , "\x81" , "\x82" , "\x83" , "\x84" , "\x85" , "\x86" , "\x87" , "\x88" , "\x89" , "\x8A" , "\x8B" , "\x8C" , "\x8D" , "\x8E" , "\x8F" ,
34+ "\x90" , "\x91" , "\x92" , "\x93" , "\x94" , "\x95" , "\x96" , "\x97" , "\x98" , "\x99" , "\x9A" , "\x9B" , "\x9C" , "\x9D" , "\x9E" , "\x9F" ,
35+ "\xA0" , "\xA1" , "\xA2" , "\xA3" , "\xA4" , "\xA5" , "\xA6" , "\xA7" , "\xA8" , "\xA9" , "\xAA" , "\xAB" , "\xAC" , "\xAD" , "\xAE" , "\xAF" ,
36+ "\xB0" , "\xB1" , "\xB2" , "\xB3" , "\xB4" , "\xB5" , "\xB6" , "\xB7" , "\xB8" , "\xB9" , "\xBA" , "\xBB" , "\xBC" , "\xBD" , "\xBE" , "\xBF" ,
37+ "\xC0" , "\xC1" , "\xC2" , "\xC3" , "\xC4" , "\xC5" , "\xC6" , "\xC7" , "\xC8" , "\xC9" , "\xCA" , "\xCB" , "\xCC" , "\xCD" , "\xCE" , "\xCF" ,
38+ "\xD0" , "\xD1" , "\xD2" , "\xD3" , "\xD4" , "\xD5" , "\xD6" , "\xD7" , "\xD8" , "\xD9" , "\xDA" , "\xDB" , "\xDC" , "\xDD" , "\xDE" , "\xDF" ,
39+ "\xE0" , "\xE1" , "\xE2" , "\xE3" , "\xE4" , "\xE5" , "\xE6" , "\xE7" , "\xE8" , "\xE9" , "\xEA" , "\xEB" , "\xEC" , "\xED" , "\xEE" , "\xEF" ,
40+ "\xF0" , "\xF1" , "\xF2" , "\xF3" , "\xF4" , "\xF5" , "\xF6" , "\xF7" , "\xF8" , "\xF9" , "\xFA" , "\xFB" , "\xFC" , "\xFD" , "\xFE" , "\xFF" ,
41+ ]; // Lookup for chr($x): much faster.
42+
43+ public const ALPHA_LOOKUP = [
44+ 0x00000000 => "\xff" , 0x01000000 => "\xfd" , 0x02000000 => "\xfb" , 0x03000000 => "\xf9" ,
45+ 0x04000000 => "\xf7" , 0x05000000 => "\xf5" , 0x06000000 => "\xf3" , 0x07000000 => "\xf1" ,
46+ 0x08000000 => "\xef" , 0x09000000 => "\xed" , 0x0a000000 => "\xeb" , 0x0b000000 => "\xe9" ,
47+ 0x0c000000 => "\xe7" , 0x0d000000 => "\xe5" , 0x0e000000 => "\xe3" , 0x0f000000 => "\xe1" ,
48+ 0x10000000 => "\xdf" , 0x11000000 => "\xdd" , 0x12000000 => "\xdb" , 0x13000000 => "\xd9" ,
49+ 0x14000000 => "\xd7" , 0x15000000 => "\xd5" , 0x16000000 => "\xd3" , 0x17000000 => "\xd1" ,
50+ 0x18000000 => "\xcf" , 0x19000000 => "\xcd" , 0x1a000000 => "\xcb" , 0x1b000000 => "\xc9" ,
51+ 0x1c000000 => "\xc7" , 0x1d000000 => "\xc5" , 0x1e000000 => "\xc3" , 0x1f000000 => "\xc1" ,
52+ 0x20000000 => "\xbf" , 0x21000000 => "\xbd" , 0x22000000 => "\xbb" , 0x23000000 => "\xb9" ,
53+ 0x24000000 => "\xb7" , 0x25000000 => "\xb5" , 0x26000000 => "\xb3" , 0x27000000 => "\xb1" ,
54+ 0x28000000 => "\xaf" , 0x29000000 => "\xad" , 0x2a000000 => "\xab" , 0x2b000000 => "\xa9" ,
55+ 0x2c000000 => "\xa7" , 0x2d000000 => "\xa5" , 0x2e000000 => "\xa3" , 0x2f000000 => "\xa1" ,
56+ 0x30000000 => "\x9f" , 0x31000000 => "\x9d" , 0x32000000 => "\x9b" , 0x33000000 => "\x99" ,
57+ 0x34000000 => "\x97" , 0x35000000 => "\x95" , 0x36000000 => "\x93" , 0x37000000 => "\x91" ,
58+ 0x38000000 => "\x8f" , 0x39000000 => "\x8d" , 0x3a000000 => "\x8b" , 0x3b000000 => "\x89" ,
59+ 0x3c000000 => "\x87" , 0x3d000000 => "\x85" , 0x3e000000 => "\x83" , 0x3f000000 => "\x81" ,
60+ 0x40000000 => "\x7f" , 0x41000000 => "\x7d" , 0x42000000 => "\x7b" , 0x43000000 => "\x79" ,
61+ 0x44000000 => "\x77" , 0x45000000 => "\x75" , 0x46000000 => "\x73" , 0x47000000 => "\x71" ,
62+ 0x48000000 => "\x6f" , 0x49000000 => "\x6d" , 0x4a000000 => "\x6b" , 0x4b000000 => "\x69" ,
63+ 0x4c000000 => "\x67" , 0x4d000000 => "\x65" , 0x4e000000 => "\x63" , 0x4f000000 => "\x61" ,
64+ 0x50000000 => "\x5f" , 0x51000000 => "\x5d" , 0x52000000 => "\x5b" , 0x53000000 => "\x59" ,
65+ 0x54000000 => "\x57" , 0x55000000 => "\x55" , 0x56000000 => "\x53" , 0x57000000 => "\x51" ,
66+ 0x58000000 => "\x4f" , 0x59000000 => "\x4d" , 0x5a000000 => "\x4b" , 0x5b000000 => "\x49" ,
67+ 0x5c000000 => "\x47" , 0x5d000000 => "\x45" , 0x5e000000 => "\x43" , 0x5f000000 => "\x41" ,
68+ 0x60000000 => "\x3f" , 0x61000000 => "\x3d" , 0x62000000 => "\x3b" , 0x63000000 => "\x39" ,
69+ 0x64000000 => "\x37" , 0x65000000 => "\x35" , 0x66000000 => "\x33" , 0x67000000 => "\x31" ,
70+ 0x68000000 => "\x2f" , 0x69000000 => "\x2d" , 0x6a000000 => "\x2b" , 0x6b000000 => "\x29" ,
71+ 0x6c000000 => "\x27" , 0x6d000000 => "\x25" , 0x6e000000 => "\x23" , 0x6f000000 => "\x21" ,
72+ 0x70000000 => "\x1f" , 0x71000000 => "\x1d" , 0x72000000 => "\x1b" , 0x73000000 => "\x19" ,
73+ 0x74000000 => "\x17" , 0x75000000 => "\x15" , 0x76000000 => "\x13" , 0x77000000 => "\x11" ,
74+ 0x78000000 => "\x0f" , 0x79000000 => "\x0d" , 0x7a000000 => "\x0b" , 0x7b000000 => "\x09" ,
75+ 0x7c000000 => "\x07" , 0x7d000000 => "\x05" , 0x7e000000 => "\x03" , 0x7f000000 => "\x00"
76+ ]; // Lookup table for chr(255-(($x >> 23) & 0x7f)).
77+
2478 public function __construct (public ImageInterface $ image , public int $ channels = 4 )
2579 {
2680 if ($ this ->image instanceof \Imagine \Vips \Image) {
@@ -263,6 +317,9 @@ public static function fromTensor(Tensor $tensor, string $channelFormat = 'CHW')
263317 // Make sure the tensor is the right data type
264318 $ tensor = $ tensor ->to (Tensor::uint8);
265319
320+ /**
321+ * Handle Vips images
322+ */
266323 if ($ image instanceof \Imagine \Vips \Image) {
267324 $ vipImage = $ image ->getVips ()::newFromMemory ($ tensor ->buffer ()->dump (), $ width , $ height , $ channels , 'uchar ' );
268325
@@ -271,6 +328,9 @@ public static function fromTensor(Tensor $tensor, string $channelFormat = 'CHW')
271328 return new self ($ image , $ channels );
272329 }
273330
331+ /**
332+ * Handle Imagick images
333+ */
274334 if ($ image instanceof \Imagine \Imagick \Image) {
275335 $ map = match ($ channels ) {
276336 1 => 'I ' ,
@@ -287,17 +347,38 @@ public static function fromTensor(Tensor $tensor, string $channelFormat = 'CHW')
287347 return new self ($ image , $ channels );
288348 }
289349
290- $ pixels = $ tensor ->reshape ([$ width * $ height , $ channels ])->toArray ();
350+ /**
351+ * Handle GD images
352+ */
353+
354+
355+ $ gdImage = $ image ->getGdResource ();
356+ $ imageData = $ tensor ->buffer ()->dump ();
357+
358+ $ reverseCHR = array_flip (self ::CHR );
359+ $ reverseAlphaLookup = array_flip (self ::ALPHA_LOOKUP );
291360
361+ $ j = 0 ;
362+ // Iterate through each pixel's worth of string data
292363 for ($ y = 0 ; $ y < $ height ; $ y ++) {
293364 for ($ x = 0 ; $ x < $ width ; $ x ++) {
294- $ index = $ y * $ width + $ x ;
295-
296- $ color = $ channels === 1 ? $ pixels [$ index ][0 ] : $ pixels [$ index ];
365+ $ argb = 0 ;
297366
298- $ color = $ image ->palette ()->color ([$ color [0 ], $ color [1 ], $ color [2 ]], $ color [3 ] ?? null );
367+ if ($ channels >= 1 ) {
368+ $ argb |= ($ reverseCHR [$ imageData [$ j ++]] << 16 ); // R
369+ }
370+ if ($ channels >= 2 ) {
371+ $ argb |= ($ reverseCHR [$ imageData [$ j ++]] << 8 ); // G
372+ }
373+ if ($ channels >= 3 ) {
374+ $ argb |= $ reverseCHR [$ imageData [$ j ++]]; // B
375+ }
376+ if ($ channels >= 4 ) {
377+ $ argb |= $ reverseAlphaLookup [$ imageData [$ j ++]]; // A
378+ }
299379
300- $ image ->draw ()->dot (new Point ($ x , $ y ), $ color );
380+ // Set the pixel at (x, y) to the ARGB value
381+ imagesetpixel ($ gdImage , $ x , $ y , $ argb );
301382 }
302383 }
303384
@@ -356,87 +437,27 @@ public function getPixelData(): string
356437 * brute-force method, suggested by @DewiMorgan on StackOverflow: https://stackoverflow.com/a/30136602/11209184.
357438 * It's tons faster than other methods I tried, and rivals the speed of the Imagick method, so I'll keep it for now.
358439 */
359- $ alphaLookup = [
360- 0x00000000 => "\xff" , 0x01000000 => "\xfd" , 0x02000000 => "\xfb" , 0x03000000 => "\xf9" ,
361- 0x04000000 => "\xf7" , 0x05000000 => "\xf5" , 0x06000000 => "\xf3" , 0x07000000 => "\xf1" ,
362- 0x08000000 => "\xef" , 0x09000000 => "\xed" , 0x0a000000 => "\xeb" , 0x0b000000 => "\xe9" ,
363- 0x0c000000 => "\xe7" , 0x0d000000 => "\xe5" , 0x0e000000 => "\xe3" , 0x0f000000 => "\xe1" ,
364- 0x10000000 => "\xdf" , 0x11000000 => "\xdd" , 0x12000000 => "\xdb" , 0x13000000 => "\xd9" ,
365- 0x14000000 => "\xd7" , 0x15000000 => "\xd5" , 0x16000000 => "\xd3" , 0x17000000 => "\xd1" ,
366- 0x18000000 => "\xcf" , 0x19000000 => "\xcd" , 0x1a000000 => "\xcb" , 0x1b000000 => "\xc9" ,
367- 0x1c000000 => "\xc7" , 0x1d000000 => "\xc5" , 0x1e000000 => "\xc3" , 0x1f000000 => "\xc1" ,
368- 0x20000000 => "\xbf" , 0x21000000 => "\xbd" , 0x22000000 => "\xbb" , 0x23000000 => "\xb9" ,
369- 0x24000000 => "\xb7" , 0x25000000 => "\xb5" , 0x26000000 => "\xb3" , 0x27000000 => "\xb1" ,
370- 0x28000000 => "\xaf" , 0x29000000 => "\xad" , 0x2a000000 => "\xab" , 0x2b000000 => "\xa9" ,
371- 0x2c000000 => "\xa7" , 0x2d000000 => "\xa5" , 0x2e000000 => "\xa3" , 0x2f000000 => "\xa1" ,
372- 0x30000000 => "\x9f" , 0x31000000 => "\x9d" , 0x32000000 => "\x9b" , 0x33000000 => "\x99" ,
373- 0x34000000 => "\x97" , 0x35000000 => "\x95" , 0x36000000 => "\x93" , 0x37000000 => "\x91" ,
374- 0x38000000 => "\x8f" , 0x39000000 => "\x8d" , 0x3a000000 => "\x8b" , 0x3b000000 => "\x89" ,
375- 0x3c000000 => "\x87" , 0x3d000000 => "\x85" , 0x3e000000 => "\x83" , 0x3f000000 => "\x81" ,
376- 0x40000000 => "\x7f" , 0x41000000 => "\x7d" , 0x42000000 => "\x7b" , 0x43000000 => "\x79" ,
377- 0x44000000 => "\x77" , 0x45000000 => "\x75" , 0x46000000 => "\x73" , 0x47000000 => "\x71" ,
378- 0x48000000 => "\x6f" , 0x49000000 => "\x6d" , 0x4a000000 => "\x6b" , 0x4b000000 => "\x69" ,
379- 0x4c000000 => "\x67" , 0x4d000000 => "\x65" , 0x4e000000 => "\x63" , 0x4f000000 => "\x61" ,
380- 0x50000000 => "\x5f" , 0x51000000 => "\x5d" , 0x52000000 => "\x5b" , 0x53000000 => "\x59" ,
381- 0x54000000 => "\x57" , 0x55000000 => "\x55" , 0x56000000 => "\x53" , 0x57000000 => "\x51" ,
382- 0x58000000 => "\x4f" , 0x59000000 => "\x4d" , 0x5a000000 => "\x4b" , 0x5b000000 => "\x49" ,
383- 0x5c000000 => "\x47" , 0x5d000000 => "\x45" , 0x5e000000 => "\x43" , 0x5f000000 => "\x41" ,
384- 0x60000000 => "\x3f" , 0x61000000 => "\x3d" , 0x62000000 => "\x3b" , 0x63000000 => "\x39" ,
385- 0x64000000 => "\x37" , 0x65000000 => "\x35" , 0x66000000 => "\x33" , 0x67000000 => "\x31" ,
386- 0x68000000 => "\x2f" , 0x69000000 => "\x2d" , 0x6a000000 => "\x2b" , 0x6b000000 => "\x29" ,
387- 0x6c000000 => "\x27" , 0x6d000000 => "\x25" , 0x6e000000 => "\x23" , 0x6f000000 => "\x21" ,
388- 0x70000000 => "\x1f" , 0x71000000 => "\x1d" , 0x72000000 => "\x1b" , 0x73000000 => "\x19" ,
389- 0x74000000 => "\x17" , 0x75000000 => "\x15" , 0x76000000 => "\x13" , 0x77000000 => "\x11" ,
390- 0x78000000 => "\x0f" , 0x79000000 => "\x0d" , 0x7a000000 => "\x0b" , 0x7b000000 => "\x09" ,
391- 0x7c000000 => "\x07" , 0x7d000000 => "\x05" , 0x7e000000 => "\x03" , 0x7f000000 => "\x00"
392- ]; // Lookup table for chr(255-(($x >> 23) & 0x7f)).
393-
394- $ chr = [
395- "\x00" , "\x01" , "\x02" , "\x03" , "\x04" , "\x05" , "\x06" , "\x07" , "\x08" , "\x09" , "\x0A" , "\x0B" , "\x0C" , "\x0D" , "\x0E" , "\x0F" ,
396- "\x10" , "\x11" , "\x12" , "\x13" , "\x14" , "\x15" , "\x16" , "\x17" , "\x18" , "\x19" , "\x1A" , "\x1B" , "\x1C" , "\x1D" , "\x1E" , "\x1F" ,
397- "\x20" , "\x21" , "\x22" , "\x23" , "\x24" , "\x25" , "\x26" , "\x27" , "\x28" , "\x29" , "\x2A" , "\x2B" , "\x2C" , "\x2D" , "\x2E" , "\x2F" ,
398- "\x30" , "\x31" , "\x32" , "\x33" , "\x34" , "\x35" , "\x36" , "\x37" , "\x38" , "\x39" , "\x3A" , "\x3B" , "\x3C" , "\x3D" , "\x3E" , "\x3F" ,
399- "\x40" , "\x41" , "\x42" , "\x43" , "\x44" , "\x45" , "\x46" , "\x47" , "\x48" , "\x49" , "\x4A" , "\x4B" , "\x4C" , "\x4D" , "\x4E" , "\x4F" ,
400- "\x50" , "\x51" , "\x52" , "\x53" , "\x54" , "\x55" , "\x56" , "\x57" , "\x58" , "\x59" , "\x5A" , "\x5B" , "\x5C" , "\x5D" , "\x5E" , "\x5F" ,
401- "\x60" , "\x61" , "\x62" , "\x63" , "\x64" , "\x65" , "\x66" , "\x67" , "\x68" , "\x69" , "\x6A" , "\x6B" , "\x6C" , "\x6D" , "\x6E" , "\x6F" ,
402- "\x70" , "\x71" , "\x72" , "\x73" , "\x74" , "\x75" , "\x76" , "\x77" , "\x78" , "\x79" , "\x7A" , "\x7B" , "\x7C" , "\x7D" , "\x7E" , "\x7F" ,
403- "\x80" , "\x81" , "\x82" , "\x83" , "\x84" , "\x85" , "\x86" , "\x87" , "\x88" , "\x89" , "\x8A" , "\x8B" , "\x8C" , "\x8D" , "\x8E" , "\x8F" ,
404- "\x90" , "\x91" , "\x92" , "\x93" , "\x94" , "\x95" , "\x96" , "\x97" , "\x98" , "\x99" , "\x9A" , "\x9B" , "\x9C" , "\x9D" , "\x9E" , "\x9F" ,
405- "\xA0" , "\xA1" , "\xA2" , "\xA3" , "\xA4" , "\xA5" , "\xA6" , "\xA7" , "\xA8" , "\xA9" , "\xAA" , "\xAB" , "\xAC" , "\xAD" , "\xAE" , "\xAF" ,
406- "\xB0" , "\xB1" , "\xB2" , "\xB3" , "\xB4" , "\xB5" , "\xB6" , "\xB7" , "\xB8" , "\xB9" , "\xBA" , "\xBB" , "\xBC" , "\xBD" , "\xBE" , "\xBF" ,
407- "\xC0" , "\xC1" , "\xC2" , "\xC3" , "\xC4" , "\xC5" , "\xC6" , "\xC7" , "\xC8" , "\xC9" , "\xCA" , "\xCB" , "\xCC" , "\xCD" , "\xCE" , "\xCF" ,
408- "\xD0" , "\xD1" , "\xD2" , "\xD3" , "\xD4" , "\xD5" , "\xD6" , "\xD7" , "\xD8" , "\xD9" , "\xDA" , "\xDB" , "\xDC" , "\xDD" , "\xDE" , "\xDF" ,
409- "\xE0" , "\xE1" , "\xE2" , "\xE3" , "\xE4" , "\xE5" , "\xE6" , "\xE7" , "\xE8" , "\xE9" , "\xEA" , "\xEB" , "\xEC" , "\xED" , "\xEE" , "\xEF" ,
410- "\xF0" , "\xF1" , "\xF2" , "\xF3" , "\xF4" , "\xF5" , "\xF6" , "\xF7" , "\xF8" , "\xF9" , "\xFA" , "\xFB" , "\xFC" , "\xFD" , "\xFE" , "\xFF" ,
411- ]; // Lookup for chr($x): much faster.
412-
413- $ imageData = match ($ this ->channels ) {
414- 1 => str_repeat ("\x00" , $ width * $ height ),
415- 2 => str_repeat ("\x00\x00" , $ width * $ height ),
416- 3 => str_repeat ("\x00\x00\x00" , $ width * $ height ),
417- 4 => str_repeat ("\x00\x00\x00\x00" , $ width * $ height ),
418- default => throw new Exception ("Unsupported number of channels: $ this ->channels " ),
419- };
420-
440+ $ imageData = str_repeat ("\x00" , $ width * $ height * $ this ->channels );
441+ $ gdImage = $ this ->image ->getGdResource ();
421442
422443 // Loop over each single pixel.
423444 $ j = 0 ;
424445 for ($ y = 0 ; $ y < $ height ; $ y ++) {
425446 for ($ x = 0 ; $ x < $ width ; $ x ++) {
426447 // Grab the pixel data.
427- $ argb = imagecolorat ($ this -> image -> getGdResource () , $ x , $ y );
448+ $ argb = imagecolorat ($ gdImage , $ x , $ y );
428449
429450 if ($ this ->channels >= 1 ) {
430- $ imageData [$ j ++] = $ chr [($ argb >> 16 ) & 0xFF ]; // R
451+ $ imageData [$ j ++] = self :: CHR [($ argb >> 16 ) & 0xFF ]; // R
431452 }
432453 if ($ this ->channels >= 2 ) {
433- $ imageData [$ j ++] = $ chr [($ argb >> 8 ) & 0xFF ]; // G
454+ $ imageData [$ j ++] = self :: CHR [($ argb >> 8 ) & 0xFF ]; // G
434455 }
435456 if ($ this ->channels >= 3 ) {
436- $ imageData [$ j ++] = $ chr [$ argb & 0xFF ]; // B
457+ $ imageData [$ j ++] = self :: CHR [$ argb & 0xFF ]; // B
437458 }
438459 if ($ this ->channels >= 4 ) {
439- $ imageData [$ j ++] = $ alphaLookup [$ argb & 0x7f000000 ]; // A
460+ $ imageData [$ j ++] = self :: ALPHA_LOOKUP [$ argb & 0x7f000000 ]; // A
440461 }
441462 }
442463 }
0 commit comments