<?
class JPEGInformation {
private $JPEG_Segment_Names = array(
0xC0 => "SOF0",
0xC1 => "SOF1",
0xC2 => "SOF2",
0xC3 => "SOF4",
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",
);
private $JPEG_Segment_Descriptions = array(
0xC0 => "Start Of Frame (SOF) Huffman - Baseline DCT",
0xC1 => "Start Of Frame (SOF) Huffman - Extended sequential DCT",
0xC2 => "Start Of Frame Huffman - Progressive DCT (SOF2)",
0xC3 => "Start Of Frame Huffman - Spatial (sequential) lossless (SOF3)",
0xC5 => "Start Of Frame Huffman - Differential sequential DCT (SOF5)",
0xC6 => "Start Of Frame Huffman - Differential progressive DCT (SOF6)",
0xC7 => "Start Of Frame Huffman - Differential spatial (SOF7)",
0xC8 => "Start Of Frame Arithmetic - Reserved for JPEG extensions (JPG)",
0xC9 => "Start Of Frame Arithmetic - Extended sequential DCT (SOF9)",
0xCA => "Start Of Frame Arithmetic - Progressive DCT (SOF10)",
0xCB => "Start Of Frame Arithmetic - Spatial (sequential) lossless (SOF11)",
0xCD => "Start Of Frame Arithmetic - Differential sequential DCT (SOF13)",
0xCE => "Start Of Frame Arithmetic - Differential progressive DCT (SOF14)",
0xCF => "Start Of Frame Arithmetic - Differential spatial (SOF15)",
0xC4 => "Define Huffman Table(s) (DHT)",
0xCC => "Define Arithmetic coding conditioning(s) (DAC)",
0xD0 => "Restart with modulo 8 count 0 (RST0)",
0xD1 => "Restart with modulo 8 count 1 (RST1)",
0xD2 => "Restart with modulo 8 count 2 (RST2)",
0xD3 => "Restart with modulo 8 count 3 (RST3)",
0xD4 => "Restart with modulo 8 count 4 (RST4)",
0xD5 => "Restart with modulo 8 count 5 (RST5)",
0xD6 => "Restart with modulo 8 count 6 (RST6)",
0xD7 => "Restart with modulo 8 count 7 (RST7)",
0xD8 => "Start of Image (SOI)",
0xD9 => "End of Image (EOI)",
0xDA => "Start of Scan (SOS)",
0xDB => "Define quantization Table(s) (DQT)",
0xDC => "Define Number of Lines (DNL)",
0xDD => "Define Restart Interval (DRI)",
0xDE => "Define Hierarchical progression (DHP)",
0xDF => "Expand Reference Component(s) (EXP)",
0xE0 => "Application Field 0 (APP0) - usually JFIF or JFXX",
0xE1 => "Application Field 1 (APP1) - usually EXIF or XMP/RDF",
0xE2 => "Application Field 2 (APP2) - usually Flashpix",
0xE3 => "Application Field 3 (APP3)",
0xE4 => "Application Field 4 (APP4)",
0xE5 => "Application Field 5 (APP5)",
0xE6 => "Application Field 6 (APP6)",
0xE7 => "Application Field 7 (APP7)",
0xE8 => "Application Field 8 (APP8)",
0xE9 => "Application Field 9 (APP9)",
0xEA => "Application Field 10 (APP10)",
0xEB => "Application Field 11 (APP11)",
0xEC => "Application Field 12 (APP12) - usually [picture info]",
0xED => "Application Field 13 (APP13) - usually photoshop IRB / IPTC",
0xEE => "Application Field 14 (APP14)",
0xEF => "Application Field 15 (APP15)",
0xF0 => "Reserved for JPEG extensions (JPG0)",
0xF1 => "Reserved for JPEG extensions (JPG1)",
0xF2 => "Reserved for JPEG extensions (JPG2)",
0xF3 => "Reserved for JPEG extensions (JPG3)",
0xF4 => "Reserved for JPEG extensions (JPG4)",
0xF5 => "Reserved for JPEG extensions (JPG5)",
0xF6 => "Reserved for JPEG extensions (JPG6)",
0xF7 => "Reserved for JPEG extensions (JPG7)",
0xF8 => "Reserved for JPEG extensions (JPG8)",
0xF9 => "Reserved for JPEG extensions (JPG9)",
0xFA => "Reserved for JPEG extensions (JPG10)",
0xFB => "Reserved for JPEG extensions (JPG11)",
0xFC => "Reserved for JPEG extensions (JPG12)",
0xFD => "Reserved for JPEG extensions (JPG13)",
0xFE => "Comment (COM)",
0x01 => "For temp private use arith code (TEM)",
0x02 => "Reserved (RES)",
);
private $filepath;
private $jpeg_app_seg = array();
private $jpeg_intrinsic_values = array();
private $jpeg_comment = null;
private $jpeg_fif = array();
public function __construct($filepath) {
$this->filepath = $filepath;
$this->get_jpeg_header_data();
}
private function get_jpeg_header_data() {
// prevent refresh from aborting file operations and hosing file
ignore_user_abort(true);
$filehnd = @fopen($this->filepath, 'rb');
if (!$filehnd)
{
throw new Exception('Could not open file '.$this->filepath);
}
$data = $this->network_safe_fread($filehnd, 2);
// Check that the first two characters are 0xFF 0xDA (SOI - Start of image)
if ($data != "\xFF\xD8")
{
fclose($filehnd);
throw new Exception('This probably is not a JPEG file');
}
// Read the third character
$data = $this->network_safe_fread($filehnd, 2);
// Check that the third character is 0xFF (Start of first segment header)
if ( $data{0} != "\xFF" )
{
fclose($filehnd);
throw new Exception('JPEG file corrupted');
}
// Flag that we havent yet hit the compressed image data
$hit_compressed_image_data = 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
while (($data{1} != "\xD9") && !$hit_compressed_image_data && !feof($filehnd))
{
// 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
// Read the next two bytes (size)
$sizestr = $this->network_safe_fread($filehnd, 2);
// convert the size bytes to an integer
$decodedsize = unpack ("nsize", $sizestr);
// Save the start position of the data
$segdatastart = ftell($filehnd);
// Read the segment data with length indicated by the previously read size
$segdata = $this->network_safe_fread( $filehnd, $decodedsize['size'] - 2 );
// Store the segment information in the output array
$headerdata[] = array(
"SegType" => ord($data{1}),
"SegName" => $this->JPEG_Segment_Names[ord($data{1})],
"SegDesc" => $this->JPEG_Segment_Descriptions[ord($data{1})],
"SegDataStart" => $segdatastart,
"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" )
{
// Flag that we have hit the compressed image data - exit loop as no more headers available.
$hit_compressed_image_data = TRUE;
}
else
{
// Not an SOS - Read the next two bytes - should be the segment marker for the next segment
$data = $this->network_safe_fread( $filehnd, 2 );
// Check that the first byte of the two is 0xFF as it should be for a marker
if ( $data{0} != "\xFF" )
{
fclose($filehnd);
throw new Exception('JPEG file corrupted');
}
}
}
// Close File
fclose($filehnd);
// Alow the user to abort from now on
ignore_user_abort(false);
// Fill all Arrays
$this->fill_jpeg_app_seg($headerdata);
$this->fill_jpeg_intrinsic_values($headerdata);
$this->fill_jpeg_comment($headerdata);
$this->fill_jpeg_fif($headerdata);
}
private function network_safe_fread($file_handle, $length)
{
$data = "";
while (!feof($file_handle) && (strlen($data) < $length))
{
$data .= fread($file_handle, $length - strlen($data));
}
return $data;
}
private function fill_jpeg_app_seg($jpeg_header_data) {
foreach($jpeg_header_data as $jpeg_header) {
if ($jpeg_header['SegType'] >= 0xE0 && $jpeg_header['SegType'] <= 0xEF) {
$seg_name = strtok($jpeg_header['SegData'], "\x00");
if ($seg_name == 'http://ns.adobe.com/xap/1.0/') {
// http://ns.adobe.com/xap/1.0/
$seg_name = 'XAP/RDF';
}
elseif ($seg_name == 'Photoshop 3.0') {
$seg_name = 'Photoshop IRB (3.0)';
}
elseif (strncmp($seg_name, "[picture info]", 14) == 0 || strncmp ($seg_name, "\x0a\x09\x09\x09\x09[picture info]", 19) == 0) {
$seg_name = '[picture info]';
}
elseif (strncmp($seg_name, "Type=", 5) == 0) {
$seg_name = 'Epson Info';
}
elseif (strncmp($seg_name, "HHHHHHHHHHHHHHH", 15) == 0 || strncmp($seg_name, "@s33", 5) == 0) {
$seg_name = 'HP segment full of "HHHHH"';
}
$this->jpeg_app_seg[$seg_name] = strlen($jpeg_header['SegData']);
}
}
}
public function get_jpeg_app_seg() {
return $this->jpeg_app_seg;
}
private function fill_jpeg_intrinsic_values($jpeg_header_data) {
//Cycle through the header segments until Start Of Frame (SOF) is found or we run out of segments
$i = 0;
while ($i < count($jpeg_header_data) && substr($jpeg_header_data[$i]['SegName'], 0, 3 ) != "SOF") {
$i++;
}
// Check if a SOF segment has been found
if ( substr( $jpeg_header_data[$i]['SegName'], 0, 3 ) == "SOF" ) {
// SOF segment was found, extract the information
$data = $jpeg_header_data[$i]['SegData'];
// First byte is Bits per component
$bpc = ord( $data{0} );
// Forth and fifth bytes are Image Width
$this->jpeg_intrinsic_values['Image Width'] = ord( $data{ 3 } ) * 256 + ord( $data{ 4 } );
// Second and third bytes are Image Height
$this->jpeg_intrinsic_values['Image Height'] = ord( $data{ 1 } ) * 256 + ord( $data{ 2 } );
// Sixth byte is number of components
$numcomponents = ord($data{ 5 });
// Following this is a table containing information about the components
// for($i = 0; $i < $numcomponents; $i++) {
// $this->jpeg_intrinsic_values['Components'][] = array (
// 'Component Identifier' => ord($data{ 6 + $i * 3 }),
// 'Horizontal Sampling Factor' => (ord($data{ 7 + $i * 3 }) & 0xF0 ) / 16,
// 'Vertical Sampling Factor' => (ord($data{ 7 + $i * 3 }) & 0x0F ),
// 'Quantization table destination selector' => ord($data{ 8 + $i * 3 })
// );
// }
if (count($this->jpeg_intrinsic_values['Components']) == 1) {
$this->jpeg_intrinsic_values['Colour Depth'] = $bpc.' Bit Monochrome';
}
else {
$this->jpeg_intrinsic_values['Colour Depth'] = $bpc * $numcomponents.' Bit';
}
}
}
public function jpeg_intrinsic_values() {
return $this->jpeg_intrinsic_values;
}
private function fill_jpeg_comment($jpeg_header_data) {
$i = 0;
while ($i < count($jpeg_header_data) && $jpeg_header_data[$i]['SegName'] != "COM") {
$i++;
}
// Check if a COM segment has been found
if ($i < count($jpeg_header_data)) {
$this->jpeg_comment = $jpeg_header_data[$i]['SegData'];
}
}
public function get_jpeg_comment() {
return $this->jpeg_comment;
}
private function fill_jpeg_fif($jpeg_header_data) {
for($i=0; $i < count($jpeg_header_data); $i++) {
// If we find an APP0 header,
if (strcmp($jpeg_header_data[$i]['SegName'], "APP0") == 0) {
// And if it has the JFIF label,
if(strncmp($jpeg_header_data[$i]['SegData'], "JFIF\x00", 5) == 0)
{
// Found a JPEG File Interchange Format (JFIF) Block
// unpack the JFIF data from the incoming string
// First is the JFIF label string
// Then a two byte version number
// Then a byte, units identifier, ( 0 = aspect ration, 1 = dpi, 2 = dpcm)
// Then a two byte int X-Axis pixel Density (resolution)
// Then a two byte int Y-Axis pixel Density (resolution)
// Then a byte X-Axis JFIF thumbnail size
// Then a byte Y-Axis JFIF thumbnail size
// Then the uncompressed RGB JFIF thumbnail data
$this->jpeg_fif = unpack('a5JFIF/C2Version/CUnits/nXDensity/nYDensity/CThumbX/CThumbY/a*ThumbData', $jpeg_header_data[$i]['SegData']);
}
}
}
}
public function get_jpeg_fif() {
return $this->jpeg_fif;
}
}
?>