/** * Get width and height from the bmp header. * * @param File $image * @param string $filename * @return array */ function getImageSize($image, $filename) { $f = fopen($filename, 'rb'); if (!$f) { return false; } $header = fread($f, 54); fclose($f); // Extract binary form of width and height from the header $w = substr($header, 18, 4); $h = substr($header, 22, 4); // Convert the unsigned long 32 bits (little endian): try { $w = wfUnpack('V', $w, 4); $h = wfUnpack('V', $h, 4); } catch (Exception $e) { return false; } return [$w[1], $h[1]]; }
/** * Metadata for a given XCF file * * Will return false if file magic signature is not recognized * @author Hexmode * @author Hashar * * @param string $filename Full path to a XCF file * @return bool|array Metadata Array just like PHP getimagesize() */ static function getXCFMetaData($filename) { # Decode master structure $f = fopen($filename, 'rb'); if (!$f) { return false; } # The image structure always starts at offset 0 in the XCF file. # So we just read it :-) $binaryHeader = fread($f, 26); fclose($f); /** * Master image structure: * * byte[9] "gimp xcf " File type magic * byte[4] version XCF version * "file" - version 0 * "v001" - version 1 * "v002" - version 2 * byte 0 Zero-terminator for version tag * uint32 width With of canvas * uint32 height Height of canvas * uint32 base_type Color mode of the image; one of * 0: RGB color * 1: Grayscale * 2: Indexed color * (enum GimpImageBaseType in libgimpbase/gimpbaseenums.h) */ try { $header = wfUnpack("A9magic" . "/a5version" . "/Nwidth" . "/Nheight" . "/Nbase_type", $binaryHeader); } catch (Exception $mwe) { return false; } # Check values if ($header['magic'] !== 'gimp xcf') { wfDebug(__METHOD__ . " '{$filename}' has invalid magic signature.\n"); return false; } # TODO: we might want to check for sane values of width and height wfDebug(__METHOD__ . ": canvas size of '{$filename}' is {$header['width']} x {$header['height']} px\n"); return $header; }
/** * Metadata for a given XCF file * * Will return false if file magic signature is not recognized * @author Hexmode * @author Hashar * * @param string $filename Full path to a XCF file * @return bool|array metadata array just like PHP getimagesize() */ static function getXCFMetaData($filename) { # Decode master structure $f = fopen($filename, 'rb'); if (!$f) { return false; } # The image structure always starts at offset 0 in the XCF file. # So we just read it :-) $binaryHeader = fread($f, 26); fclose($f); # Master image structure: # # byte[9] "gimp xcf " File type magic # byte[4] version XCF version # "file" - version 0 # "v001" - version 1 # "v002" - version 2 # byte 0 Zero-terminator for version tag # uint32 width With of canvas # uint32 height Height of canvas # uint32 base_type Color mode of the image; one of # 0: RGB color # 1: Grayscale # 2: Indexed color # (enum GimpImageBaseType in libgimpbase/gimpbaseenums.h) try { $header = wfUnpack("A9magic" . "/a5version" . "/Nwidth" . "/Nheight" . "/Nbase_type", $binaryHeader); } catch (MWException $mwe) { return false; } # Check values if ($header['magic'] !== 'gimp xcf') { wfDebug(__METHOD__ . " '{$filename}' has invalid magic signature.\n"); return false; } # TODO: we might want to check for sane values of width and height wfDebug(__METHOD__ . ": canvas size of '{$filename}' is {$header['width']} x {$header['height']} px\n"); # Forge a return array containing metadata information just like getimagesize() # See PHP documentation at: http://www.php.net/getimagesize $metadata = array(); $metadata[0] = $header['width']; $metadata[1] = $header['height']; $metadata[2] = null; # IMAGETYPE constant, none exist for XCF. $metadata[3] = sprintf('height="%s" width="%s"', $header['height'], $header['width']); $metadata['mime'] = 'image/x-xcf'; $metadata['channels'] = null; $metadata['bits'] = 8; # Always 8-bits per color assert('7 == count($metadata); # return array must contains 7 elements just like getimagesize() return'); return $metadata; }
/** * This reads the photoshop image resource. * Currently it only compares the iptc/iim hash * with the stored hash, which is used to determine the precedence * of the iptc data. In future it may extract some other info, like * url of copyright license. * * This should generally be called by BitmapMetadataHandler::doApp13() * * @param string $app13 Photoshop psir app13 block from jpg. * @throws MWException (It gets caught next level up though) * @return string If the iptc hash is good or not. One of 'iptc-no-hash', * 'iptc-good-hash', 'iptc-bad-hash'. */ public static function doPSIR($app13) { if (!$app13) { throw new MWException("No App13 segment given"); } // First compare hash with real thing // 0x404 contains IPTC, 0x425 has hash // This is used to determine if the iptc is newer than // the xmp data, as xmp programs update the hash, // where non-xmp programs don't. $offset = 14; // skip past PHOTOSHOP 3.0 identifier. should already be checked. $appLen = strlen($app13); $realHash = ""; $recordedHash = ""; // the +12 is the length of an empty item. while ($offset + 12 <= $appLen) { $valid = true; if (substr($app13, $offset, 4) !== '8BIM') { // its supposed to be 8BIM // but apparently sometimes isn't esp. in // really old jpg's $valid = false; } $offset += 4; $id = substr($app13, $offset, 2); // id is a 2 byte id number which identifies // the piece of info this record contains. $offset += 2; // some record types can contain a name, which // is a pascal string 0-padded to be an even // number of bytes. Most times (and any time // we care) this is empty, making it two null bytes. $lenName = ord(substr($app13, $offset, 1)) + 1; // we never use the name so skip it. +1 for length byte if ($lenName % 2 == 1) { $lenName++; } // pad to even. $offset += $lenName; // now length of data (unsigned long big endian) $lenData = wfUnpack('Nlen', substr($app13, $offset, 4), 4); // PHP can take issue with very large unsigned ints and make them negative. // Which should never ever happen, as this has to be inside a segment // which is limited to a 16 bit number. if ($lenData['len'] < 0) { throw new MWException("Too big PSIR (" . $lenData['len'] . ')'); } $offset += 4; // 4bytes length field; // this should not happen, but check. if ($lenData['len'] + $offset > $appLen) { throw new MWException("PSIR data too long. (item length=" . $lenData['len'] . "; offset={$offset}; total length={$appLen})"); } if ($valid) { switch ($id) { case "": // IPTC block $realHash = md5(substr($app13, $offset, $lenData['len']), true); break; case "%": $recordedHash = substr($app13, $offset, $lenData['len']); break; } } // if odd, add 1 to length to account for // null pad byte. if ($lenData['len'] % 2 == 1) { $lenData['len']++; } $offset += $lenData['len']; } if (!$realHash || !$recordedHash) { return 'iptc-no-hash'; } elseif ($realHash === $recordedHash) { return 'iptc-good-hash'; } else { /*$realHash !== $recordedHash */ return 'iptc-bad-hash'; } }