1.10 : changed put_jpeg_header_data to check if the data * being written exists * * URL: http://electronics.ozhiker.com * * Copyright: Copyright Evan Hunter 2004 * * License: This file is part of the PHP JPEG Metadata Toolkit. * * The PHP JPEG Metadata Toolkit is free software; you can * redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your * option) any later version. * * The PHP JPEG Metadata Toolkit is distributed in the hope * that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public * License along with the PHP JPEG Metadata Toolkit; if not, * write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA * * If you require a different license for commercial or other * purposes, please contact the author: evan@ozhiker.com * ******************************************************************************/ /****************************************************************************** * * Function: get_jpeg_header_data * * Description: Reads all the JPEG header segments from an JPEG image file into an * array * * Parameters: filename - the filename of the file to JPEG file to read * * Returns: headerdata - Array of JPEG header segments * FALSE - if headers could not be read * ******************************************************************************/ function get_jpeg_header_data( $filename ) { // prevent refresh from aborting file operations and hosing file ignore_user_abort(true); // Attempt to open the jpeg file - the at symbol supresses the error message about // not being able to open files. The file_exists would have been used, but it // does not work with files fetched over http or ftp. $filehnd = @fopen($filename, 'rb'); // Check if the file opened successfully if ( ! $filehnd ) { // Could't open the file - exit echo "
Could not open file $filename
\n"; return FALSE; } // Read the first two characters $data = network_safe_fread( $filehnd, 2 ); // 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; echo "This probably is not a JPEG file
\n"; fclose($filehnd); return FALSE; } // Read the third character $data = network_safe_fread( $filehnd, 2 ); // 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 fclose($filehnd); return FALSE; } // 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 = 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 = network_safe_fread( $filehnd, $decodedsize['size'] - 2 ); // Store the segment information in the output array $headerdata[] = array( "SegType" => ord($data[1]), "SegName" => $GLOBALS[ "JPEG_Segment_Names" ][ ord($data[1]) ], "SegDesc" => $GLOBALS[ "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 = 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" ) { // NO FF found - close file and return - JPEG is probably corrupted fclose($filehnd); return FALSE; } } } // Close File fclose($filehnd); // Alow the user to abort from now on ignore_user_abort(false); // Return the header data retrieved return $headerdata; } /****************************************************************************** * End of Function: get_jpeg_header_data ******************************************************************************/ /****************************************************************************** * * Function: put_jpeg_header_data * * Description: Writes JPEG header data into a JPEG file. Takes an array in the * same format as from get_jpeg_header_data, and combines it with * the image data of an existing JPEG file, to create a new JPEG file * WARNING: As this function will replace all JPEG headers, * including SOF etc, it is best to read the jpeg headers * from a file, alter them, then put them back on the same * file. If a SOF segment wer to be transfered from one * file to another, the image could become unreadable unless * the images were idenical size and configuration * * * Parameters: old_filename - the JPEG file from which the image data will be retrieved * new_filename - the name of the new JPEG to create (can be same as old_filename) * jpeg_header_data - a JPEG header data array in the same format * as from get_jpeg_header_data * * Returns: TRUE - on Success * FALSE - on Failure * ******************************************************************************/ function put_jpeg_header_data( $old_filename, $new_filename, $jpeg_header_data ) { // Change: added check to ensure data exists, as of revision 1.10 // Check if the data to be written exists if ( $jpeg_header_data == FALSE ) { // Data to be written not valid - abort return FALSE; } // extract the compressed image data from the old file $compressed_image_data = get_jpeg_image_data( $old_filename ); // Check if the extraction worked if ( ( $compressed_image_data === FALSE ) || ( $compressed_image_data === NULL ) ) { // Couldn't get image data from old file return FALSE; } // Cycle through new headers foreach ($jpeg_header_data as $segno => $segment) { // Check that this header is smaller than the maximum size if ( strlen($segment['SegData']) > 0xfffd ) { // Could't open the file - exit echo "A Header is too large to fit in JPEG segment
\n"; return FALSE; } } ignore_user_abort(true); ## prevent refresh from aborting file operations and hosing file // Attempt to open the new jpeg file $newfilehnd = @fopen($new_filename, 'wb'); // Check if the file opened successfully if ( ! $newfilehnd ) { // Could't open the file - exit echo "Could not open file $new_filename
\n"; return FALSE; } // Write SOI fwrite( $newfilehnd, "\xFF\xD8" ); // Cycle through new headers, writing them to the new file foreach ($jpeg_header_data as $segno => $segment) { // Write segment marker fwrite( $newfilehnd, sprintf( "\xFF%c", $segment['SegType'] ) ); // Write segment size fwrite( $newfilehnd, pack( "n", strlen($segment['SegData']) + 2 ) ); // Write segment data fwrite( $newfilehnd, $segment['SegData'] ); } // Write the compressed image data fwrite( $newfilehnd, $compressed_image_data ); // Write EOI fwrite( $newfilehnd, "\xFF\xD9" ); // Close File fclose($newfilehnd); // Alow the user to abort from now on ignore_user_abort(false); return TRUE; } /****************************************************************************** * End of Function: put_jpeg_header_data ******************************************************************************/ /****************************************************************************** * * Function: get_jpeg_Comment * * Description: Retreives the contents of the JPEG Comment (COM = 0xFFFE) segment if one * exists * * Parameters: jpeg_header_data - the JPEG header data, as retrieved * from the get_jpeg_header_data function * * Returns: string - Contents of the Comment segement * FALSE - if the comment segment couldnt be found * ******************************************************************************/ function get_jpeg_Comment( $jpeg_header_data ) { //Cycle through the header segments until COM is found or we run out of segments $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) ) { // A COM segment was found, return it's contents return $jpeg_header_data[$i]['SegData']; } else { // No COM segment found return FALSE; } } /****************************************************************************** * End of Function: get_jpeg_Comment ******************************************************************************/ /****************************************************************************** * * Function: put_jpeg_Comment * * Description: Creates a new JPEG Comment segment from a string, and inserts * this segment into the supplied JPEG header array * * Parameters: jpeg_header_data - a JPEG header data array in the same format * as from get_jpeg_header_data, into which the * new Comment segment will be put * $new_Comment - a string containing the new Comment * * Returns: jpeg_header_data - the JPEG header data array with the new * JPEG Comment segment added * ******************************************************************************/ function put_jpeg_Comment( $jpeg_header_data, $new_Comment ) { //Cycle through the header segments for( $i = 0; $i < count( $jpeg_header_data ); $i++ ) { // If we find an COM header, if ( strcmp ( $jpeg_header_data[$i]['SegName'], "COM" ) == 0 ) { // Found a preexisting Comment block - Replace it with the new one and return. $jpeg_header_data[$i]['SegData'] = $new_Comment; return $jpeg_header_data; } } // No preexisting Comment block found, find where to put it by searching for the highest app segment $i = 0; while ( ( $i < count( $jpeg_header_data ) ) && ( $jpeg_header_data[$i]["SegType"] >= 0xE0 ) ) { $i++; } // insert a Comment segment new at the position found of the header data. array_splice($jpeg_header_data, $i , 0, array( array( "SegType" => 0xFE, "SegName" => $GLOBALS[ "JPEG_Segment_Names" ][ 0xFE ], "SegDesc" => $GLOBALS[ "JPEG_Segment_Descriptions" ][ 0xFE ], "SegData" => $new_Comment ) ) ); return $jpeg_header_data; } /****************************************************************************** * End of Function: put_jpeg_Comment ******************************************************************************/ /****************************************************************************** * * Function: Interpret_Comment_to_HTML * * Description: Generates html showing the contents of any JPEG Comment segment * * Parameters: jpeg_header_data - the JPEG header data, as retrieved * from the get_jpeg_header_data function * * Returns: output - the HTML * ******************************************************************************/ function Interpret_Comment_to_HTML( $jpeg_header_data ) { // Create a string to receive the output $output = ""; // read the comment segment $comment = get_jpeg_Comment( $jpeg_header_data ); // Check if the comment segment was valid if ( $comment !== FALSE ) { // Comment exists - add it to the output $output .= "$comment
\n"; } // Return the result return $output; } /****************************************************************************** * End of Function: Interpret_Comment_to_HTML ******************************************************************************/ /****************************************************************************** * * Function: get_jpeg_intrinsic_values * * Description: Retreives information about the intrinsic characteristics of the * jpeg image, such as Bits per Component, Height and Width. * * Parameters: jpeg_header_data - the JPEG header data, as retrieved * from the get_jpeg_header_data function * * Returns: array - An array containing the intrinsic JPEG values * FALSE - if the comment segment couldnt be found * ******************************************************************************/ function get_jpeg_intrinsic_values( $jpeg_header_data ) { // Create a blank array for the output $Outputarray = array( ); //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 $Outputarray['Bits per Component'] = ord( $data[0] ); // Second and third bytes are Image Height $Outputarray['Image Height'] = ord( $data[1] ) * 256 + ord( $data[2] ); // Forth and fifth bytes are Image Width $Outputarray['Image Width'] = ord( $data[3] ) * 256 + ord( $data[4] ); // 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++ ) { $Outputarray['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 } ) ); } } else { // Couldn't find Start Of Frame segment, hence can't retrieve info return FALSE; } return $Outputarray; } /****************************************************************************** * End of Function: get_jpeg_intrinsic_values ******************************************************************************/ /****************************************************************************** * * Function: Interpret_intrinsic_values_to_HTML * * Description: Generates html showing some of the intrinsic JPEG values which * were retrieved with the get_jpeg_intrinsic_values function * * Parameters: values - the JPEG intrinsic values, as read from get_jpeg_intrinsic_values * * Returns: OutputStr - A string containing the HTML * ******************************************************************************/ function Interpret_intrinsic_values_to_HTML( $values ) { // Check values are valid if ( $values != FALSE ) { // Write Heading $OutputStr = "Image Height | " . $values['Image Height'] . " pixels |
Image Width | " . $values['Image Width'] . " pixels |
Colour Depth | " . $values['Bits per Component'] . " bit Monochrome |
Colour Depth | " . ($values['Bits per Component'] * count( $values['Components'] ) ) . " bit |
$seg_name | " . $jpeg_header['SegName'] . " | " . strlen( $jpeg_header['SegData']). " bytes |