<?php if ( ! class_exists( 'Jpeg_Image' ) ) { class Jpeg_Image { private $_JPG_MAX_QUALITY; // The names of the JPEG segment markers, indexed by their marker number private $JPEG_Segment_Names = array( 0xC0 => "SOF0", 0xC1 => "SOF1", 0xC2 => "SOF2", 0xC3 => "SOF3", 0xC5 => "SOF5", 0xC6 => "SOF6", 0xC7 => "SOF7", 0xC8 => "JPG", 0xC9 => "SOF9", 0xCA => "SOF10", 0xCB => "SOF11", 0xCD => "SOF13", 0xCE => "SOF14", 0xCF => "SOF15", 0xC4 => "DHT", 0xCC => "DAC", 0xD0 => "RST0", 0xD1 => "RST1", 0xD2 => "RST2", 0xD3 => "RST3", 0xD4 => "RST4", 0xD5 => "RST5", 0xD6 => "RST6", 0xD7 => "RST7", 0xD8 => "SOI", 0xD9 => "EOI", 0xDA => "SOS", 0xDB => "DQT", 0xDC => "DNL", 0xDD => "DRI", 0xDE => "DHP", 0xDF => "EXP", 0xE0 => "APP0", 0xE1 => "APP1", 0xE2 => "APP2", 0xE3 => "APP3", 0xE4 => "APP4", 0xE5 => "APP5", 0xE6 => "APP6", 0xE7 => "APP7", 0xE8 => "APP8", 0xE9 => "APP9", 0xEA => "APP10", 0xEB => "APP11", 0xEC => "APP12", 0xED => "APP13", 0xEE => "APP14", 0xEF => "APP15", 0xF0 => "JPG0", 0xF1 => "JPG1", 0xF2 => "JPG2", 0xF3 => "JPG3", 0xF4 => "JPG4", 0xF5 => "JPG5", 0xF6 => "JPG6", 0xF7 => "JPG7", 0xF8 => "JPG8", 0xF9 => "JPG9", 0xFA => "JPG10", 0xFB => "JPG11", 0xFC => "JPG12", 0xFD => "JPG13", 0xFE => "COM", 0x01 => "TEM", 0x02 => "RES", ); function __construct() { // This constant should be defined externally to override the default of 100 $this->_JPG_MAX_QUALITY = defined( 'JPG_MAX_QUALITY' ) ? JPG_MAX_QUALITY : 100; } private function get_jpeg_header_data( &$buff, $want=null ) { $data = $this->buffer_read( $buff, 2, true ); // Read the first two characters // Check that the first two characters are 0xFF 0xDA (SOI - Start of image) if ( $data != "\xFF\xD8" ) { // No SOI (FF D8) at start of file - This probably isn't a JPEG file - close file and return; return false; } $data = $this->buffer_read( $buff, 2 ); // Read the third character // Check that the third character is 0xFF (Start of first segment header) if ( $data{0} != "\xFF" ) { // NO FF found - close file and return - JPEG is probably corrupted return false; } // Cycle through the file until, one of: // 1) an EOI (End of image) marker is hit, // 2) we have hit the compressed image data (no more headers are allowed after data) // 3) or end of file is hit $headerdata = array(); while ( ( $data{1} != "\xD9" ) && ( $data != '' ) ) { // Found a segment to look at. // Check that the segment marker is not a Restart marker - restart markers don't have size or data after them if ( ( ord($data{1}) < 0xD0 ) || ( ord($data{1}) > 0xD7 ) ) { // Segment isn't a Restart marker $sizestr = $this->buffer_read( $buff, 2 ); // Read the next two bytes (size) if ( null === $sizestr ) break; $decodedsize = unpack ("nsize", $sizestr); // convert the size bytes to an integer // Read the segment data with length indicated by the previously read size $segdata = $this->buffer_read( $buff, $decodedsize['size'] - 2 ); // Store the segment information in the output array if ( !$want || $want == ord($data{1}) ) { $headerdata[] = (object)array( "SegType" => ord($data{1}), "SegName" => $this->JPEG_Segment_Names[ ord($data{1}) ], "SegData" => $segdata ); } } // If this is a SOS (Start Of Scan) segment, then there is no more header data - the compressed image data follows if ( $data{1} == "\xDA" ) { break; } else { // Not an SOS - Read the next two bytes - should be the segment marker for the next segment $data = $this->buffer_read( $buff, 2 ); // Check that the first byte of the two is 0xFF as it should be for a marker if ( $data{0} != "\xFF" ) { // NO FF found - close file and return - JPEG is probably corrupted return false; } } } return $headerdata; } private function buffer_read( &$buff, $len, $new = false ) { static $pointer = 0; static $total_len = 0; if ( $new ) { $pointer = 0; $total_len = strlen( $buff ); } if ( $pointer + $len > $total_len ) { $len = $total_len - $pointer; if ( $len < 1 ) return null; } $data = substr( $buff, $pointer, $len ); $pointer += $len; return $data; } private function get_value_by_endianness( $data, $little_endian = true ) { $ret_val = 0; if ( $little_endian ) { for ( $i = ( strlen( $data ) - 1 ); $i >= 0; $i-- ) { $ret_val = $ret_val | ord( $data[$i] ); if ( $i > 0 ) $ret_val = $ret_val << 8; } } else { for ( $i = 0; $i < strlen( $data ); $i++ ) { $ret_val = $ret_val | ord( $data[$i] ); if ( $i < ( strlen( $data ) - 1 ) ) $ret_val = $ret_val << 8; } } return $ret_val; } public function get_jpeg_details( &$image_data, $return_value = 'all' ) { $tables = array( 'multi' => array( 'hash' => array( 1020, 1015, 932, 848, 780, 735, 702, 679, 660, 645, 632, 623, 613, 607, 600, 594, 589, 585, 581, 571, 555, 542, 529, 514, 494, 474, 457, 439, 424, 410, 397, 386, 373, 364, 351, 341, 334, 324, 317, 309, 299, 294, 287, 279, 274, 267, 262, 257, 251, 247, 243, 237, 232, 227, 222, 217, 213, 207, 202, 198, 192, 188, 183, 177, 173, 168, 163, 157, 153, 148, 143, 139, 132, 128, 125, 119, 115, 108, 104, 99, 94, 90, 84, 79, 74, 70, 64, 59, 55, 49, 45, 40, 34, 30, 25, 20, 15, 11, 6, 4, 0 ), // hash 'sums' => array ( 32640, 32635, 32266, 31495, 30665, 29804, 29146, 28599, 28104, 27670, 27225, 26725, 26210, 25716, 25240, 24789, 24373, 23946, 23572, 22846, 21801, 20842, 19949, 19121, 18386, 17651, 16998, 16349, 15800, 15247, 14783, 14321, 13859, 13535, 13081, 12702, 12423, 12056, 11779, 11513, 11135, 10955, 10676, 10392, 10208, 9928, 9747, 9564, 9369, 9193, 9017, 8822, 8639, 8458, 8270, 8084, 7896, 7710, 7527, 7347, 7156, 6977, 6788, 6607, 6422, 6236, 6054, 5867, 5684, 5495, 5305, 5128, 4945, 4751, 4638, 4442, 4248, 4065, 3888, 3698, 3509, 3326, 3139, 2957, 2775, 2586, 2405, 2216, 2037, 1846, 1666, 1483, 1297, 1109, 927, 735, 554, 375, 201, 128, 0 ), // sums ), // multi 'single' => array( 'hash' => array( 510, 505, 422, 380, 355, 338, 326, 318, 311, 305, 300, 297, 293, 291, 288, 286, 284, 283, 281, 280, 279, 278, 277, 273, 262, 251, 243, 233, 225, 218, 211, 205, 198, 193, 186, 181, 177, 172, 168, 164, 158, 156, 152, 148, 145, 142, 139, 136, 133, 131, 129, 126, 123, 120, 118, 115, 113, 110, 107, 105, 102, 100, 97, 94, 92, 89, 87, 83, 81, 79, 76, 74, 70, 68, 66, 63, 61, 57, 55, 52, 50, 48, 44, 42, 39, 37, 34, 31, 29, 26, 24, 21, 18, 16, 13, 11, 8, 6, 3, 2, 0 ), // hash 'sums' => array( 16320, 16315, 15946, 15277, 14655, 14073, 13623, 13230, 12859, 12560, 12240, 11861, 11456, 11081, 10714, 10360, 10027, 9679, 9368, 9056, 8680, 8331, 7995, 7668, 7376, 7084, 6823, 6562, 6345, 6125, 5939, 5756, 5571, 5421, 5240, 5086, 4976, 4829, 4719, 4616, 4463, 4393, 4280, 4166, 4092, 3980, 3909, 3835, 3755, 3688, 3621, 3541, 3467, 3396, 3323, 3247, 3170, 3096, 3021, 2952, 2874, 2804, 2727, 2657, 2583, 2509, 2437, 2362, 2290, 2211, 2136, 2068, 1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477, 1398, 1326, 1251, 1179, 1109, 1031, 961, 884, 814, 736, 667, 592, 518, 441, 369, 292, 221, 151, 86, 64, 0 ), // sums ), // single ); // tables $headers = $this->get_jpeg_header_data( $image_data ); $width = 0; $height = 0; $quality = -1; foreach ( (array)$headers as $header ) { if ( ( 'all' == $return_value ) && ( strlen( $header->SegData ) >=4 ) && ( 192 <= $header->SegType && 207 >= $header->SegType ) && ( 196 != $header->SegType && 200 != $header->SegType && 204 != $header->SegType ) ) { $height = ( ord( $header->SegData[1] ) << 8 ) | ord( $header->SegData[2] ); $width = ( ord( $header->SegData[3] ) << 8 ) | ord( $header->SegData[4] ); } if ( ( -1 == $quality ) && ( 'DQT' == $header->SegName ) ) { if ( strlen( $header->SegData ) > 128 ) { $entry = array( 0 => array(), 1 => array() ); foreach ( str_split( substr( $header->SegData, 1, 64) ) as $chr ) $entry[0][] = ord($chr); foreach ( str_split( substr( $header->SegData, -64) ) as $chr ) $entry[1][] = ord($chr); $sum = array_sum( $entry[0] ) + array_sum( $entry[1] ); $qvalue = $entry[0][2] + $entry[0][53] + $entry[1][0] + $entry[1][63]; $table = "multi"; } else if ( strlen( $header->SegData ) > 64 ) { $entry = array( 0 => array() ); foreach ( str_split( substr( $header->SegData, 1, 64) ) as $chr ) $entry[0][] = ord($chr); $sum = array_sum( $entry[0] ); $qvalue = $entry[0][2] + $entry[0][53]; $table = "single"; } else { continue; // go with the safe value } for( $i = 0; $i <= 100; $i++ ) { if ( ( $qvalue < $tables[$table]['hash'][$i] ) && ( $sum < $tables[$table]['sums'][$i] ) ) continue; $quality = min( $i+1, 100 ); break; } } else if ( ( ( -1 != $quality ) && 'quality' == $return_value ) || ( ( -1 != $quality ) && ( 0 != $width ) && ( 0 != $height ) ) ) { // we have what we came for, bail break; } } if ( 'quality' == $return_value ) return ( -1 == $quality ) ? $this->_JPG_MAX_QUALITY : $quality; else return array( 'x'=> $width, 'y'=> $height, 'q'=> ( -1 == $quality ) ? $this->_JPG_MAX_QUALITY : $quality, ); } } // class Jpeg_Image } // class_exists