1<?php 2 3/** 4 * Php library to Bake the PNG Images 5 * 6 */ 7class PNGImageBaker 8{ 9 private $_contents; 10 private $_size; 11 private $_chunks; 12 13 /** 14 * Prepares file for handling metadata. 15 * Verifies that this file is a valid PNG file. 16 * Unpacks file chunks and reads them into an array. 17 * 18 * @param string $contents File content as a string 19 */ 20 public function __construct($contents) 21 { 22 $this->_contents = $contents; 23 $png_signature = pack("C8", 137, 80, 78, 71, 13, 10, 26, 10); 24 // Read 8 bytes of PNG header and verify. 25 $header = substr($this->_contents, 0, 8); 26 if ($header != $png_signature) { 27 echo 'This is not a valid PNG image'; 28 } 29 $this->_size = strlen($this->_contents); 30 $this->_chunks = array(); 31 // Skip 8 bytes of IHDR image header. 32 $position = 8; 33 do { 34 $chunk = @unpack('Nsize/a4type', substr($this->_contents, $position, 8)); 35 $this->_chunks[$chunk['type']][] = substr($this->_contents, $position + 8, $chunk['size']); 36 // Skip 12 bytes chunk overhead. 37 $position += $chunk['size'] + 12; 38 } while ($position < $this->_size); 39 } 40 41 /** 42 * Checks if a key already exists in the chunk of said type. 43 * We need to avoid writing same keyword into file chunks. 44 * 45 * @param string $type Chunk type, like iTXt, tEXt, etc. 46 * @param string $check Keyword that needs to be checked. 47 * 48 * @return boolean (true|false) True if file is safe to write this keyword, false otherwise. 49 */ 50 public function checkChunks($type, $check) 51 { 52 if (array_key_exists($type, $this->_chunks)) { 53 foreach (array_keys($this->_chunks[$type]) as $typekey) { 54 list($key, $data) = explode("\0", $this->_chunks[$type][$typekey]); 55 if (strcmp($key, $check) == 0) { 56 echo 'Key "' . $check . '" already exists in "' . $type . '" chunk.'; 57 return false; 58 } 59 } 60 } 61 return true; 62 } 63 64 /** 65 * Add a chunk by type with given key and text 66 * 67 * @param string $chunkType Chunk type, like iTXt, tEXt, etc. 68 * @param string $key Keyword that needs to be added. 69 * @param string $value Currently an assertion URL that is added to an image metadata. 70 * 71 * @return string $result File content with a new chunk as a string. 72 */ 73 public function addChunk($chunkType, $key, $value) 74 { 75 $chunkData = $key . "\0" . $value; 76 $crc = pack("N", crc32($chunkType . $chunkData)); 77 $len = pack("N", strlen($chunkData)); 78 79 $newChunk = $len . $chunkType . $chunkData . $crc; 80 $result = substr($this->_contents, 0, $this->_size - 12) 81 . $newChunk 82 . substr($this->_contents, $this->_size - 12, 12); 83 return $result; 84 } 85 86 /** 87 * removes a chunk by type with given key and text 88 * 89 * @param string $chunkType Chunk type, like iTXt, tEXt, etc. 90 * @param string $key Keyword that needs to be deleted. 91 * @param string $png the png image. 92 * 93 * @return string $result New File content. 94 */ 95 public function removeChunks($chunkType, $key, $png) 96 { 97 // Read the magic bytes and verify 98 $retval = substr($png, 0, 8); 99 $ipos = 8; 100 if ($retval != "\x89PNG\x0d\x0a\x1a\x0a") { 101 throw new Exception('Is not a valid PNG image'); 102 } 103 // Loop through the chunks. Byte 0-3 is length, Byte 4-7 is type 104 $chunkHeader = substr($png, $ipos, 8); 105 $ipos = $ipos + 8; 106 while ($chunkHeader) { 107 // Extract length and type from binary data 108 $chunk = @unpack('Nsize/a4type', $chunkHeader); 109 $skip = false; 110 if ($chunk['type'] == $chunkType) { 111 $data = substr($png, $ipos, $chunk['size']); 112 $sections = explode("\0", $data); 113 print_r($sections); 114 if ($sections[0] == $key) { 115 $skip = true; 116 } 117 } 118 // Extract the data and the CRC 119 $data = substr($png, $ipos, $chunk['size'] + 4); 120 $ipos = $ipos + $chunk['size'] + 4; 121 // Add in the header, data, and CRC 122 if (!$skip) { 123 $retval = $retval . $chunkHeader . $data; 124 } 125 // Read next chunk header 126 $chunkHeader = substr($png, $ipos, 8); 127 $ipos = $ipos + 8; 128 } 129 return $retval; 130 } 131 132 /** 133 * Extracts the baked PNG info by the Key 134 * 135 * @param string $png the png image 136 * @param string $key Keyword that needs to be searched. 137 * 138 * @return mixed - If there is an error - boolean false is returned 139 * If there is PNG information that matches the key an array is returned 140 * 141 */ 142 public function extractBadgeInfo($png, $key = 'openbadges') 143 { 144 // Read the magic bytes and verify 145 $retval = substr($png, 0, 8); 146 $ipos = 8; 147 if ($retval != "\x89PNG\x0d\x0a\x1a\x0a") { 148 return false; 149 } 150 151 // Loop through the chunks. Byte 0-3 is length, Byte 4-7 is type 152 $chunkHeader = substr($png, $ipos, 8); 153 $ipos = $ipos + 8; 154 while ($chunkHeader) { 155 // Extract length and type from binary data 156 $chunk = @unpack('Nsize/a4type', $chunkHeader); 157 $skip = false; 158 if ($chunk['type'] == 'tEXt') { 159 $data = substr($png, $ipos, $chunk['size']); 160 $sections = explode("\0", $data); 161 if ($sections[0] == $key) { 162 return $sections; 163 } 164 } 165 // Extract the data and the CRC 166 $data = substr($png, $ipos, $chunk['size'] + 4); 167 $ipos = $ipos + $chunk['size'] + 4; 168 169 // Read next chunk header 170 $chunkHeader = substr($png, $ipos, 8); 171 $ipos = $ipos + 8; 172 } 173 } 174} 175