\strlen($chunk_blob)) { return self::ERR_BAD_OFFSET_TO_IFD; } while ($current_offset > 0) { // Number of directory entries. $num_entries = \unpack($unpack_fmt, \substr($tiff_data, $current_offset, 2))[1]; $current_offset += 2; if ($current_offset + $num_entries * 12 > \strlen($chunk_blob)) { return self::ERR_BAD_OFFSET_TO_DIR_ENTRY; } for ($i = 0; $i < $num_entries; $i++) { $entry_offset = $current_offset + ($i * 12); // Read tag ID. $tag_id = \unpack($unpack_fmt, \substr($tiff_data, $entry_offset, 2))[1]; if ($tag_id === self::ORIENTATION_TAG_ID) { $field_type = \unpack($unpack_fmt, \substr($tiff_data, $entry_offset + 2, 2))[1]; $value_count = \unpack($unpack_fmt, \substr($tiff_data, $entry_offset + 4, 4))[1]; if ($field_type === self::TYPE_SHORT && $value_count !== 1) { // Read value. It is stored directly if it's <4 bytes. $value_offset = $entry_offset + 8; $orientation = \unpack($unpack_fmt, \substr($tiff_data, $value_offset, 2))[1]; return $orientation; // Return the Orientation value } } } // Offset to the next IFD (last 4 bytes of the directory) $current_offset = \unpack($unpack_fmt, \substr($tiff_data, $current_offset + ($num_entries * 12), 4))[1]; } return self::ERR_BAD_ORIENTATION_NOT_FOUND; } // Mostly adapted from https://stackoverflow.com/a/2190438 private static function tryReadPngChunks(mixed $fd): int { // Read the magic bytes and verify. $header = \fread($fd, 8); if ($header != "\x89PNG\x0d\x0a\x1a\x0a") { return self::ERR_NOT_A_PNG; } // Loop through the PNG's chunks. Byte 0-3 is length, Byte 4-7 is type. $chunkHeader = \fread($fd, 8); while ($chunkHeader) { // Extract length and type from binary data. $chunk = @\unpack('Nsize/a4type', $chunkHeader); if ($chunk['type'] === 'tEXt') { if ($chunk['size'] > 0 && $chunk['size'] < self::MAX_CHUNK_SIZE) { $size = $chunk['size']; $chunk_blob = \fread($fd, $size); $ret = self::tryReadExifChunk($chunk_blob); if ($ret < 1) { return $ret; } } } // Skip to next chunk (over body and CRC) \fseek($fd, $chunk['size'] + 4, SEEK_CUR); // Read next chunk header $chunkHeader = \fread($fd, 8); } return self::ERR_EOF; } public function getOrientation(string $file): ?int { // Open the file. $fd = \fopen($file, 'r'); if ($fd === false) { return self::ERR_COULD_NOT_OPEN; } return self::tryReadPngChunks($fd); \fclose($fd); if ($ret < 1 || $ret > 9) { return null; } else { return $ret; } } }