1.02 : changed get_Photoshop_IRB to work with corrupted * resource names which Photoshop can still read * 1.02 -> 1.03 : Fixed put_Photoshop_IRB to output "Photoshop 3.0\x00" * string with every APP13 segment, not just the first one * 1.03 -> 1.10 : changed get_Photoshop_IRB to fix processing of embedded resource names, * after discovering that Photoshop does not process * resource names according to the standard : * "Adobe Photoshop 6.0 File Formats Specification, Version 6.0, Release 2, November 2000" * This is an update to the change 1.00 -> 1.02, which was not fully correct * changed put_Photoshop_IRB to fix the writing of embedded resource name, * to avoid creating blank resources, and to fix a problem * causing the IRB block to be incorrectly positioned if no APP segments existed. * changed get_Photoshop_IPTC to initialise the output array correctly. * 1.10 -> 1.11 : Moved code out of get_Photoshop_IRB into new function unpack_Photoshop_IRB_Data * to allow reading of IRB blocks embedded within EXIF (for TIFF Files) * Moved code out of put_Photoshop_IRB into new function pack_Photoshop_IRB_Data * to allow writing of IRB blocks embedded within EXIF (for TIFF Files) * Enabled the usage of $GLOBALS['HIDE_UNKNOWN_TAGS'] to hide unknown resources * changed Interpret_IRB_to_HTML to allow thumbnail links to work when * toolkit is portable across directories * * * 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 * ******************************************************************************/ // Change: as of version 1.11 - added to ensure the HIDE_UNKNOWN_TAGS variable is set even if EXIF.php is not included if ( !isset( $GLOBALS['HIDE_UNKNOWN_TAGS'] ) ) $GLOBALS['HIDE_UNKNOWN_TAGS']= FALSE; include_once 'IPTC.php'; include_once 'Unicode.php'; // TODO: Many Photoshop IRB resources not interpeted // TODO: Obtain a copy of the Photoshop CS File Format Specification // TODO: Find out what Photoshop IRB resources 1061, 1062 & 1064 are // TODO: Test get_Photoshop_IRB and put_Photoshop_IRB with multiple APP13 segments /****************************************************************************** * * Function: get_Photoshop_IRB * * Description: Retrieves the Photoshop Information Resource Block (IRB) information * from an App13 JPEG segment and returns it as an array. This may * include IPTC-NAA IIM Information. Uses information * supplied by the get_jpeg_header_data function * * Parameters: jpeg_header_data - a JPEG header data array in the same format * as from get_jpeg_header_data * * Returns: IRBdata - The array of Photoshop IRB records * FALSE - if an APP 13 Photoshop IRB segment could not be found, * or if an error occured * ******************************************************************************/ function get_Photoshop_IRB( $jpeg_header_data ) { // Photoshop Image Resource blocks can span several JPEG APP13 segments, so we need to join them up if there are more than one $joined_IRB = ""; //Cycle through the header segments for( $i = 0; $i < count( $jpeg_header_data ); $i++ ) { // If we find an APP13 header, if ( strcmp ( $jpeg_header_data[$i]['SegName'], "APP13" ) == 0 ) { // And if it has the photoshop label, if( strncmp ( $jpeg_header_data[$i]['SegData'], "Photoshop 3.0\x00", 14) == 0 ) { // join it to the other previous IRB data $joined_IRB .= substr ( $jpeg_header_data[$i]['SegData'], 14 ); } } } // If there was some Photoshop IRB information found, if ( $joined_IRB != "" ) { // Found a Photoshop Image Resource Block - extract it. // Change: Moved code into unpack_Photoshop_IRB_Data to allow TIFF reading as of 1.11 return unpack_Photoshop_IRB_Data( $joined_IRB ); } else { // No Photoshop IRB found return FALSE; } } /****************************************************************************** * End of Function: get_Photoshop_IRB ******************************************************************************/ /****************************************************************************** * * Function: put_Photoshop_IRB * * Description: Adds or modifies the Photoshop Information Resource Block (IRB) * information from an App13 JPEG segment. If a Photoshop IRB already * exists, it is replaced, otherwise a new one is inserted, using the * supplied data. Uses information supplied by the get_jpeg_header_data * function * * Parameters: jpeg_header_data - a JPEG header data array in the same format * as from get_jpeg_header_data * new_IRB_data - an array of the data to be stored in the Photoshop * IRB segment. Should be in the same format as received * from get_Photoshop_IRB * * Returns: jpeg_header_data - the JPEG header data array with the * Photoshop IRB added. * FALSE - if an error occured * ******************************************************************************/ function put_Photoshop_IRB( $jpeg_header_data, $new_IRB_data ) { // Delete all existing Photoshop IRB blocks - the new one will replace them //Cycle through the header segments for( $i = 0; $i < count( $jpeg_header_data ) ; $i++ ) { // If we find an APP13 header, if ( strcmp ( $jpeg_header_data[$i]['SegName'], "APP13" ) == 0 ) { // And if it has the photoshop label, if( strncmp ( $jpeg_header_data[$i]['SegData'], "Photoshop 3.0\x00", 14) == 0 ) { // Delete the block information - it needs to be rebuilt array_splice( $jpeg_header_data, $i, 1 ); } } } // Now we have deleted the pre-existing blocks // Retrieve the Packed Photoshop IRB Data // Change: Moved code into pack_Photoshop_IRB_Data to allow TIFF writing as of 1.11 $packed_IRB_data = pack_Photoshop_IRB_Data( $new_IRB_data ); // Change : This section changed to fix incorrect positioning of IRB segment, as of revision 1.10 // when there are no APP segments present //Cycle through the header segments in reverse order (to find where to put the APP13 block - after any APP0 to APP12 blocks) $i = count( $jpeg_header_data ) - 1; while (( $i >= 0 ) && ( ( $jpeg_header_data[$i]['SegType'] > 0xED ) || ( $jpeg_header_data[$i]['SegType'] < 0xE0 ) ) ) { $i--; } // Cycle through the packed output data until it's size is less than 32000 bytes, outputting each 32000 byte block to an APP13 segment while ( strlen( $packed_IRB_data ) > 32000 ) { // Change: Fixed put_Photoshop_IRB to output "Photoshop 3.0\x00" string with every APP13 segment, not just the first one, as of 1.03 // Write a 32000 byte APP13 segment array_splice($jpeg_header_data, $i +1 , 0, array( "SegType" => 0xED, "SegName" => "APP13", "SegDesc" => $GLOBALS[ "JPEG_Segment_Descriptions" ][ 0xED ], "SegData" => "Photoshop 3.0\x00" . substr( $packed_IRB_data,0,32000) ) ); // Delete the 32000 bytes from the packed output data, that were just output $packed_IRB_data = substr_replace($packed_IRB_data, '', 0, 32000); $i++; } // Write the last block of packed output data to an APP13 segment - Note array_splice doesn't work with multidimensional arrays, hence inserting a blank string array_splice($jpeg_header_data, $i + 1 , 0, "" ); $jpeg_header_data[$i + 1] = array( "SegType" => 0xED, "SegName" => "APP13", "SegDesc" => $GLOBALS[ "JPEG_Segment_Descriptions" ][ 0xED ], "SegData" => "Photoshop 3.0\x00" . $packed_IRB_data ); return $jpeg_header_data; } /****************************************************************************** * End of Function: put_Photoshop_IRB ******************************************************************************/ /****************************************************************************** * * Function: get_Photoshop_IPTC * * Description: Retrieves IPTC-NAA IIM information from within a Photoshop * IRB (if it is present) and returns it in an array. Uses * information supplied by the get_jpeg_header_data function * * Parameters: Photoshop_IRB_data - an array of Photoshop IRB records, as * returned from get_Photoshop_IRB * * Returns: IPTC_Data_Out - The array of IPTC-NAA IIM records * FALSE - if an IPTC-NAA IIM record could not be found, or if * an error occured * ******************************************************************************/ function get_Photoshop_IPTC( $Photoshop_IRB_data ) { // Change: Initialise array correctly, as of revision 1.10 $IPTC_Data_Out = array(); //Cycle through the Photoshop 8BIM records looking for the IPTC-NAA record for( $i = 0; $i < count( $Photoshop_IRB_data ); $i++ ) { // Check if each record is a IPTC record (which has id 0x0404) if ( $Photoshop_IRB_data[$i]['ResID'] == 0x0404 ) { // We've found an IPTC block - Decode it $IPTC_Data_Out = get_IPTC( $Photoshop_IRB_data[$i]['ResData'] ); } } // If there was no records put into the output array, if ( count( $IPTC_Data_Out ) == 0 ) { // Then return false return FALSE; } else { // Otherwise return the array return $IPTC_Data_Out; } } /****************************************************************************** * End of Function: get_Photoshop_IPTC ******************************************************************************/ /****************************************************************************** * * Function: put_Photoshop_IPTC * * Description: Inserts a new IPTC-NAA IIM resource into a Photoshop * IRB, or replaces an the existing resource if one is present. * Uses information supplied by the get_Photoshop_IRB function * * Parameters: Photoshop_IRB_data - an array of Photoshop IRB records, as * returned from get_Photoshop_IRB, into * which the IPTC-NAA IIM record will be inserted * new_IPTC_block - an array of IPTC-NAA records in the same format * as those returned by get_Photoshop_IPTC * * Returns: Photoshop_IRB_data - The Photoshop IRB array with the * IPTC-NAA IIM resource inserted * ******************************************************************************/ function put_Photoshop_IPTC( $Photoshop_IRB_data, $new_IPTC_block ) { $iptc_block_pos = -1; //Cycle through the 8BIM records looking for the IPTC-NAA record for( $i = 0; $i < count( $Photoshop_IRB_data ); $i++ ) { // Check if each record is a IPTC record (which has id 0x0404) if ( $Photoshop_IRB_data[$i]['ResID'] == 0x0404 ) { // We've found an IPTC block - save the position $iptc_block_pos = $i; } } // If no IPTC block was found, create a new one if ( $iptc_block_pos == -1 ) { // New block position will be at the end of the array $iptc_block_pos = count( $Photoshop_IRB_data ); } // Write the new IRB resource to the Photoshop IRB array with no data $Photoshop_IRB_data[$iptc_block_pos] = array( "ResID" => 0x0404, "ResName" => $GLOBALS['Photoshop_ID_Names'][ 0x0404 ], "ResDesc" => $GLOBALS[ "Photoshop_ID_Descriptions" ][ 0x0404 ], "ResEmbeddedName" => "\x00\x00", "ResData" => put_IPTC( $new_IPTC_block ) ); // Return the modified IRB return $Photoshop_IRB_data; } /****************************************************************************** * End of Function: put_Photoshop_IPTC ******************************************************************************/ /****************************************************************************** * * Function: Interpret_IRB_to_HTML * * Description: Generates html showing the information contained in a Photoshop * IRB data array, as retrieved with get_Photoshop_IRB, including * any IPTC-NAA IIM records found. * * Please note that the following resource numbers are not currently * decoded: ( Many of these do not apply to JPEG images) * 0x03E9, 0x03EE, 0x03EF, 0x03F0, 0x03F1, 0x03F2, 0x03F6, 0x03F9, * 0x03FA, 0x03FB, 0x03FD, 0x03FE, 0x0400, 0x0401, 0x0402, 0x0405, * 0x040E, 0x040F, 0x0410, 0x0412, 0x0413, 0x0415, 0x0416, 0x0417, * 0x041B, 0x041C, 0x041D, 0x0BB7 * * ( Also these Obsolete resource numbers) * 0x03E8, 0x03EB, 0x03FC, 0x03FF, 0x0403 * * * Parameters: IRB_array - a Photoshop IRB data array as from get_Photoshop_IRB * filename - the name of the JPEG file being processed ( used * by the script which displays the Photoshop thumbnail) * * * Returns: output_str - the HTML string * ******************************************************************************/ function Interpret_IRB_to_HTML( $IRB_array, $filename ) { // Create a string to receive the HTML $output_str = ""; // Check if the Photoshop IRB array is valid if ( $IRB_array !== FALSE ) { // Create another string to receive secondary HTML to be appended at the end $secondary_output_str = ""; // Add the Heading to the HTML $output_str .= "

Contains Photoshop Information Resource Block (IRB)

"; // Add Table to the HTML $output_str .= "\n"; // Cycle through each of the Photoshop IRB records, creating HTML for each foreach( $IRB_array as $IRB_Resource ) { // Check if the entry is a known Photoshop IRB resource // Get the Name of the Resource if ( array_key_exists( $IRB_Resource['ResID'], $GLOBALS[ "Photoshop_ID_Names" ] ) ) { $Resource_Name = $GLOBALS['Photoshop_ID_Names'][ $IRB_Resource['ResID'] ]; } else { // Change: Added check for $GLOBALS['HIDE_UNKNOWN_TAGS'] to allow hiding of unknown resources as of 1.11 if ( $GLOBALS['HIDE_UNKNOWN_TAGS'] == TRUE ) { continue; } else { // Unknown Resource - Make appropriate name $Resource_Name = "Unknown Resource (". $IRB_Resource['ResID'] .")"; } } // Add HTML for the resource as appropriate switch ( $IRB_Resource['ResID'] ) { case 0x0404 : // IPTC-NAA IIM Record $secondary_output_str .= Interpret_IPTC_to_HTML( get_IPTC( $IRB_Resource['ResData'] ) ); break; case 0x040B : // URL $output_str .= "\n"; break; case 0x040A : // Copyright Marked if ( hexdec( bin2hex( $IRB_Resource['ResData'] ) ) == 1 ) { $output_str .= "\n"; } else { $output_str .= "\n"; } break; case 0x040D : // Global Lighting Angle $output_str .= "\n"; break; case 0x0419 : // Global Altitude $output_str .= "\n"; break; case 0x0421 : // Version Info $output_str .= "\n"; break; case 0x0411 : // ICC Untagged if ( $IRB_Resource['ResData'] == "\x01" ) { $output_str .= "\n"; } else { $output_str .= "\n"; } break; case 0x041A : // Slices $output_str .= "\n"; break; case 0x0408 : // Grid and Guides information $output_str .= "\n"; case 0x0406 : // JPEG Quality $Qual_Info = unpack("nQuality/nFormat/nScans/Cconst", $IRB_Resource['ResData'] ); $output_str .= "\n"; break; case 0x0409 : // Thumbnail Resource case 0x040C : // Thumbnail Resource $thumb_data = unpack("NFormat/NWidth/NHeight/NWidthBytes/NSize/NCompressedSize/nBitsPixel/nPlanes", $IRB_Resource['ResData'] ); $output_str .= "\n"; break; case 0x0414 : // Document Specific ID's $output_str .= "\n"; break; case 0x041E : // URL List $URL_count = hexdec( bin2hex( substr( $IRB_Resource['ResData'], 0, 4 ) ) ); $output_str .= "\n"; break; case 0x03F4 : // Grayscale and multichannel halftoning information. $output_str .= "\n"; break; case 0x03F5 : // Color halftoning information $output_str .= "\n"; break; case 0x03F7 : // Grayscale and multichannel transfer function. $output_str .= "\n"; break; case 0x03F8 : // Color transfer functions $output_str .= "\n"; break; case 0x03F3 : // Print Flags $output_str .= "\n"; break; case 0x2710 : // Print Flags Information $PrintFlags = unpack( "nVersion/CCentCrop/Cjunk/NBleedWidth/nBleedWidthScale", $IRB_Resource['ResData'] ); $output_str .= "\n"; break; case 0x03ED : // Resolution Info $ResInfo = unpack( "nhRes_int/nhResdec/nhResUnit/nwidthUnit/nvRes_int/nvResdec/nvResUnit/nheightUnit", $IRB_Resource['ResData'] ); $output_str .= "\n"; break; default : // All other records $output_str .= "\n"; } } // Add the table end to the HTML $output_str .= "
$Resource_Name" . htmlentities( $IRB_Resource['ResData'] ) ."
$Resource_Name
Image is Copyrighted Material
$Resource_Name
Image is Not Copyrighted Material
$Resource_Name
Global lighting angle for effects layer = " . hexdec( bin2hex( $IRB_Resource['ResData'] ) ) . " degrees
$Resource_Name
Global Altitude = " . hexdec( bin2hex( $IRB_Resource['ResData'] ) ) . "
$Resource_Name
\n";
                                        $output_str .= "Version = " . hexdec( bin2hex( substr( $IRB_Resource['ResData'], 0, 4 ) ) ) . "\n";
                                        $output_str .= "Has Real Merged Data = " . ord( $IRB_Resource['ResData']{4} ) . "\n";
                                        $writer_size = hexdec( bin2hex( substr( $IRB_Resource['ResData'], 5, 4 ) ) ) * 2;

                                        $output_str .= "Writer Name = " . HTML_UTF16_Escape( substr( $IRB_Resource['ResData'], 9, $writer_size ), TRUE ) . "\n";
                                        $reader_size = hexdec( bin2hex( substr( $IRB_Resource['ResData'], 9 + $writer_size , 4 ) ) ) * 2;
                                        $output_str .= "Reader Name = " . HTML_UTF16_Escape( substr( $IRB_Resource['ResData'], 13 + $writer_size, $reader_size ), TRUE ) . "\n";
                                        $output_str .= "File Version = " . hexdec( bin2hex( substr( $IRB_Resource['ResData'], 13 + $writer_size + $reader_size, 4 ) ) ) . "\n";
                                        $output_str .=  "
$Resource_Name
Intentionally untagged - any assumed ICC profile handling disabled
$Resource_Name
Unknown value (0x" .bin2hex( $IRB_Resource['ResData'] ). ")
$Resource_Name"; // Unpack the first 24 bytes $Slices_Info = unpack("NVersion/NBound_top/NBound_left/NBound_bottom/NBound_right/NStringlen", $IRB_Resource['ResData'] ); $output_str .= "Version = " . $Slices_Info['Version'] . "
\n"; $output_str .= "Bounding Rectangle = Top:" . $Slices_Info['Bound_top'] . ", Left:" . $Slices_Info['Bound_left'] . ", Bottom:" . $Slices_Info['Bound_bottom'] . ", Right:" . $Slices_Info['Bound_right'] . " (Pixels)
\n"; $Slicepos = 24; // Extract a Unicode String $output_str .= "Text = '" . HTML_UTF16_Escape( substr( $IRB_Resource['ResData'], 24, $Slices_Info['Stringlen']*2), TRUE ) . "'
\n"; $Slicepos += $Slices_Info['Stringlen'] * 2; // Unpack the number of Slices $Num_Slices = hexdec( bin2hex( substr( $IRB_Resource['ResData'], $Slicepos, 4 ) ) ); $output_str .= "Number of Slices = " . $Num_Slices . "\n"; $Slicepos += 4; // Cycle through the slices for( $i = 1; $i <= $Num_Slices; $i++ ) { $output_str .= "

Slice $i:
\n"; // Unpack the first 16 bytes of the slice $SliceA = unpack("NID/NGroupID/NOrigin/NStringlen", substr($IRB_Resource['ResData'], $Slicepos ) ); $Slicepos += 16; $output_str .= "ID = " . $SliceA['ID'] . "
\n"; $output_str .= "Group ID = " . $SliceA['GroupID'] . "
\n"; $output_str .= "Origin = " . $SliceA['Origin'] . "
\n"; // Extract a Unicode String $output_str .= "Text = '" . HTML_UTF16_Escape( substr( $IRB_Resource['ResData'], $Slicepos, $SliceA['Stringlen']*2), TRUE ) . "'
\n"; $Slicepos += $SliceA['Stringlen'] * 2; // Unpack the next 24 bytes of the slice $SliceB = unpack("NType/NLeftPos/NTopPos/NRightPos/NBottomPos/NURLlen", substr($IRB_Resource['ResData'], $Slicepos ) ); $Slicepos += 24; $output_str .= "Type = " . $SliceB['Type'] . "
\n"; $output_str .= "Position = Top:" . $SliceB['TopPos'] . ", Left:" . $SliceB['LeftPos'] . ", Bottom:" . $SliceB['BottomPos'] . ", Right:" . $SliceB['RightPos'] . " (Pixels)
\n"; // Extract a Unicode String $output_str .= "URL = " . HTML_UTF16_Escape( substr( $IRB_Resource['ResData'], $Slicepos, $SliceB['URLlen']*2), TRUE ) . "
\n"; $Slicepos += $SliceB['URLlen'] * 2; // Unpack the length of a Unicode String $Targetlen = hexdec( bin2hex( substr( $IRB_Resource['ResData'], $Slicepos, 4 ) ) ); $Slicepos += 4; // Extract a Unicode String $output_str .= "Target = '" . HTML_UTF16_Escape( substr( $IRB_Resource['ResData'], $Slicepos, $Targetlen*2), TRUE ) . "'
\n"; $Slicepos += $Targetlen * 2; // Unpack the length of a Unicode String $Messagelen = hexdec( bin2hex( substr( $IRB_Resource['ResData'], $Slicepos, 4 ) ) ); $Slicepos += 4; // Extract a Unicode String $output_str .= "Message = '" . HTML_UTF16_Escape( substr( $IRB_Resource['ResData'], $Slicepos, $Messagelen*2), TRUE ) . "'
\n"; $Slicepos += $Messagelen * 2; // Unpack the length of a Unicode String $AltTaglen = hexdec( bin2hex( substr( $IRB_Resource['ResData'], $Slicepos, 4 ) ) ); $Slicepos += 4; // Extract a Unicode String $output_str .= "Alt Tag = '" . HTML_UTF16_Escape( substr( $IRB_Resource['ResData'], $Slicepos, $AltTaglen*2), TRUE ) . "'
\n"; $Slicepos += $AltTaglen * 2; // Unpack the HTML flag if ( ord( $IRB_Resource['ResData']{ $Slicepos } ) === 0x01 ) { $output_str .= "Cell Text is HTML
\n"; } else { $output_str .= "Cell Text is NOT HTML
\n"; } $Slicepos++; // Unpack the length of a Unicode String $CellTextlen = hexdec( bin2hex( substr( $IRB_Resource['ResData'], $Slicepos, 4 ) ) ); $Slicepos += 4; // Extract a Unicode String $output_str .= "Cell Text = '" . HTML_UTF16_Escape( substr( $IRB_Resource['ResData'], $Slicepos, $CellTextlen*2), TRUE ) . "'
\n"; $Slicepos += $CellTextlen * 2; // Unpack the last 12 bytes of the slice $SliceC = unpack("NAlignH/NAlignV/CAlpha/CRed/CGreen/CBlue", substr($IRB_Resource['ResData'], $Slicepos ) ); $Slicepos += 12; $output_str .= "Alignment = Horizontal:" . $SliceC['AlignH'] . ", Vertical:" . $SliceC['AlignV'] . "
\n"; $output_str .= "Alpha Colour = " . $SliceC['Alpha'] . "
\n"; $output_str .= "Red = " . $SliceC['Red'] . "
\n"; $output_str .= "Green = " . $SliceC['Green'] . "
\n"; $output_str .= "Blue = " . $SliceC['Blue'] . "\n"; } $output_str .= "
$Resource_Name"; // Unpack the Grids info $Grid_Info = unpack("NVersion/NGridCycleH/NGridCycleV/NGuideCount", $IRB_Resource['ResData'] ); $output_str .= "Version = " . $Grid_Info['Version'] . "
\n"; $output_str .= "Grid Cycle = " . $Grid_Info['GridCycleH']/32 . " Pixel(s) x " . $Grid_Info['GridCycleV']/32 . " Pixel(s)
\n"; $output_str .= "Number of Guides = " . $Grid_Info['GuideCount'] . "\n"; // Cycle through the Guides for( $i = 0; $i < $Grid_Info['GuideCount']; $i++ ) { // Unpack the info for this guide $Guide_Info = unpack("NLocation/CDirection", substr($IRB_Resource['ResData'],16+$i*5,5) ); $output_str .= "
Guide $i : Location = " . $Guide_Info['Location']/32 . " Pixel(s) from edge"; if ( $Guide_Info['Direction'] === 0 ) { $output_str .= ", Vertical\n"; } else { $output_str .= ", Horizontal\n"; } } break; $output_str .= "
$Resource_Name"; switch ( $Qual_Info['Quality'] ) { case 0xFFFD: $output_str .= "Quality 1 (Low)
\n"; break; case 0xFFFE: $output_str .= "Quality 2 (Low)
\n"; break; case 0xFFFF: $output_str .= "Quality 3 (Low)
\n"; break; case 0x0000: $output_str .= "Quality 4 (Low)
\n"; break; case 0x0001: $output_str .= "Quality 5 (Medium)
\n"; break; case 0x0002: $output_str .= "Quality 6 (Medium)
\n"; break; case 0x0003: $output_str .= "Quality 7 (Medium)
\n"; break; case 0x0004: $output_str .= "Quality 8 (High)
\n"; break; case 0x0005: $output_str .= "Quality 9 (High)
\n"; break; case 0x0006: $output_str .= "Quality 10 (Maximum)
\n"; break; case 0x0007: $output_str .= "Quality 11 (Maximum)
\n"; break; case 0x0008: $output_str .= "Quality 12 (Maximum)
\n"; break; default: $output_str .= "Unknown Quality (" . $Qual_Info['Quality'] . ")
\n"; break; } switch ( $Qual_Info['Format'] ) { case 0x0000: $output_str .= "Standard Format\n"; break; case 0x0001: $output_str .= "Optimised Format\n"; break; case 0x0101: $output_str .= "Progressive Format
\n"; break; default: $output_str .= "Unknown Format (" . $Qual_Info['Format'] .")\n"; break; } if ( $Qual_Info['Format'] == 0x0101 ) { switch ( $Qual_Info['Scans'] ) { case 0x0001: $output_str .= "3 Scans\n"; break; case 0x0002: $output_str .= "4 Scans\n"; break; case 0x0003: $output_str .= "5 Scans\n"; break; default: $output_str .= "Unknown number of scans (" . $Qual_Info['Scans'] .")\n"; break; } } $output_str .= "
$Resource_Name
\n";
                                        $output_str .= "Format = " . (( $thumb_data['Format'] == 1 ) ? "JPEG RGB\n" :  "Raw RGB\n");
                                        $output_str .= "Width = " . $thumb_data['Width'] . "\n";
                                        $output_str .= "Height = " . $thumb_data['Height'] . "\n";
                                        $output_str .= "Padded Row Bytes = " . $thumb_data['WidthBytes'] . " bytes\n";
                                        $output_str .= "Total Size = " . $thumb_data['Size'] . " bytes\n";
                                        $output_str .= "Compressed Size = " . $thumb_data['CompressedSize'] . " bytes\n";
                                        $output_str .= "Bits per Pixel = " . $thumb_data['BitsPixel'] . " bits\n";
                                        $output_str .= "Number of planes = " . $thumb_data['Planes'] . " bytes\n";

                                        // Change: as of version 1.11 - Changed to make thumbnail link portable across directories
                                        // Build the path of the thumbnail script and its filename parameter to put in a url
                                        $link_str = get_relative_path( dirname(__FILE__) . "/get_ps_thumb.php" , getcwd ( ) );
                                        $link_str .= "?filename=";
                                        $link_str .= get_relative_path( $filename, dirname(__FILE__) );

                                        // Add thumbnail link to html
                                        $output_str .= "Thumbnail Data:
\n"; $output_str .= "
$Resource_Name
" . hexdec( bin2hex( $IRB_Resource['ResData'] ) ) . "
$Resource_Name\n"; $output_str .= "$URL_count URL's in list
\n"; $urlstr = substr( $IRB_Resource['ResData'], 4 ); // TODO: Check if URL List in Photoshop IRB works for( $i = 0; $i < $URL_count; $i++ ) { $url_data = unpack( "NLong/NID/NURLSize", $urlstr ); $output_str .= "URL $i info: long = " . $url_data['Long'] .", "; $output_str .= "ID = " . $url_data['ID'] . ", "; $urlstr = substr( $urlstr, 12 ); $url = substr( $urlstr, 0, $url_data['URLSize'] ); $output_str .= "URL = " . HTML_UTF16_Escape( $url, TRUE ) . "
\n"; } $output_str .= "
$Resource_Name
\n";
                                        $output_str .= Interpret_Halftone( $IRB_Resource['ResData'] );
                                        $output_str .= "
$Resource_Name
\n";
                                        $output_str .= "Cyan Halftoning Info:\n" . Interpret_Halftone( substr( $IRB_Resource['ResData'], 0, 18 ) ) . "\n\n";
                                        $output_str .= "Magenta Halftoning Info:\n" . Interpret_Halftone( substr( $IRB_Resource['ResData'], 18, 18 ) ) . "\n\n";
                                        $output_str .= "Yellow Halftoning Info:\n" . Interpret_Halftone( substr( $IRB_Resource['ResData'], 36, 18 ) ) . "\n";
                                        $output_str .= "Black Halftoning Info:\n" . Interpret_Halftone( substr( $IRB_Resource['ResData'], 54, 18 ) ) . "\n";
                                        $output_str .= "
$Resource_Name
\n";
                                        $output_str .= Interpret_Transfer_Function( substr( $IRB_Resource['ResData'], 0, 28 ) ) ;
                                        $output_str .= "
$Resource_Name
\n";
                                        $output_str .= "Red Transfer Function:   \n" . Interpret_Transfer_Function( substr( $IRB_Resource['ResData'], 0, 28 ) ) . "\n\n";
                                        $output_str .= "Green Transfer Function: \n" . Interpret_Transfer_Function( substr( $IRB_Resource['ResData'], 28, 28 ) ) . "\n\n";
                                        $output_str .= "Blue Transfer Function:  \n" . Interpret_Transfer_Function( substr( $IRB_Resource['ResData'], 56, 28 ) ) . "\n";
                                        $output_str .= "
$Resource_Name
\n";
                                        if ( $IRB_Resource['ResData']{0} == "\x01" )
                                        {
                                                $output_str .= "Labels Selected\n";
                                        }
                                        else
                                        {
                                                $output_str .= "Labels Not Selected\n";
                                        }
                                        if ( $IRB_Resource['ResData']{1} == "\x01" )
                                        {
                                                $output_str .= "Crop Marks Selected\n";
                                        }
                                        else
                                        {
                                                $output_str .= "Crop Marks Not Selected\n";
                                        }
                                        if ( $IRB_Resource['ResData']{2} == "\x01" )
                                        {
                                                $output_str .= "Color Bars Selected\n";
                                        }
                                        else
                                        {
                                                $output_str .= "Color Bars Not Selected\n";
                                        }
                                        if ( $IRB_Resource['ResData']{3} == "\x01" )
                                        {
                                                $output_str .= "Registration Marks Selected\n";
                                        }
                                        else
                                        {
                                                $output_str .= "Registration Marks Not Selected\n";
                                        }
                                        if ( $IRB_Resource['ResData']{4} == "\x01" )
                                        {
                                                $output_str .= "Negative Selected\n";
                                        }
                                        else
                                        {
                                                $output_str .= "Negative Not Selected\n";
                                        }
                                        if ( $IRB_Resource['ResData']{5} == "\x01" )
                                        {
                                                $output_str .= "Flip Selected\n";
                                        }
                                        else
                                        {
                                                $output_str .= "Flip Not Selected\n";
                                        }
                                        if ( $IRB_Resource['ResData']{6} == "\x01" )
                                        {
                                                $output_str .= "Interpolate Selected\n";
                                        }
                                        else
                                        {
                                                $output_str .= "Interpolate Not Selected\n";
                                        }
                                        if ( $IRB_Resource['ResData']{7} == "\x01" )
                                        {
                                                $output_str .= "Caption Selected";
                                        }
                                        else
                                        {
                                                $output_str .= "Caption Not Selected";
                                        }
                                        $output_str .= "
$Resource_Name
\n";
                                        $output_str .= "Version = " . $PrintFlags['Version'] . "\n";
                                        $output_str .= "Centre Crop Marks = " . $PrintFlags['CentCrop'] . "\n";
                                        $output_str .= "Bleed Width = " . $PrintFlags['BleedWidth'] . "\n";
                                        $output_str .= "Bleed Width Scale = " . $PrintFlags['BleedWidthScale'];
                                        $output_str .= "
$Resource_Name
\n";
                                        $output_str .= "Horizontal Resolution = " . ($ResInfo['hRes_int'] + $ResInfo['hResdec']/65536) . " pixels per Inch\n";
                                        $output_str .= "Vertical Resolution = " . ($ResInfo['vRes_int'] + $ResInfo['vResdec']/65536) . " pixels per Inch\n";
                                        if ( $ResInfo['hResUnit'] == 1 )
                                        {
                                                $output_str .= "Display units for Horizontal Resolution = Pixels per Inch\n";
                                        }
                                        elseif ( $ResInfo['hResUnit'] == 2 )
                                        {
                                                $output_str .= "Display units for Horizontal Resolution = Pixels per Centimetre\n";
                                        }
                                        else
                                        {
                                                $output_str .= "Display units for Horizontal Resolution = Unknown Value (". $ResInfo['hResUnit'] .")\n";
                                        }

                                        if ( $ResInfo['vResUnit'] == 1 )
                                        {
                                                $output_str .= "Display units for Vertical Resolution = Pixels per Inch\n";
                                        }
                                        elseif ( $ResInfo['vResUnit'] == 2 )
                                        {
                                                $output_str .= "Display units for Vertical Resolution = Pixels per Centimetre\n";
                                        }
                                        else
                                        {
                                                $output_str .= "Display units for Vertical Resolution = Unknown Value (". $ResInfo['vResUnit'] .")\n";
                                        }

                                        if ( $ResInfo['widthUnit'] == 1 )
                                        {
                                                $output_str .= "Display units for Image Width = Inches\n";
                                        }
                                        elseif ( $ResInfo['widthUnit'] == 2 )
                                        {
                                                $output_str .= "Display units for Image Width = Centimetres\n";
                                        }
                                        elseif ( $ResInfo['widthUnit'] == 3 )
                                        {
                                                $output_str .= "Display units for Image Width = Points\n";
                                        }
                                        elseif ( $ResInfo['widthUnit'] == 4 )
                                        {
                                                $output_str .= "Display units for Image Width = Picas\n";
                                        }
                                        elseif ( $ResInfo['widthUnit'] == 5 )
                                        {
                                                $output_str .= "Display units for Image Width = Columns\n";
                                        }
                                        else
                                        {
                                                $output_str .= "Display units for Image Width = Unknown Value (". $ResInfo['widthUnit'] .")\n";
                                        }

                                        if ( $ResInfo['heightUnit'] == 1 )
                                        {
                                                $output_str .= "Display units for Image Height = Inches";
                                        }
                                        elseif ( $ResInfo['heightUnit'] == 2 )
                                        {
                                                $output_str .= "Display units for Image Height = Centimetres";
                                        }
                                        elseif ( $ResInfo['heightUnit'] == 3 )
                                        {
                                                $output_str .= "Display units for Image Height = Points";
                                        }
                                        elseif ( $ResInfo['heightUnit'] == 4 )
                                        {
                                                $output_str .= "Display units for Image Height = Picas";
                                        }
                                        elseif ( $ResInfo['heightUnit'] == 5 )
                                        {
                                                $output_str .= "Display units for Image Height = Columns";
                                        }
                                        else
                                        {
                                                $output_str .= "Display units for Image Height = Unknown Value (". $ResInfo['heightUnit'] .")";
                                        }
                                        $output_str .= "
$Resource_NameRESOURCE DECODING NOT IMPLEMENTED YET
" . strlen( $IRB_Resource['ResData'] ) . " bytes
\n"; // Add any secondary output to the HTML $output_str .= $secondary_output_str; } // Return the HTML return $output_str; } /****************************************************************************** * End of Function: Interpret_IRB_to_HTML ******************************************************************************/ /****************************************************************************** * * INTERNAL FUNCTIONS * ******************************************************************************/ /****************************************************************************** * * Function: unpack_Photoshop_IRB_Data * * Description: Extracts Photoshop Information Resource Block (IRB) information * from a binary string containing the IRB, as read from a file * * Parameters: IRB_Data - The binary string containing the IRB * * Returns: IRBdata - The array of Photoshop IRB records * ******************************************************************************/ function unpack_Photoshop_IRB_Data( $IRB_Data ) { $pos = 0; // Cycle through the IRB and extract its records - Records are started with 8BIM, so cycle until no more instances of 8BIM can be found while ( ( $pos < strlen( $IRB_Data ) ) && ( ($pos = strpos( $IRB_Data, "8BIM", $pos) ) !== FALSE ) ) { // Skip the position over the 8BIM characters $pos += 4; // Next two characters are the record ID - denoting what type of record it is. $ID = ord( $IRB_Data{ $pos } ) * 256 + ord( $IRB_Data{ $pos +1 } ); // Skip the positionover the two record ID characters $pos += 2; // Next comes a Record Name - usually not used, but it should be a null terminated string, padded with 0x00 to be an even length $namestartpos = $pos; // Change: Fixed processing of embedded resource names, as of revision 1.10 // NOTE: Photoshop does not process resource names according to the standard : // "Adobe Photoshop 6.0 File Formats Specification, Version 6.0, Release 2, November 2000" // // The resource name is actually formatted as follows: // One byte name length, followed by the null terminated ascii name string. // The field is then padded with a Null character if required, to ensure that the // total length of the name length and name is even. // Name - process it // Get the length $namelen = ord ( $IRB_Data{ $namestartpos } ); // Total length of name and length info must be even, hence name length must be odd // Check if the name length is even, if ( $namelen % 2 == 0 ) { // add one to length to make it odd $namelen ++; } // Extract the name $resembeddedname = trim( substr ( $IRB_Data, $namestartpos+1, $namelen) ); $pos += $namelen + 1; // Next is a four byte size field indicating the size in bytes of the record's data - MSB first $datasize = ord( $IRB_Data{ $pos } ) * 16777216 + ord( $IRB_Data{ $pos + 1 } ) * 65536 + ord( $IRB_Data{ $pos + 2 } ) * 256 + ord( $IRB_Data{ $pos + 3 } ); $pos += 4; // The record is stored padded with 0x00 characters to make the size even, so we need to calculate the stored size $storedsize = $datasize + ($datasize % 2); $resdata = substr ( $IRB_Data, $pos, $datasize ); // Get the description for this resource // Check if this is a Path information Resource, since they have a range of ID's if ( ( $ID >= 0x07D0 ) && ( $ID <= 0x0BB6 ) ) { $ResDesc = "ID Info : Path Information (saved paths)."; } else { if ( array_key_exists( $ID, $GLOBALS[ "Photoshop_ID_Descriptions" ] ) ) { $ResDesc = $GLOBALS[ "Photoshop_ID_Descriptions" ][ $ID ]; } else { $ResDesc = ""; } } // Get the Name of the Resource if ( array_key_exists( $ID, $GLOBALS[ "Photoshop_ID_Names" ] ) ) { $ResName = $GLOBALS['Photoshop_ID_Names'][ $ID ]; } else { $ResName = ""; } // Store the Resource in the array to be returned $IRB_Array[] = array( "ResID" => $ID, "ResName" => $ResName, "ResDesc" => $ResDesc, "ResEmbeddedName" => $resembeddedname, "ResData" => $resdata ); // Jump over the data to the next record $pos += $storedsize; } // Return the array created return $IRB_Array; } /****************************************************************************** * End of Function: unpack_Photoshop_IRB_Data ******************************************************************************/ /****************************************************************************** * * Function: pack_Photoshop_IRB_Data * * Description: Packs a Photoshop Information Resource Block (IRB) array into it's * binary form, which can be written to a file * * Parameters: IRB_data - an Photoshop IRB array to be converted. Should be in * the same format as received from get_Photoshop_IRB * * Returns: packed_IRB_data - the binary string of packed IRB data * ******************************************************************************/ function pack_Photoshop_IRB_Data( $IRB_data ) { $packed_IRB_data = ""; // Cycle through each resource in the IRB, foreach ($IRB_data as $resource) { // Change: Fix to avoid creating blank resources, as of revision 1.10 // Check if there is actually any data for this resource if( strlen( $resource['ResData'] ) == 0 ) { // No data for resource - skip it continue; } // Append the 8BIM tag, and resource ID to the packed output data $packed_IRB_data .= pack("a4n", "8BIM", $resource['ResID'] ); // Change: Fixed processing of embedded resource names, as of revision 1.10 // NOTE: Photoshop does not process resource names according to the standard : // "Adobe Photoshop 6.0 File Formats Specification, Version 6.0, Release 2, November 2000" // // The resource name is actually formatted as follows: // One byte name length, followed by the null terminated ascii name string. // The field is then padded with a Null character if required, to ensure that the // total length of the name length and name is even. // Append Name Size $packed_IRB_data .= pack( "c", strlen(trim($resource['ResEmbeddedName']))); // Append the Resource Name to the packed output data $packed_IRB_data .= trim($resource['ResEmbeddedName']); // If the resource name is even length, then with the addition of // the size it becomes odd and needs to be padded to an even number if ( strlen( trim($resource['ResEmbeddedName']) ) % 2 == 0 ) { // then it needs to be evened up by appending another null $packed_IRB_data .= "\x00"; } // Append the resource data size to the packed output data $packed_IRB_data .= pack("N", strlen( $resource['ResData'] ) ); // Append the resource data to the packed output data $packed_IRB_data .= $resource['ResData']; // If the resource data is odd length, if ( strlen( $resource['ResData'] ) % 2 == 1 ) { // then it needs to be evened up by appending another null $packed_IRB_data .= "\x00"; } } // Return the packed data string return $packed_IRB_data; } /****************************************************************************** * End of Function: pack_Photoshop_IRB_Data ******************************************************************************/ /****************************************************************************** * * Internal Function: Interpret_Transfer_Function * * Description: Used by Interpret_IRB_to_HTML to interpret Color transfer functions * for Photoshop IRB resource 0x03F8. Converts the transfer function * information to a human readable version. * * Parameters: Transfer_Function_Binary - a 28 byte Ink curves structure string * * Returns: output_str - the text string containing the transfer function * information * ******************************************************************************/ function Interpret_Transfer_Function( $Transfer_Function_Binary ) { // Unpack the Transfer function information $Trans_vals = unpack ( "n13Curve/nOverride", $Transfer_Function_Binary ); $output_str = "Transfer Function Points: "; // Cycle through each of the Transfer function array values foreach ( $Trans_vals as $Key => $val ) { // Check if the value should be negative if ($val > 32768 ) { // Value should be negative - make it so $val = $val - 65536; } // Check that the Override item is not getting in this list, and // that the value is not -1, which means ignored if ( ( $Key != "Override" ) && ( $val != -1 ) ) { // This is a valid transfer function point, output it $output_str .= $val/10 . "%, "; } } // Output the override info if ( $Trans_vals['Override'] == 0 ) { $output_str .= "\nOverride: Let printer supply curve"; } else { $output_str .= "\nOverride: Override printer’s default transfer curve"; } // Return the result return $output_str; } /****************************************************************************** * End of Function: Interpret_Transfer_Function ******************************************************************************/ /****************************************************************************** * * Internal Function: Interpret_Halftone * * Description: Used by Interpret_IRB_to_HTML to interpret Color halftoning information * for Photoshop IRB resource 0x03F5. Converts the halftoning info * to a human readable version. * * Parameters: Transfer_Function_Binary - a 18 byte Halftone screen parameter & structure string * * Returns: output_str - the text string containing the transfer function * information * ******************************************************************************/ function Interpret_Halftone( $Halftone_Binary ) { // Create a string to receive the output $output_str = ""; // Unpack the binary data into an array $HalftoneInfo = unpack( "nFreqVal_int/nFreqVal_dec/nFreqScale/nAngle_int/nAngle_dec/nShapeCode/NMisc/CAccurate/CDefault", $Halftone_Binary ); // Interpret Ink Screen Frequency $output_str .= "Ink Screen Frequency = " . ($HalftoneInfo['FreqVal_int'] + $HalftoneInfo['FreqVal_dec']/65536) . " lines per Inch\n"; if ( $HalftoneInfo['FreqScale'] == 1 ) { $output_str .= "Display units for Ink Screen Frequency = Inches\n"; } else { $output_str .= "Display units for Ink Screen Frequency = Centimetres\n"; } // Interpret Angle for screen $output_str .= "Angle for screen = " . ($HalftoneInfo['Angle_int'] + $HalftoneInfo['Angle_dec']/65536) . " degrees\n"; // Interpret Shape of Halftone Dots if ($HalftoneInfo['ShapeCode'] > 32768 ) { $HalftoneInfo['ShapeCode'] = $HalftoneInfo['ShapeCode'] - 65536; } if ( $HalftoneInfo['ShapeCode'] == 0 ) { $output_str .= "Shape of Halftone Dots = Round\n"; } elseif ( $HalftoneInfo['ShapeCode'] == 1 ) { $output_str .= "Shape of Halftone Dots = Ellipse\n"; } elseif ( $HalftoneInfo['ShapeCode'] == 2 ) { $output_str .= "Shape of Halftone Dots = Line\n"; } elseif ( $HalftoneInfo['ShapeCode'] == 3 ) { $output_str .= "Shape of Halftone Dots = Square\n"; } elseif ( $HalftoneInfo['ShapeCode'] == 4 ) { $output_str .= "Shape of Halftone Dots = Cross\n"; } elseif ( $HalftoneInfo['ShapeCode'] == 6 ) { $output_str .= "Shape of Halftone Dots = Diamond\n"; } else { $output_str .= "Shape of Halftone Dots = Unknown shape (" . $HalftoneInfo['ShapeCode'] . ")\n"; } // Interpret Accurate Screens if ( $HalftoneInfo['Accurate'] == 1 ) { $output_str .= "Use Accurate Screens Selected\n"; } else { $output_str .= "Use Other (not Accurate) Screens Selected\n"; } // Interpret Printer Default Screens if ( $HalftoneInfo['Default'] == 1 ) { $output_str .= "Use printer’s default screens\n"; } else { $output_str .= "Use Other (not Printer Default) Screens Selected\n"; } // Return Text return $output_str; } /****************************************************************************** * End of Global Variable: Interpret_Halftone ******************************************************************************/ /****************************************************************************** * Global Variable: Photoshop_ID_Names * * Contents: The Names of the Photoshop IRB resources, indexed by their * resource number * ******************************************************************************/ $GLOBALS[ "Photoshop_ID_Names" ] = array( 0x03E8 => "Number of channels, rows, columns, depth, and mode. (Obsolete)", 0x03E9 => "Macintosh print manager info ", 0x03EB => "Indexed color table (Obsolete)", 0x03ED => "Resolution Info", 0x03EE => "Alpha Channel Names", 0x03EF => "Display Info", 0x03F0 => "Caption String", 0x03F1 => "Border information", 0x03F2 => "Background color", 0x03F3 => "Print flags", 0x03F4 => "Grayscale and multichannel halftoning information", 0x03F5 => "Color halftoning information", 0x03F6 => "Duotone halftoning information", 0x03F7 => "Grayscale and multichannel transfer function", 0x03F8 => "Color transfer functions", 0x03F9 => "Duotone transfer functions", 0x03FA => "Duotone image information", 0x03FB => "Black and white values", 0x03FC => "Obsolete Resource.", 0x03FD => "EPS options", 0x03FE => "Quick Mask information", 0x03FF => "Obsolete Resource", 0x0400 => "Layer state information", 0x0401 => "Working path (not saved)", 0x0402 => "Layers group information", 0x0403 => "Obsolete Resource", 0x0404 => "IPTC-NAA record", 0x0405 => "Raw Format Image mode", 0x0406 => "JPEG quality", 0x0408 => "Grid and guides information", 0x0409 => "Thumbnail resource", 0x040A => "Copyright flag", 0x040B => "URL", 0x040C => "Thumbnail resource", 0x040D => "Global Angle", 0x040E => "Color samplers resource", 0x040F => "ICC Profile", 0x0410 => "Watermark", 0x0411 => "ICC Untagged", 0x0412 => "Effects visible", 0x0413 => "Spot Halftone", 0x0414 => "Document Specific IDs", 0x0415 => "Unicode Alpha Names", 0x0416 => "Indexed Color Table Count", 0x0417 => "Tansparent Index. Index of transparent color, if any.", 0x0419 => "Global Altitude", 0x041A => "Slices", 0x041B => "Workflow URL", 0x041C => "Jump To XPEP", 0x041D => "Alpha Identifiers", 0x041E => "URL List", 0x0421 => "Version Info", 0x0BB7 => "Name of clipping path.", 0x2710 => "Print flags information" ); /****************************************************************************** * End of Global Variable: Photoshop_ID_Names ******************************************************************************/ /****************************************************************************** * Global Variable: Photoshop_ID_Descriptions * * Contents: The Descriptions of the Photoshop IRB resources, indexed by their * resource number * ******************************************************************************/ $GLOBALS[ "Photoshop_ID_Descriptions" ] = array( 0x03E8 => "Obsolete—Photoshop 2.0 only. number of channels, rows, columns, depth, and mode.", 0x03E9 => "Optional. Macintosh print manager print info record.", 0x03EB => "Obsolete—Photoshop 2.0 only. Contains the indexed color table.", 0x03ED => "ResolutionInfo structure. See Appendix A in Photoshop SDK Guide.pdf", 0x03EE => "Names of the alpha channels as a series of Pascal strings.", 0x03EF => "DisplayInfo structure. See Appendix A in Photoshop SDK Guide.pdf", 0x03F0 => "Optional. The caption as a Pascal string.", 0x03F1 => "Border information. border width, border units", 0x03F2 => "Background color.", 0x03F3 => "Print flags. labels, crop marks, color bars, registration marks, negative, flip, interpolate, caption.", 0x03F4 => "Grayscale and multichannel halftoning information.", 0x03F5 => "Color halftoning information.", 0x03F6 => "Duotone halftoning information.", 0x03F7 => "Grayscale and multichannel transfer function.", 0x03F8 => "Color transfer functions.", 0x03F9 => "Duotone transfer functions.", 0x03FA => "Duotone image information.", 0x03FB => "Effective black and white values for the dot range.", 0x03FC => "Obsolete Resource.", 0x03FD => "EPS options.", 0x03FE => "Quick Mask information. Quick Mask channel ID, Mask initially empty.", 0x03FF => "Obsolete Resource.", 0x0400 => "Layer state information. Index of target layer.", 0x0401 => "Working path (not saved).", 0x0402 => "Layers group information. Group ID for the dragging groups. Layers in a group have the same group ID.", 0x0403 => "Obsolete Resource.", 0x0404 => "IPTC-NAA record. This contains the File Info... information. See the IIMV4.pdf document.", 0x0405 => "Image mode for raw format files.", 0x0406 => "JPEG quality. Private.", 0x0408 => "Grid and guides information.", 0x0409 => "Thumbnail resource.", 0x040A => "Copyright flag. Boolean indicating whether image is copyrighted. Can be set via Property suite or by user in File Info...", 0x040B => "URL. Handle of a text string with uniform resource locator. Can be set via Property suite or by user in File Info...", 0x040C => "Thumbnail resource.", 0x040D => "Global Angle. Global lighting angle for effects layer.", 0x040E => "Color samplers resource.", 0x040F => "ICC Profile. The raw bytes of an ICC format profile, see the ICC34.pdf and ICC34.h files from the Internation Color Consortium.", 0x0410 => "Watermark.", 0x0411 => "ICC Untagged. Disables any assumed profile handling when opening the file. 1 = intentionally untagged.", 0x0412 => "Effects visible. Show/hide all the effects layer.", 0x0413 => "Spot Halftone. Version, length, variable length data.", 0x0414 => "Document specific IDs for layer identification", 0x0415 => "Unicode Alpha Names. Length and the string", 0x0416 => "Indexed Color Table Count. Number of colors in table that are actually defined", 0x0417 => "Transparent Index. Index of transparent color, if any.", 0x0419 => "Global Altitude.", 0x041A => "Slices.", 0x041B => "Workflow URL. Length, string.", 0x041C => "Jump To XPEP. Major version, Minor version, Count. Table which can include: Dirty flag, Mod date.", 0x041D => "Alpha Identifiers.", 0x041E => "URL List. Count of URLs, IDs, and strings", 0x0421 => "Version Info. Version, HasRealMergedData, string of writer name, string of reader name, file version.", 0x0BB7 => "Name of clipping path.", 0x2710 => "Print flags information. Version, Center crop marks, Bleed width value, Bleed width scale." ); /****************************************************************************** * End of Global Variable: Photoshop_ID_Descriptions ******************************************************************************/ ?> 1.10 : added function get_EXIF_TIFF to allow extracting EXIF from a TIFF file * 1.10 -> 1.11 : added functionality to allow decoding of XMP and Photoshop IRB information * embedded within the EXIF data * added checks for http and ftp wrappers, as these are not supported * changed interpret_IFD to allow thumbnail links to work when * toolkit is portable across directories * * * 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 * ******************************************************************************/ // TODO : Thoroughly test the functions for writing EXIF segments // TODO : Figure out a way to allow EXIF to function normally with HTTP and FTP wrappers // TODO : Implement EXIF decoding of Device Setting Description field // TODO : Implement EXIF decoding of SpatialFrequencyResponse field // TODO : Implement EXIF decoding of OECF field // TODO : Implement EXIF decoding of SubjectArea field // TODO : Add a put_EXIF_TIFF function /****************************************************************************** * * Initialisation * ******************************************************************************/ if ( !isset( $GLOBALS['HIDE_UNKNOWN_TAGS'] ) ) $GLOBALS['HIDE_UNKNOWN_TAGS']= FALSE; if ( !isset( $GLOBALS['SHOW_BINARY_DATA_HEX'] ) ) $GLOBALS['SHOW_BINARY_DATA_HEX'] = FALSE; if ( !isset( $GLOBALS['SHOW_BINARY_DATA_TEXT'] ) ) $GLOBALS['SHOW_BINARY_DATA_TEXT'] = FALSE; include_once 'EXIF_Tags.php'; include_once 'EXIF_Makernote.php'; include_once 'PIM.php'; include_once 'Unicode.php'; include_once 'JPEG.php'; include_once 'IPTC.php'; include_once 'Photoshop_IRB.php'; // Change: as of version 1.11 - Required for TIFF with embedded IRB include_once 'XMP.php'; // Change: as of version 1.11 - Required for TIFF with embedded XMP include_once 'pjmt_utils.php'; // Change: as of version 1.11 - Required for directory portability /****************************************************************************** * * Function: get_EXIF_JPEG * * Description: Retrieves information from a Exchangeable Image File Format (EXIF) * APP1 segment and returns it in an array. * * Parameters: filename - the filename of the JPEG image to process * * Returns: OutputArray - Array of EXIF records * FALSE - If an error occured in decoding * ******************************************************************************/ function get_EXIF_JPEG( $filename ) { // Change: Added as of version 1.11 // Check if a wrapper is being used - these are not currently supported (see notes at top of file) if ( ( stristr ( $filename, "http://" ) != FALSE ) || ( stristr ( $filename, "ftp://" ) != FALSE ) ) { // A HTTP or FTP wrapper is being used - show a warning and abort echo "HTTP and FTP wrappers are currently not supported with EXIF - See EXIF functionality documentation - a local file must be specified
"; echo "To work on an internet file, copy it locally to start with:

\n"; echo "\$newfilename = tempnam ( \$dir, \"tmpexif\" );
\n"; echo "copy ( \"http://whatever.com\", \$newfilename );

\n"; return FALSE; } // get the JPEG headers $jpeg_header_data = get_jpeg_header_data( $filename ); // Flag that an EXIF segment has not been found yet $EXIF_Location = -1; //Cycle through the header segments for( $i = 0; $i < count( $jpeg_header_data ); $i++ ) { // If we find an APP1 header, if ( strcmp ( $jpeg_header_data[$i]['SegName'], "APP1" ) == 0 ) { // And if it has the EXIF label, if ( ( strncmp ( $jpeg_header_data[$i]['SegData'], "Exif\x00\x00", 6) == 0 ) || ( strncmp ( $jpeg_header_data[$i]['SegData'], "Exif\x00\xFF", 6) == 0 ) ) // For some reason, some files have a faulty EXIF name which has a 0xFF in it { // Save the location of the EXIF segment $EXIF_Location = $i; } } } // Check if an EXIF segment was found if ( $EXIF_Location == -1 ) { // Couldn't find any EXIF block to decode return FALSE; } $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; } fseek( $filehnd, $jpeg_header_data[$EXIF_Location]['SegDataStart'] + 6 ); // Decode the Exif segment into an array and return it $exif_data = process_TIFF_Header( $filehnd, "TIFF" ); // Close File fclose($filehnd); return $exif_data; } /****************************************************************************** * End of Function: get_EXIF_JPEG ******************************************************************************/ /****************************************************************************** * * Function: put_EXIF_JPEG * * Description: Stores information into a Exchangeable Image File Format (EXIF) * APP1 segment from an EXIF array. * * WARNING: Because the EXIF standard allows pointers to data * outside the APP1 segment, if there are any such pointers in * a makernote, this function will DAMAGE them since it will not * be aware that there is an external pointer. This will often * happen with Makernotes that include an embedded thumbnail. * This damage could be prevented where makernotes can be decoded, * but currently this is not implemented. * * * Parameters: exif_data - The array of EXIF data to insert into the JPEG header * jpeg_header_data - The JPEG header into which the EXIF data * should be stored, as from get_jpeg_header_data * * Returns: jpeg_header_data - JPEG header array with the EXIF segment inserted * FALSE - If an error occured * ******************************************************************************/ function put_EXIF_JPEG( $exif_data, $jpeg_header_data ) { // pack the EXIF data into its proper format for a JPEG file $packed_data = get_TIFF_Packed_Data( $exif_data ); if ( $packed_data === FALSE ) { return $jpeg_header_data; } $packed_data = "Exif\x00\x00$packed_data"; //Cycle through the header segments for( $i = 0; $i < count( $jpeg_header_data ); $i++ ) { // If we find an APP1 header, if ( strcmp ( $jpeg_header_data[$i]['SegName'], "APP1" ) == 0 ) { // And if it has the EXIF label, if ( ( strncmp ( $jpeg_header_data[$i]['SegData'], "Exif\x00\x00", 6) == 0 ) || ( strncmp ( $jpeg_header_data[$i]['SegData'], "Exif\x00\xFF", 6) == 0 ) ) // For some reason, some files have a faulty EXIF name which has a 0xFF in it { // Found a preexisting EXIF block - Replace it with the new one and return. $jpeg_header_data[$i]['SegData'] = $packed_data; return $jpeg_header_data; } } } // No preexisting segment segment found, insert a new one at the start of the header data. // Determine highest position of an APP segment at or below APP3, so we can put the // new APP3 at this position $highest_APP = -1; //Cycle through the header segments for( $i = 0; $i < count( $jpeg_header_data ); $i++ ) { // Check if we have found an APP segment at or below APP3, if ( ( $jpeg_header_data[$i]['SegType'] >= 0xE0 ) && ( $jpeg_header_data[$i]['SegType'] <= 0xE3 ) ) { // Found an APP segment at or below APP12 $highest_APP = $i; } } // No preexisting EXIF block found, insert a new one at the start of the header data. array_splice($jpeg_header_data, $highest_APP + 1 , 0, array( array( "SegType" => 0xE1, "SegName" => "APP1", "SegDesc" => $GLOBALS[ "JPEG_Segment_Descriptions" ][ 0xE1 ], "SegData" => $packed_data ) ) ); return $jpeg_header_data; } /****************************************************************************** * End of Function: put_EXIF_JPEG ******************************************************************************/ /****************************************************************************** * * Function: get_Meta_JPEG * * Description: Retrieves information from a Meta APP3 segment and returns it * in an array. Uses information supplied by the * get_jpeg_header_data function. * The Meta segment has the same format as an EXIF segment, but * uses different tags * * Parameters: filename - the filename of the JPEG image to process * * Returns: OutputArray - Array of Meta records * FALSE - If an error occured in decoding * ******************************************************************************/ function get_Meta_JPEG( $filename ) { // Change: Added as of version 1.11 // Check if a wrapper is being used - these are not currently supported (see notes at top of file) if ( ( stristr ( $filename, "http://" ) != FALSE ) || ( stristr ( $filename, "ftp://" ) != FALSE ) ) { // A HTTP or FTP wrapper is being used - show a warning and abort echo "HTTP and FTP wrappers are currently not supported with Meta - See EXIF/Meta functionality documentation - a local file must be specified
"; echo "To work on an internet file, copy it locally to start with:

\n"; echo "\$newfilename = tempnam ( \$dir, \"tmpmeta\" );
\n"; echo "copy ( \"http://whatever.com\", \$newfilename );

\n"; return FALSE; } // get the JPEG headers $jpeg_header_data = get_jpeg_header_data( $filename ); // Flag that an Meta segment has not been found yet $Meta_Location = -1; //Cycle through the header segments for( $i = 0; $i < count( $jpeg_header_data ); $i++ ) { // If we find an APP3 header, if ( strcmp ( $jpeg_header_data[$i]['SegName'], "APP3" ) == 0 ) { // And if it has the Meta label, if ( ( strncmp ( $jpeg_header_data[$i]['SegData'], "Meta\x00\x00", 6) == 0 ) || ( strncmp ( $jpeg_header_data[$i]['SegData'], "META\x00\x00", 6) == 0 ) ) { // Save the location of the Meta segment $Meta_Location = $i; } } } // Check if an EXIF segment was found if ( $Meta_Location == -1 ) { // Couldn't find any Meta block to decode return FALSE; } $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; } fseek( $filehnd, $jpeg_header_data[$Meta_Location]['SegDataStart'] + 6 ); // Decode the Meta segment into an array and return it $meta = process_TIFF_Header( $filehnd, "Meta" ); // Close File fclose($filehnd); return $meta; } /****************************************************************************** * End of Function: get_Meta ******************************************************************************/ /****************************************************************************** * * Function: put_Meta_JPEG * * Description: Stores information into a Meta APP3 segment from a Meta array. * * * WARNING: Because the Meta (EXIF) standard allows pointers to data * outside the APP1 segment, if there are any such pointers in * a makernote, this function will DAMAGE them since it will not * be aware that there is an external pointer. This will often * happen with Makernotes that include an embedded thumbnail. * This damage could be prevented where makernotes can be decoded, * but currently this is not implemented. * * * Parameters: meta_data - The array of Meta data to insert into the JPEG header * jpeg_header_data - The JPEG header into which the Meta data * should be stored, as from get_jpeg_header_data * * Returns: jpeg_header_data - JPEG header array with the Meta segment inserted * FALSE - If an error occured * ******************************************************************************/ function put_Meta_JPEG( $meta_data, $jpeg_header_data ) { // pack the Meta data into its proper format for a JPEG file $packed_data = get_TIFF_Packed_Data( $meta_data ); if ( $packed_data === FALSE ) { return $jpeg_header_data; } $packed_data = "Meta\x00\x00$packed_data"; //Cycle through the header segments for( $i = 0; $i < count( $jpeg_header_data ); $i++ ) { // If we find an APP1 header, if ( strcmp ( $jpeg_header_data[$i]['SegName'], "APP3" ) == 0 ) { // And if it has the Meta label, if ( ( strncmp ( $jpeg_header_data[$i]['SegData'], "Meta\x00\x00", 6) == 0 ) || ( strncmp ( $jpeg_header_data[$i]['SegData'], "META\x00\x00", 6) == 0 ) ) { // Found a preexisting Meta block - Replace it with the new one and return. $jpeg_header_data[$i]['SegData'] = $packed_data; return $jpeg_header_data; } } } // No preexisting segment segment found, insert a new one at the start of the header data. // Determine highest position of an APP segment at or below APP3, so we can put the // new APP3 at this position $highest_APP = -1; //Cycle through the header segments for( $i = 0; $i < count( $jpeg_header_data ); $i++ ) { // Check if we have found an APP segment at or below APP3, if ( ( $jpeg_header_data[$i]['SegType'] >= 0xE0 ) && ( $jpeg_header_data[$i]['SegType'] <= 0xE3 ) ) { // Found an APP segment at or below APP12 $highest_APP = $i; } } // No preexisting Meta block found, insert a new one at the start of the header data. array_splice($jpeg_header_data, $highest_APP + 1 , 0, array( array( "SegType" => 0xE3, "SegName" => "APP3", "SegDesc" => $GLOBALS[ "JPEG_Segment_Descriptions" ][ 0xE1 ], "SegData" => $packed_data ) ) ); return $jpeg_header_data; } /****************************************************************************** * End of Function: put_Meta_JPEG ******************************************************************************/ /****************************************************************************** * * Function: get_EXIF_TIFF * * Description: Retrieves information from a Exchangeable Image File Format (EXIF) * within a TIFF file and returns it in an array. * * Parameters: filename - the filename of the TIFF image to process * * Returns: OutputArray - Array of EXIF records * FALSE - If an error occured in decoding * ******************************************************************************/ function get_EXIF_TIFF( $filename ) { // Change: Added as of version 1.11 // Check if a wrapper is being used - these are not currently supported (see notes at top of file) if ( ( stristr ( $filename, "http://" ) != FALSE ) || ( stristr ( $filename, "ftp://" ) != FALSE ) ) { // A HTTP or FTP wrapper is being used - show a warning and abort echo "HTTP and FTP wrappers are currently not supported with TIFF - See EXIF/TIFF functionality documentation - a local file must be specified
"; echo "To work on an internet file, copy it locally to start with:

\n"; echo "\$newfilename = tempnam ( \$dir, \"tmptiff\" );
\n"; echo "copy ( \"http://whatever.com\", \$newfilename );

\n"; return FALSE; } $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; } // Decode the Exif segment into an array and return it $exif_data = process_TIFF_Header( $filehnd, "TIFF" ); // Close File fclose($filehnd); return $exif_data; } /****************************************************************************** * End of Function: get_EXIF_TIFF ******************************************************************************/ /****************************************************************************** * * Function: Interpret_EXIF_to_HTML * * Description: Generates html detailing the contents an APP1 EXIF array * which was retrieved with a get_EXIF_.... function. * Can also be used for APP3 Meta arrays. * * Parameters: Exif_array - the EXIF array,as read from get_EXIF_.... * filename - the name of the Image file being processed ( used * by scripts which displays EXIF thumbnails) * * Returns: output_str - A string containing the HTML * ******************************************************************************/ function Interpret_EXIF_to_HTML( $Exif_array, $filename ) { // Create the string to receive the html output $output_str = ""; // Check if the array to process is valid if ( $Exif_array === FALSE ) { // Exif Array is not valid - abort processing return $output_str; } // Ouput the heading according to what type of tags were used in processing if ( $Exif_array[ 'Tags Name' ] == "TIFF" ) { $output_str .= "

Contains Exchangeable Image File Format (EXIF) Information

\n"; } else if ( $Exif_array[ 'Tags Name' ] == "Meta" ) { $output_str .= "

Contains META Information (APP3)

\n"; } else { $output_str .= "

Contains " . $Exif_array[ 'Tags Name' ] . " Information

\n"; } // Check that there are actually items to process in the array if ( count( $Exif_array ) < 1 ) { // No items to process in array - abort processing return $output_str; } // Output secondary heading $output_str .= "

Main Image Information

\n"; // Interpret the zeroth IFD to html $output_str .= interpret_IFD( $Exif_array[0], $filename, $Exif_array['Byte_Align'] ); // Check if there is a first IFD to process if ( array_key_exists( 1, $Exif_array ) ) { // There is a first IFD for a thumbnail // Add a heading for it to the output $output_str .= "

Thumbnail Information

\n"; // Interpret the IFD to html and add it to the output $output_str .= interpret_IFD( $Exif_array[1], $filename, $Exif_array['Byte_Align'] ); } // Cycle through any other IFD's $i = 2; while ( array_key_exists( $i, $Exif_array ) ) { // Add a heading for the IFD $output_str .= "

Image File Directory (IFD) $i Information

\n"; // Interpret the IFD to html and add it to the output $output_str .= interpret_IFD( $Exif_array[$i], $filename, $Exif_array['Byte_Align'] ); $i++; } // Return the resulting HTML return $output_str; } /****************************************************************************** * End of Function: Interpret_EXIF_to_HTML ******************************************************************************/ /****************************************************************************** * * INTERNAL FUNCTIONS * ******************************************************************************/ /****************************************************************************** * * Internal Function: get_TIFF_Packed_Data * * Description: Packs TIFF IFD data from EXIF or Meta into a form ready for * either a JPEG EXIF/Meta segment or a TIFF file * This function attempts to protect the contents of an EXIF makernote, * by ensuring that it remains in the same position relative to the * TIFF header * * Parameters: tiff_data - the EXIF array,as read from get_EXIF_JPEG or get_Meta_JPEG * * Returns: packed_data - A string containing packed segment * ******************************************************************************/ function get_TIFF_Packed_Data( $tiff_data ) { // Check that the segment is valid if ( $tiff_data === FALSE ) { return FALSE; } // Get the byte alignment $Byte_Align = $tiff_data['Byte_Align']; // Add the Byte Alignment to the Packed data $packed_data = $Byte_Align; // Add the TIFF ID to the Packed Data $packed_data .= put_IFD_Data_Type( 42, 3, $Byte_Align ); // Create a string for the makernote $makernote = ""; // Check if the makernote exists if ( $tiff_data[ 'Makernote_Tag' ] !== FALSE ) { // A makernote exists - We need to ensure that it stays in the same position as it was // Put the Makernote before any of the IFD's by padding zeros to the correct offset $makernote .= str_repeat("\x00",( $tiff_data[ 'Makernote_Tag' ][ 'Offset' ] - 8 ) ); $makernote .= $tiff_data[ 'Makernote_Tag' ]['Data']; } // Calculage where the zeroth ifd will be $ifd_offset = strlen( $makernote ) + 8; // Add the Zeroth IFD pointer to the packed data $packed_data .= put_IFD_Data_Type( $ifd_offset, 4, $Byte_Align ); // Add the makernote to the packed data (if there was one) $packed_data .= $makernote; //Add the IFD's to the packed data $packed_data .= get_IFD_Array_Packed_Data( $tiff_data, $ifd_offset, $Byte_Align ); // Return the result return $packed_data; } /****************************************************************************** * End of Function: get_TIFF_Packed_Data ******************************************************************************/ /****************************************************************************** * * Internal Function: get_IFD_Array_Packed_Data * * Description: Packs a chain of IFD's from EXIF or Meta segments into a form * ready for either a JPEG EXIF/Meta segment or a TIFF file * * Parameters: ifd_data - the IFD chain array, as read from get_EXIF_JPEG or get_Meta_JPEG * Zero_IFD_offset - The offset to the first IFD from the start of the TIFF header * Byte_Align - the Byte alignment to use - "MM" or "II" * * Returns: packed_data - A string containing packed IFD's * ******************************************************************************/ function get_IFD_Array_Packed_Data( $ifd_data, $Zero_IFD_offset, $Byte_Align ) { // Create a string to receive the packed output $packed_data = ""; // Count the IFDs $ifd_count = 0; foreach( $ifd_data as $key => $IFD ) { // Make sure we only count the IFD's, not other information keys if ( is_numeric( $key ) ) { $ifd_count++; } } // Cycle through each IFD, for ( $ifdno = 0; $ifdno < $ifd_count; $ifdno++ ) { // Check if this IFD is the last one if ( $ifdno == $ifd_count - 1 ) { // This IFD is the last one, get it's packed data $packed_data .= get_IFD_Packed_Data( $ifd_data[ $ifdno ], $Zero_IFD_offset +strlen($packed_data), $Byte_Align, FALSE ); } else { // This IFD is NOT the last one, get it's packed data $packed_data .= get_IFD_Packed_Data( $ifd_data[ $ifdno ], $Zero_IFD_offset +strlen($packed_data), $Byte_Align, TRUE ); } } // Return the packed output return $packed_data; } /****************************************************************************** * End of Function: get_IFD_Array_Packed_Data ******************************************************************************/ /****************************************************************************** * * Internal Function: get_IFD_Packed_Data * * Description: Packs an IFD from EXIF or Meta segments into a form * ready for either a JPEG EXIF/Meta segment or a TIFF file * * Parameters: ifd_data - the IFD chain array, as read from get_EXIF_JPEG or get_Meta_JPEG * IFD_offset - The offset to the IFD from the start of the TIFF header * Byte_Align - the Byte alignment to use - "MM" or "II" * Another_IFD - boolean - false if this is the last IFD in the chain * - true if it is not the last * * Returns: packed_data - A string containing packed IFD's * ******************************************************************************/ function get_IFD_Packed_Data( $ifd_data, $IFD_offset, $Byte_Align, $Another_IFD ) { $ifd_body_str = ""; $ifd_data_str = ""; $Tag_Definitions_Name = $ifd_data[ 'Tags Name' ]; // Count the Tags in this IFD $tag_count = 0; foreach( $ifd_data as $key => $tag ) { // Make sure we only count the Tags, not other information keys if ( is_numeric( $key ) ) { $tag_count++; } } // Add the Tag count to the packed data $packed_data = put_IFD_Data_Type( $tag_count, 3, $Byte_Align ); // Calculate the total length of the IFD (without the offset data) $IFD_len = 2 + $tag_count * 12 + 4; // Cycle through each tag foreach( $ifd_data as $key => $tag ) { // Make sure this is a tag, not another information key if ( is_numeric( $key ) ) { // Add the tag number to the packed data $ifd_body_str .= put_IFD_Data_Type( $tag[ 'Tag Number' ], 3, $Byte_Align ); // Add the Data type to the packed data $ifd_body_str .= put_IFD_Data_Type( $tag['Data Type'], 3, $Byte_Align ); // Check if this is a Print Image Matching entry if ( $tag['Type'] == "PIM" ) { // This is a Print Image Matching entry, // encode it $data = Encode_PIM( $tag, $Byte_Align ); } // Check if this is a IPTC/NAA Record within the EXIF IFD else if ( ( ( $Tag_Definitions_Name == "EXIF" ) || ( $Tag_Definitions_Name == "TIFF" ) ) && ( $tag[ 'Tag Number' ] == 33723 ) ) { // This is a IPTC/NAA Record, encode it $data = put_IPTC( $tag['Data'] ); } // Change: Check for embedded XMP as of version 1.11 // Check if this is a XMP Record within the EXIF IFD else if ( ( ( $Tag_Definitions_Name == "EXIF" ) || ( $Tag_Definitions_Name == "TIFF" ) ) && ( $tag[ 'Tag Number' ] == 700 ) ) { // This is a XMP Record, encode it $data = write_XMP_array_to_text( $tag['Data'] ); } // Change: Check for embedded IRB as of version 1.11 // Check if this is a Photoshop IRB Record within the EXIF IFD else if ( ( ( $Tag_Definitions_Name == "EXIF" ) || ( $Tag_Definitions_Name == "TIFF" ) ) && ( $tag[ 'Tag Number' ] == 34377 ) ) { // This is a Photoshop IRB Record, encode it $data = pack_Photoshop_IRB_Data( $tag['Data'] ); } // Exif Thumbnail Offset else if ( ( $tag[ 'Tag Number' ] == 513 ) && ( $Tag_Definitions_Name == "TIFF" ) ) { // The Exif Thumbnail Offset is a pointer but of type Long, not Unknown // Hence we need to put the data into the packed string separately // Calculate the thumbnail offset $data_offset = $IFD_offset + $IFD_len + strlen($ifd_data_str); // Create the Offset for the IFD $data = put_IFD_Data_Type( $data_offset, 4, $Byte_Align ); // Store the thumbnail $ifd_data_str .= $tag['Data']; } // Exif Thumbnail Length else if ( ( $tag[ 'Tag Number' ] == 514 ) && ( $Tag_Definitions_Name == "TIFF" ) ) { // Encode the Thumbnail Length $data = put_IFD_Data_Type( strlen($ifd_data[513]['Data']), 4, $Byte_Align ); } // Sub-IFD else if ( $tag['Type'] == "SubIFD" ) { // This is a Sub-IFD // Calculate the offset to the start of the Sub-IFD $data_offset = $IFD_offset + $IFD_len + strlen($ifd_data_str); // Get the packed data for the IFD chain as the data for this tag $data = get_IFD_Array_Packed_Data( $tag['Data'], $data_offset, $Byte_Align ); } else { // Not a special tag // Create a string to receive the data $data = ""; // Check if this is a type Unknown tag if ( $tag['Data Type'] != 7 ) { // NOT type Unknown // Cycle through each data value and add it to the data string foreach( $tag[ 'Data' ] as $data_val ) { $data .= put_IFD_Data_Type( $data_val, $tag['Data Type'], $Byte_Align ); } } else { // This is a type Unknown - just add the data as is to the data string $data .= $tag[ 'Data' ]; } } // Pad the data string out to at least 4 bytes $data = str_pad ( $data, 4, "\x00" ); // Check if the data type is an ASCII String or type Unknown if ( ( $tag['Data Type'] == 2 ) || ( $tag['Data Type'] == 7 ) ) { // This is an ASCII String or type Unknown // Add the Length of the string to the packed data as the Count $ifd_body_str .= put_IFD_Data_Type( strlen($data), 4, $Byte_Align ); } else { // Add the array count to the packed data as the Count $ifd_body_str .= put_IFD_Data_Type( count($tag[ 'Data' ]), 4, $Byte_Align ); } // Check if the data is over 4 bytes long if ( strlen( $data ) > 4 ) { // Data is longer than 4 bytes - it needs to be offset // Check if this entry is the Maker Note if ( ( $Tag_Definitions_Name == "EXIF" ) && ( $tag[ 'Tag Number' ] == 37500 ) ) { // This is the makernote - It will have already been stored // at its original offset to help preserve it // all we need to do is add the Offset to the IFD packed data $data_offset = $tag[ 'Offset' ]; $ifd_body_str .= put_IFD_Data_Type( $data_offset, 4, $Byte_Align ); } else { // This is NOT the makernote // Calculate the data offset $data_offset = $IFD_offset + $IFD_len + strlen($ifd_data_str); // Add the offset to the IFD packed data $ifd_body_str .= put_IFD_Data_Type( $data_offset, 4, $Byte_Align ); // Add the data to the offset packed data $ifd_data_str .= $data; } } else { // Data is less than or equal to 4 bytes - Add it to the packed IFD data as is $ifd_body_str .= $data; } } } // Assemble the IFD body onto the packed data $packed_data .= $ifd_body_str; // Check if there is another IFD after this one if( $Another_IFD === TRUE ) { // There is another IFD after this // Calculate the Next-IFD offset so that it goes immediately after this IFD $next_ifd_offset = $IFD_offset + $IFD_len + strlen($ifd_data_str); } else { // There is NO IFD after this - indicate with offset=0 $next_ifd_offset = 0; } // Add the Next-IFD offset to the packed data $packed_data .= put_IFD_Data_Type( $next_ifd_offset, 4, $Byte_Align ); // Add the offset data to the packed data $packed_data .= $ifd_data_str; // Return the resulting packed data return $packed_data; } /****************************************************************************** * End of Function: get_IFD_Packed_Data ******************************************************************************/ /****************************************************************************** * * Internal Function: process_TIFF_Header * * Description: Decodes the information stored in a TIFF header and it's * Image File Directories (IFD's). This information is returned * in an array * * Parameters: filehnd - The handle of a open image file, positioned at the * start of the TIFF header * Tag_Definitions_Name - The name of the Tag Definitions group * within the global array IFD_Tag_Definitions * * * Returns: OutputArray - Array of IFD records * FALSE - If an error occured in decoding * ******************************************************************************/ function process_TIFF_Header( $filehnd, $Tag_Definitions_Name ) { // Save the file position where the TIFF header starts, as offsets are relative to this position $Tiff_start_pos = ftell( $filehnd ); // Read the eight bytes of the TIFF header $DataStr = network_safe_fread( $filehnd, 8 ); // Check that we did get all eight bytes if ( strlen( $DataStr ) != 8 ) { return FALSE; // Couldn't read the TIFF header properly } $pos = 0; // First two bytes indicate the byte alignment - should be 'II' or 'MM' // II = Intel (LSB first, MSB last - Little Endian) // MM = Motorola (MSB first, LSB last - Big Endian) $Byte_Align = substr( $DataStr, $pos, 2 ); // Check the Byte Align Characters for validity if ( ( $Byte_Align != "II" ) && ( $Byte_Align != "MM" ) ) { // Byte align field is invalid - we won't be able to decode file return FALSE; } // Skip over the Byte Align field which was just read $pos += 2; // Next two bytes are TIFF ID - should be value 42 with the appropriate byte alignment $TIFF_ID = substr( $DataStr, $pos, 2 ); if ( get_IFD_Data_Type( $TIFF_ID, 3, $Byte_Align ) != 42 ) { // TIFF header ID not found return FALSE; } // Skip over the TIFF ID field which was just read $pos += 2; // Next four bytes are the offset to the first IFD $offset_str = substr( $DataStr, $pos, 4 ); $offset = get_IFD_Data_Type( $offset_str, 4, $Byte_Align ); // Done reading TIFF Header // Move to first IFD if ( fseek( $filehnd, $Tiff_start_pos + $offset ) !== 0 ) { // Error seeking to position of first IFD return FALSE; } // Flag that a makernote has not been found yet $GLOBALS[ "Maker_Note_Tag" ] = FALSE; // Read the IFD chain into an array $Output_Array = read_Multiple_IFDs( $filehnd, $Tiff_start_pos, $Byte_Align, $Tag_Definitions_Name ); // Check if a makernote was found if ( $GLOBALS[ "Maker_Note_Tag" ] != FALSE ) { // Makernote was found - Process it // The makernote needs to be processed after all other // tags as it may require some of the other tags in order // to be processed properly $GLOBALS[ "Maker_Note_Tag" ] = Read_Makernote_Tag( $GLOBALS[ "Maker_Note_Tag" ], $Output_Array, $filehnd ); } $Output_Array[ 'Makernote_Tag' ] = $GLOBALS[ "Maker_Note_Tag" ]; // Save the Name of the Tags used in the output array $Output_Array[ 'Tags Name' ] = $Tag_Definitions_Name; // Save the Byte alignment $Output_Array['Byte_Align'] = $Byte_Align; // Return the output array return $Output_Array ; } /****************************************************************************** * End of Function: process_TIFF_Header ******************************************************************************/ /****************************************************************************** * * Internal Function: read_Multiple_IFDs * * Description: Reads and interprets a chain of standard Image File Directories (IFD's), * and returns the entries in an array. This chain is made up from IFD's * which have a pointer to the next IFD. IFD's are read until the next * pointer indicates there are no more * * Parameters: filehnd - a handle for the image file being read, positioned at the * start of the IFD chain * Tiff_offset - The offset of the TIFF header from the start of the file * Byte_Align - either "MM" or "II" indicating Motorola or Intel Byte alignment * Tag_Definitions_Name - The name of the Tag Definitions group within the global array IFD_Tag_Definitions * local_offsets - True indicates that offset data should be interpreted as being relative to the start of the currrent entry * False (normal) indicates offests are relative to start of Tiff header as per IFD standard * read_next_ptr - True (normal) indicates that a pointer to the next IFD should be read at the end of the IFD * False indicates that no pointer follows the IFD * * * Returns: OutputArray - Array of IFD entries * ******************************************************************************/ function read_Multiple_IFDs( $filehnd, $Tiff_offset, $Byte_Align, $Tag_Definitions_Name, $local_offsets = FALSE, $read_next_ptr = TRUE ) { // Start at the offset of the first IFD $Next_Offset = 0; do { // Read an IFD list($IFD_Array , $Next_Offset) = read_IFD_universal( $filehnd, $Tiff_offset, $Byte_Align, $Tag_Definitions_Name, $local_offsets, $read_next_ptr ); // Move to the position of the next IFD if ( fseek( $filehnd, $Tiff_offset + $Next_Offset ) !== 0 ) { // Error seeking to position of next IFD echo "

Error: Corrupted EXIF

\n"; return FALSE; } $Output_Array[] = $IFD_Array; } while ( $Next_Offset != 0 ); // Until the Next IFD Offset is zero // return resulting array return $Output_Array ; } /****************************************************************************** * End of Function: read_Multiple_IFDs ******************************************************************************/ /****************************************************************************** * * Internal Function: read_IFD_universal * * Description: Reads and interprets a standard or Non-standard Image File * Directory (IFD), and returns the entries in an array * * Parameters: filehnd - a handle for the image file being read, positioned at the start * of the IFD * Tiff_offset - The offset of the TIFF header from the start of the file * Byte_Align - either "MM" or "II" indicating Motorola or Intel Byte alignment * Tag_Definitions_Name - The name of the Tag Definitions group within the global array IFD_Tag_Definitions * local_offsets - True indicates that offset data should be interpreted as being relative to the start of the currrent entry * False (normal) indicates offests are relative to start of Tiff header as per IFD standard * read_next_ptr - True (normal) indicates that a pointer to the next IFD should be read at the end of the IFD * False indicates that no pointer follows the IFD * * Returns: OutputArray - Array of IFD entries * Next_Offset - Offset to next IFD (zero = no next IFD) * ******************************************************************************/ function read_IFD_universal( $filehnd, $Tiff_offset, $Byte_Align, $Tag_Definitions_Name, $local_offsets = FALSE, $read_next_ptr = TRUE ) { if ( ( $filehnd == NULL ) || ( feof( $filehnd ) ) ) { return array (FALSE , 0); } // Record the Name of the Tag Group used for this IFD in the output array $OutputArray[ 'Tags Name' ] = $Tag_Definitions_Name; // Record the offset of the TIFF header in the output array $OutputArray[ 'Tiff Offset' ] = $Tiff_offset; // First 2 bytes of IFD are number of entries in the IFD $No_Entries_str = network_safe_fread( $filehnd, 2 ); $No_Entries = get_IFD_Data_Type( $No_Entries_str, 3, $Byte_Align ); // If the data is corrupt, the number of entries may be huge, which will cause errors // This is often caused by a lack of a Next-IFD pointer if ( $No_Entries> 10000 ) { // Huge number of entries - abort echo "

Error: huge number of EXIF entries - EXIF is probably Corrupted

\n"; return array ( FALSE , 0); } // If the data is corrupt or just stupid, the number of entries may zero, // Indicate this by returning false if ( $No_Entries === 0 ) { // No entries - abort return array ( FALSE , 0); } // Save the file position where first IFD record starts as non-standard offsets // need to know this to calculate an absolute offset $IFD_first_rec_pos = ftell( $filehnd ); // Read in the IFD structure $IFD_Data = network_safe_fread( $filehnd, 12 * $No_Entries ); // Check if the entire IFD was able to be read if ( strlen( $IFD_Data ) != (12 * $No_Entries) ) { // Couldn't read the IFD Data properly, Some Casio files have no Next IFD pointer, hence cause this error echo "

Error: EXIF Corrupted

\n"; return array(FALSE, 0); } // Last 4 bytes of a standard IFD are the offset to the next IFD // Some NON-Standard IFD implementations do not have this, hence causing problems if it is read // If the Next IFD pointer has been requested to be read, if ( $read_next_ptr ) { // Read the pointer to the next IFD $Next_Offset_str = network_safe_fread( $filehnd, 4 ); $Next_Offset = get_IFD_Data_Type( $Next_Offset_str, 4, $Byte_Align ); } else { // Otherwise set the pointer to zero ( no next IFD ) $Next_Offset = 0; } // Initialise current position to the start $pos = 0; // Loop for reading IFD entries for ( $i = 0; $i < $No_Entries; $i++ ) { // First 2 bytes of IFD entry are the tag number ( Unsigned Short ) $Tag_No_str = substr( $IFD_Data, $pos, 2 ); $Tag_No = get_IFD_Data_Type( $Tag_No_str, 3, $Byte_Align ); $pos += 2; // Next 2 bytes of IFD entry are the data format ( Unsigned Short ) $Data_Type_str = substr( $IFD_Data, $pos, 2 ); $Data_Type = get_IFD_Data_Type( $Data_Type_str, 3, $Byte_Align ); $pos += 2; // If Datatype is not between 1 and 12, then skip this entry, it is probably corrupted or custom if (( $Data_Type > 12 ) || ( $Data_Type < 1 ) ) { $pos += 8; continue 1; // Stop trying to process the tag any further and skip to the next one } // Next 4 bytes of IFD entry are the data count ( Unsigned Long ) $Data_Count_str = substr( $IFD_Data, $pos, 4 ); $Data_Count = get_IFD_Data_Type( $Data_Count_str, 4, $Byte_Align ); $pos += 4; if ( $Data_Count > 100000 ) { echo "

Error: huge EXIF data count - EXIF is probably Corrupted

\n"; // Some Casio files have no Next IFD pointer, hence cause errors return array ( FALSE , 0); } // Total Data size is the Data Count multiplied by the size of the Data Type $Total_Data_Size = $GLOBALS['IFD_Data_Sizes'][ $Data_Type ] * $Data_Count; $Data_Start_pos = -1; // If the total data size is larger than 4 bytes, then the data part is the offset to the real data if ( $Total_Data_Size > 4 ) { // Not enough room for data - offset provided instead $Data_Offset_str = substr( $IFD_Data, $pos, 4 ); $Data_Start_pos = get_IFD_Data_Type( $Data_Offset_str, 4, $Byte_Align ); // In some NON-STANDARD makernotes, the offset is relative to the start of the current IFD entry if ( $local_offsets ) { // This is a NON-Standard IFD, seek relative to the start of the current tag fseek( $filehnd, $IFD_first_rec_pos + $pos - 8 + $Data_Start_pos ); } else { // This is a normal IFD, seek relative to the start of the TIFF header fseek( $filehnd, $Tiff_offset + $Data_Start_pos ); } // Read the data block from the offset position $DataStr = network_safe_fread( $filehnd, $Total_Data_Size ); } else { // The data block is less than 4 bytes, and is provided in the IFD entry, so read it $DataStr = substr( $IFD_Data, $pos, $Total_Data_Size ); } // Increment the position past the data $pos += 4; // Now create the entry for output array $Data_Array = array( ); // Read the data items from the data block if ( ( $Data_Type != 2 ) && ( $Data_Type != 7 ) ) { // The data type is Numerical, Read the data items from the data block for ( $j = 0; $j < $Data_Count; $j++ ) { $Part_Data_Str = substr( $DataStr, $j * $GLOBALS['IFD_Data_Sizes'][ $Data_Type ], $GLOBALS['IFD_Data_Sizes'][ $Data_Type ] ); $Data_Array[] = get_IFD_Data_Type( $Part_Data_Str, $Data_Type, $Byte_Align ); } } elseif ( $Data_Type == 2 ) { // The data type is String(s) (type 2) // Strip the last terminating Null $DataStr = substr( $DataStr, 0, strlen($DataStr)-1 ); // Split the data block into multiple strings whereever there is a Null $Data_Array = explode( "\x00", $DataStr ); } else { // The data type is Unknown (type 7) // Do nothing to data $Data_Array = $DataStr; } // If this is a Sub-IFD entry, if ( ( array_key_exists( $Tag_No, $GLOBALS[ "IFD_Tag_Definitions" ][$Tag_Definitions_Name] ) ) && ( "SubIFD" == $GLOBALS[ "IFD_Tag_Definitions" ][$Tag_Definitions_Name][ $Tag_No ]['Type'] ) ) { // This is a Sub-IFD entry, go and process the data forming Sub-IFD and use its output array as the new data for this entry fseek( $filehnd, $Tiff_offset + $Data_Array[0] ); $Data_Array = read_Multiple_IFDs( $filehnd, $Tiff_offset, $Byte_Align, $GLOBALS[ "IFD_Tag_Definitions" ][$Tag_Definitions_Name][ $Tag_No ]['Tags Name'] ); } $desc = ""; $units = ""; // Check if this tag exists in the list of tag definitions, if ( array_key_exists ( $Tag_No, $GLOBALS[ "IFD_Tag_Definitions" ][$Tag_Definitions_Name]) ) { if ( array_key_exists ( 'Description', $GLOBALS[ "IFD_Tag_Definitions" ][$Tag_Definitions_Name][ $Tag_No ] ) ) { $desc = $GLOBALS[ "IFD_Tag_Definitions" ][$Tag_Definitions_Name][ $Tag_No ]['Description']; } if ( array_key_exists ( 'Units', $GLOBALS[ "IFD_Tag_Definitions" ][$Tag_Definitions_Name][ $Tag_No ] ) ) { $units = $GLOBALS[ "IFD_Tag_Definitions" ][$Tag_Definitions_Name][ $Tag_No ]['Units']; } // Tag exists in definitions, append details to output array $OutputArray[ $Tag_No ] = array ( "Tag Number" => $Tag_No, "Tag Name" => $GLOBALS[ "IFD_Tag_Definitions" ][$Tag_Definitions_Name][ $Tag_No ]['Name'], "Tag Description" => $desc, "Data Type" => $Data_Type, "Type" => $GLOBALS[ "IFD_Tag_Definitions" ][$Tag_Definitions_Name][ $Tag_No ]['Type'], "Units" => $units, "Data" => $Data_Array ); } else { // Tag doesnt exist in definitions, append unknown details to output array $OutputArray[ $Tag_No ] = array ( "Tag Number" => $Tag_No, "Tag Name" => "Unknown Tag #" . $Tag_No, "Tag Description" => "", "Data Type" => $Data_Type, "Type" => "Unknown", "Units" => "", "Data" => $Data_Array ); } // Some information of type "Unknown" (type 7) might require information about // how it's position and byte alignment in order to be decoded if ( $Data_Type == 7 ) { $OutputArray[ $Tag_No ]['Offset'] = $Data_Start_pos; $OutputArray[ $Tag_No ]['Byte Align'] = $Byte_Align; } //////////////////////////////////////////////////////////////////////// // Special Data handling //////////////////////////////////////////////////////////////////////// // Check if this is a Print Image Matching entry if ( $OutputArray[ $Tag_No ]['Type'] == "PIM" ) { // This is a Print Image Matching entry, decode it. $OutputArray[ $Tag_No ] = Decode_PIM( $OutputArray[ $Tag_No ], $Tag_Definitions_Name ); } // Interpret the entry into a text string using a custom interpreter $text_val = get_Tag_Text_Value( $OutputArray[ $Tag_No ], $Tag_Definitions_Name ); // Check if a text string was generated if ( $text_val !== FALSE ) { // A string was generated, append it to the output array entry $OutputArray[ $Tag_No ]['Text Value'] = $text_val; $OutputArray[ $Tag_No ]['Decoded'] = TRUE; } else { // A string was NOT generated, append a generic string to the output array entry $OutputArray[ $Tag_No ]['Text Value'] = get_IFD_value_as_text( $OutputArray[ $Tag_No ] ) . " " . $units; $OutputArray[ $Tag_No ]['Decoded'] = FALSE; } // Check if this entry is the Maker Note if ( ( $Tag_Definitions_Name == "EXIF" ) && ( $Tag_No == 37500 ) ) { // Save some extra information which will allow Makernote Decoding with the output array entry $OutputArray[ $Tag_No ]['Offset'] = $Data_Start_pos; $OutputArray[ $Tag_No ][ 'Tiff Offset' ] = $Tiff_offset; $OutputArray[ $Tag_No ]['ByteAlign'] = $Byte_Align; // Save a pointer to this entry for Maker note processing later $GLOBALS[ "Maker_Note_Tag" ] = & $OutputArray[ $Tag_No ]; } // Check if this is a IPTC/NAA Record within the EXIF IFD if ( ( ( $Tag_Definitions_Name == "EXIF" ) || ( $Tag_Definitions_Name == "TIFF" ) ) && ( $Tag_No == 33723 ) ) { // This is a IPTC/NAA Record, interpret it and put result in the data for this entry $OutputArray[ $Tag_No ]['Data'] = get_IPTC( $DataStr ); $OutputArray[ $Tag_No ]['Decoded'] = TRUE; } // Change: Check for embedded XMP as of version 1.11 // Check if this is a XMP Record within the EXIF IFD if ( ( ( $Tag_Definitions_Name == "EXIF" ) || ( $Tag_Definitions_Name == "TIFF" ) ) && ( $Tag_No == 700 ) ) { // This is a XMP Record, interpret it and put result in the data for this entry $OutputArray[ $Tag_No ]['Data'] = read_XMP_array_from_text( $DataStr ); $OutputArray[ $Tag_No ]['Decoded'] = TRUE; } // Change: Check for embedded IRB as of version 1.11 // Check if this is a Photoshop IRB Record within the EXIF IFD if ( ( ( $Tag_Definitions_Name == "EXIF" ) || ( $Tag_Definitions_Name == "TIFF" ) ) && ( $Tag_No == 34377 ) ) { // This is a Photoshop IRB Record, interpret it and put result in the data for this entry $OutputArray[ $Tag_No ]['Data'] = unpack_Photoshop_IRB_Data( $DataStr ); $OutputArray[ $Tag_No ]['Decoded'] = TRUE; } // Exif Thumbnail // Check that both the thumbnail length and offset entries have been processed, // and that this is one of them if ( ( ( ( $Tag_No == 513 ) && ( array_key_exists( 514, $OutputArray ) ) ) || ( ( $Tag_No == 514 ) && ( array_key_exists( 513, $OutputArray ) ) ) ) && ( $Tag_Definitions_Name == "TIFF" ) ) { // Seek to the start of the thumbnail using the offset entry fseek( $filehnd, $Tiff_offset + $OutputArray[513]['Data'][0] ); // Read the thumbnail data, and replace the offset data with the thumbnail $OutputArray[513]['Data'] = network_safe_fread( $filehnd, $OutputArray[514]['Data'][0] ); } // Casio Thumbnail // Check that both the thumbnail length and offset entries have been processed, // and that this is one of them if ( ( ( ( $Tag_No == 0x0004 ) && ( array_key_exists( 0x0003, $OutputArray ) ) ) || ( ( $Tag_No == 0x0003 ) && ( array_key_exists( 0x0004, $OutputArray ) ) ) ) && ( $Tag_Definitions_Name == "Casio Type 2" ) ) { // Seek to the start of the thumbnail using the offset entry fseek( $filehnd, $Tiff_offset + $OutputArray[0x0004]['Data'][0] ); // Read the thumbnail data, and replace the offset data with the thumbnail $OutputArray[0x0004]['Data'] = network_safe_fread( $filehnd, $OutputArray[0x0003]['Data'][0] ); } // Minolta Thumbnail // Check that both the thumbnail length and offset entries have been processed, // and that this is one of them if ( ( ( ( $Tag_No == 0x0088 ) && ( array_key_exists( 0x0089, $OutputArray ) ) ) || ( ( $Tag_No == 0x0089 ) && ( array_key_exists( 0x0088, $OutputArray ) ) ) ) && ( $Tag_Definitions_Name == "Olympus" ) ) { // Seek to the start of the thumbnail using the offset entry fseek( $filehnd, $Tiff_offset + $OutputArray[0x0088]['Data'][0] ); // Read the thumbnail data, and replace the offset data with the thumbnail $OutputArray[0x0088]['Data'] = network_safe_fread( $filehnd, $OutputArray[0x0089]['Data'][0] ); // Sometimes the minolta thumbnail data is empty (or the offset is corrupt, which results in the same thing) // Check if the thumbnail data exists if ( $OutputArray[0x0088]['Data'] != "" ) { // Thumbnail exists // Minolta Thumbnails are missing their first 0xFF for some reason, // which is replaced with some weird character, so fix this $OutputArray[0x0088]['Data']{0} = "\xFF"; } else { // Thumbnail doesnt exist - make it obvious $OutputArray[0x0088]['Data'] = FALSE; } } } // Return the array of IFD entries and the offset to the next IFD return array ($OutputArray , $Next_Offset); } /****************************************************************************** * End of Function: read_IFD_universal ******************************************************************************/ /****************************************************************************** * * Internal Function: get_Tag_Text_Value * * Description: Attempts to interpret an IFD entry into a text string using the * information in the IFD_Tag_Definitions global array. * * Parameters: Tag - The IFD entry to process * Tag_Definitions_Name - The name of the tag definitions to use from within the IFD_Tag_Definitions global array * * Returns: String - if the tag was successfully decoded into a text string * FALSE - if the tag could not be decoded using the information * in the IFD_Tag_Definitions global array * ******************************************************************************/ function get_Tag_Text_Value( $Tag, $Tag_Definitions_Name ) { // Check what format the entry is specified as if ( $Tag['Type'] == "String" ) { // Format is Text String // If "Unknown" (type 7) data type, if ( $Tag['Data Type'] == 7 ) { // Return data as is. return $Tag['Data']; } else { // Otherwise return the default string value of the datatype return get_IFD_value_as_text( $Tag ); } } else if ( $Tag['Type'] == "Character Coded String" ) { // Format is Character Coded String (First 8 characters indicate coding scheme) // Convert Data to a string if ( $Tag['Data Type'] == 7 ) { // If it is type "Unknown" (type 7) use data as is $data = $Tag['Data']; } else { // Otherwise use the default string value of the datatype $data = get_IFD_value_as_text( $Tag ); } // Some implementations allow completely data with no Coding Scheme Name, // so we need to handle this to avoid errors if ( trim( $data ) == "" ) { return ""; } // Extract the Coding Scheme Name from the first 8 characters $char_code = substr( $data, 0, 8 ); // Extract the Data part from after the first 8 characters $characters = substr( $data, 8 ); // Check coding scheme and interpret as neccessary if ( $char_code === "ASCII\x00\x00\x00" ) { // ASCII coding - return data as is. return $characters; } elseif ( ( $char_code === "UNICODE\x00" ) || ( $char_code === "Unicode\x00" ) ) // Note lowercase is non standard { // Unicode coding - interpret and return result. return xml_UTF16_clean( $characters, TRUE ); } else { // Unknown coding - return string indicating this return "Unsupported character coding : \"$char_code\"\n\"" . trim($characters) . "\""; } break; } else if ( $Tag['Type'] == "Numeric" ) { // Format is numeric - return default text value with any required units text appended if ( array_key_exists ( 'Units', $GLOBALS[ "IFD_Tag_Definitions" ][$Tag_Definitions_Name][ $Tag["Tag Number"] ] ) ) { $units = $GLOBALS[ "IFD_Tag_Definitions" ][$Tag_Definitions_Name][ $Tag["Tag Number"] ]['Units']; } else { $units = ""; } return get_IFD_value_as_text( $Tag ) . " " . $units; } else if ( $Tag['Type'] == "Lookup" ) { // Format is a Lookup Table // Get a numeric value to use in lookup if ( is_array( $Tag['Data'] ) ) { // If data is an array, use first element $first_val = $Tag['Data'][0]; } else if ( is_string( $Tag['Data'] ) ) { // If data is a string, use the first character $first_val = ord($Tag['Data']{0}); } else { // Otherwise use the data as is $first_val = $Tag['Data']; } // Check if the data value exists in the lookup table for this IFD entry if ( array_key_exists( $first_val, $GLOBALS[ "IFD_Tag_Definitions" ][$Tag_Definitions_Name][ $Tag["Tag Number"] ] ) ) { // Data value exists in lookup table - return the matching string return $GLOBALS[ "IFD_Tag_Definitions" ][$Tag_Definitions_Name][ $Tag["Tag Number"] ][ $first_val ]; } else { // Data value doesnt exist in lookup table - return explanation string return "Unknown Reserved value $first_val "; } } else if ( $Tag['Type'] == "Special" ) { // Format is special - interpret to text with special handlers return get_Special_Tag_Text_Value( $Tag, $Tag_Definitions_Name ); } else if ( $Tag['Type'] == "PIM" ) { // Format is Print Image Matching info - interpret with custom handler return get_PIM_Text_Value( $Tag, $Tag_Definitions_Name ); } else if ( $Tag['Type'] == "SubIFD" ) { // Format is a Sub-IFD - this has no text value return ""; } else { // Unknown Format - Couldn't interpret using the IFD_Tag_Definitions global array information return FALSE; } } /****************************************************************************** * End of Function: get_Tag_Text_Value ******************************************************************************/ /****************************************************************************** * * Internal Function: get_Special_Tag_Text_Value * * Description: Interprets an IFD entry marked as "Special" in the IFD_Tag_Definitions * global array into a text string using custom handlers * * Parameters: Tag - The IFD entry to process * Tag_Definitions_Name - The name of the tag definitions to use from within the IFD_Tag_Definitions global array * * Returns: String - if the tag was successfully decoded into a text string * FALSE - if the tag could not be decoded * ******************************************************************************/ function get_Special_Tag_Text_Value( $Tag, $Tag_Definitions_Name ) { // Check what type of IFD is being decoded if ( $Tag_Definitions_Name == "TIFF" ) { // This is a TIFF IFD (bottom level) // Check what tag number the IFD entry has. switch ( $Tag['Tag Number'] ) { case 530: // YCbCr Sub Sampling Entry // Data contains two numerical values if ( ( $Tag['Data'][0] == 2 ) && ( $Tag['Data'][1] == 1 ) ) { // Values are 2,1 - hence YCbCr 4:2:2 return "YCbCr 4:2:2 ratio of chrominance components to the luminance components"; } elseif ( ( $Tag['Data'][0] == 2 ) && ( $Tag['Data'][1] == 2 ) ) { // Values are 2,2 - hence YCbCr 4:2:0 return "YCbCr 4:2:0 ratio of chrominance components to the luminance components"; } else { // Other values are unknown return "Unknown Reserved value (" . $Tag['Data'][0] . ")"; } break; default: return FALSE; } } else if ( $Tag_Definitions_Name == "EXIF" ) { // This is an EXIF IFD // Check what tag number the IFD entry has. switch ( $Tag['Tag Number'] ) { case 37121: // Components configuration // Data contains 4 numerical values indicating component type $output_str = ""; // Cycle through each component for ( $Num = 0; $Num < 4; $Num++ ) { // Construct first part of text string $output_str .= "Component " . ( $Num + 1 ) . ": "; // Construct second part of text string via // lookup using numerical value $value = ord( $Tag['Data']{$Num} ); switch( $value ) { case 0: $output_str .= "Does not exist\n"; break; case 1: $output_str .= "Y (Luminance)\n"; break; case 2: $output_str .= "Cb (Chroma minus Blue)\n"; break; case 3: $output_str .= "Cr (Chroma minus Red)\n"; break; case 4: $output_str .= "Red\n"; break; case 5: $output_str .= "Green\n"; break; case 6: $output_str .= "Blue\n"; break; default: $output_str .= "Unknown value $value\n"; }; } // Return the completed string return $output_str; break; case 41730: // Colour Filter Array Pattern // The first two characters are a SHORT for Horizontal repeat pixel unit - $n_max = get_IFD_Data_Type( substr( $Tag['Data'], 0, 2 ), 3, $Tag['Byte Align'] ); // The next two characters are a SHORT for Vertical repeat pixel unit - $m_max = get_IFD_Data_Type( substr( $Tag['Data'], 2, 2 ), 3, $Tag['Byte Align'] ); // At least one camera type appears to have byte reversed values for N_Max and M_Max // Check if they need reversing if ( $n_max > 256 ) { $n_max = $n_max/256 + 256*($n_max%256); } if ( $m_max > 256 ) { $m_max = $m_max/256 + 256*($m_max%256); } $output_str = ""; // Cycle through all the elements in the resulting 2 dimensional array, for( $m = 1; $m <= $m_max; $m++ ) { for( $n = 1; $n <= $n_max; $n++ ) { // Append text from a lookup table according to // the value read for this element switch ( ord($Tag['Data']{($n_max*($m-1)+$n+3)}) ) { case 0: $output_str .= "RED "; break; case 1: $output_str .= "GREEN "; break; case 2: $output_str .= "BLUE "; break; case 3: $output_str .= "CYAN "; break; case 4: $output_str .= "MAGENTA "; break; case 5: $output_str .= "YELLOW "; break; case 6: $output_str .= "WHITE "; break; default: $output_str .= "Unknown "; break; }; }; $output_str .= "\n"; }; // Return the resulting string return $output_str; break; default: return FALSE; } } else { // Unknown IFD type, see if it is part of a makernote return get_Makernote_Text_Value( $Tag, $Tag_Definitions_Name ); } } /****************************************************************************** * End of Function: get_Tag_Text_Value ******************************************************************************/ /****************************************************************************** * * Function: interpret_IFD * * Description: Generates html detailing the contents a single IFD. * * Parameters: IFD_array - the array containing an IFD * filename - the name of the Image file being processed ( used * by scripts which displays EXIF thumbnails) * * Returns: output_str - A string containing the HTML * ******************************************************************************/ function interpret_IFD( $IFD_array, $filename ) { // Create the output string with the table tag $output_str = "\n"; // Create an extra output string to receive any supplementary html // which cannot go inside the table $extra_IFD_str = ""; // Check that the IFD array is valid if ( ( $IFD_array === FALSE ) || ( $IFD_array === NULL ) ) { // the IFD array is NOT valid - exit return ""; } // Check if this is an EXIF IFD and if there is a makernote present if ( ( $IFD_array['Tags Name'] === "EXIF" ) && ( ! array_key_exists( 37500, $IFD_array ) ) ) { // This is an EXIF IFD but NO makernote is present - Add a message to the output $extra_IFD_str .= "

No Makernote Present

"; } // Cycle through each tag in the IFD foreach( $IFD_array as $Tag_ID => $Exif_Tag ) { // Ignore the non numeric elements - they aren't tags if ( ! is_numeric ( $Tag_ID ) ) { // Skip Tags Name } // Check if the Tag has been decoded successfully else if ( $Exif_Tag['Decoded'] == TRUE ) { // This tag has been successfully decoded // Table cells won't get drawn with nothing in them - // Ensure that at least a non breaking space exists in them if ( trim($Exif_Tag['Text Value']) == "" ) { $Exif_Tag['Text Value'] = " "; } // Check if the tag is a sub-IFD if ( $Exif_Tag['Type'] == "SubIFD" ) { // This is a sub-IFD tag // Add a sub-heading for the sub-IFD $extra_IFD_str .= "

" . $Exif_Tag['Tag Name'] . " contents

"; // Cycle through each sub-IFD in the chain foreach ( $Exif_Tag['Data'] as $subIFD ) { // Interpret this sub-IFD and add the html to the secondary output $extra_IFD_str .= interpret_IFD( $subIFD, $filename ); } } // Check if the tag is a makernote else if ( $Exif_Tag['Type'] == "Maker Note" ) { // This is a Makernote Tag // Add a sub-heading for the Makernote $extra_IFD_str .= "

Maker Note Contents

"; // Interpret the Makernote and add the html to the secondary output $extra_IFD_str .= Interpret_Makernote_to_HTML( $Exif_Tag, $filename ); } // Check if this is a IPTC/NAA Record within the EXIF IFD else if ( $Exif_Tag['Type'] == "IPTC" ) { // This is a IPTC/NAA Record, interpret it and output to the secondary html $extra_IFD_str .= "

Contains IPTC/NAA Embedded in EXIF

"; $extra_IFD_str .=Interpret_IPTC_to_HTML( $Exif_Tag['Data'] ); } // Change: Check for embedded XMP as of version 1.11 // Check if this is a XMP Record within the EXIF IFD else if ( $Exif_Tag['Type'] == "XMP" ) { // This is a XMP Record, interpret it and output to the secondary html $extra_IFD_str .= "

Contains XMP Embedded in EXIF

"; $extra_IFD_str .= Interpret_XMP_to_HTML( $Exif_Tag['Data'] ); } // Change: Check for embedded IRB as of version 1.11 // Check if this is a Photoshop IRB Record within the EXIF IFD else if ( $Exif_Tag['Type'] == "IRB" ) { // This is a Photoshop IRB Record, interpret it and output to the secondary html $extra_IFD_str .= "

Contains Photoshop IRB Embedded in EXIF

"; $extra_IFD_str .= Interpret_IRB_to_HTML( $Exif_Tag['Data'], $filename ); } // Check if the tag is Numeric else if ( $Exif_Tag['Type'] == "Numeric" ) { // Numeric Tag - Output text value as is. $output_str .= "\n"; } else { // Other tag - Output text as preformatted $output_str .= "\n"; } } else { // Tag has NOT been decoded successfully // Hence it is either an unknown tag, or one which // requires processing at the time of html construction // Table cells won't get drawn with nothing in them - // Ensure that at least a non breaking space exists in them if ( trim($Exif_Tag['Text Value']) == "" ) { $Exif_Tag['Text Value'] = " "; } // Check if this tag is the first IFD Thumbnail if ( ( $IFD_array['Tags Name'] == "TIFF" ) && ( $Tag_ID == 513 ) ) { // This is the first IFD thumbnail - Add html to the output // Change: as of version 1.11 - Changed to make thumbnail link portable across directories // Build the path of the thumbnail script and its filename parameter to put in a url $link_str = get_relative_path( dirname(__FILE__) . "/get_exif_thumb.php" , getcwd ( ) ); $link_str .= "?filename="; $link_str .= get_relative_path( $filename, dirname(__FILE__) ); // Add thumbnail link to html $output_str .= "\n"; } // Check if this is the Makernote else if ( $Exif_Tag['Type'] == "Maker Note" ) { // This is the makernote, but has not been decoded // Add a message to the secondary output $extra_IFD_str .= "

Makernote Coding Unknown

\n"; } else { // This is an Unknown Tag // Check if the user wants to hide unknown tags if ( $GLOBALS['HIDE_UNKNOWN_TAGS'] === FALSE ) { // User wants to display unknown tags // Check if the Data is an ascii string if ( $Exif_Tag['Data Type'] == 2 ) { // This is a Ascii String field - add it preformatted to the output $output_str .= "\n"; } else { // Not an ASCII string - add it as is to the output $output_str .= "\n"; } } } } } // Close the table in the output $output_str .= "
" . $Exif_Tag['Tag Name'] . "" . $Exif_Tag['Text Value'] . "
" . $Exif_Tag['Tag Name'] . "
" . trim( $Exif_Tag['Text Value']) . "
" . $Exif_Tag['Tag Name'] . "
" . $Exif_Tag['Tag Name'] . "
" . trim( $Exif_Tag['Text Value'] ) . "
" . $Exif_Tag['Tag Name'] . "" . trim( $Exif_Tag['Text Value'] ) . "
\n"; // Add the secondary output at the end of the main output $output_str .= "$extra_IFD_str\n"; // Return the resulting html return $output_str; } /****************************************************************************** * End of Function: interpret_IFD ******************************************************************************/ /****************************************************************************** * * Function: get_IFD_Data_Type * * Description: Decodes an IFD field value from a binary data string, using * information supplied about the data type and byte alignment of * the stored data. * This function should be used for all datatypes except ASCII strings * * Parameters: input_data - a binary data string containing the IFD value, * must be exact length of the value * data_type - a number representing the IFD datatype as per the * TIFF 6.0 specification: * 1 = Unsigned 8-bit Byte * 2 = ASCII String * 3 = Unsigned 16-bit Short * 4 = Unsigned 32-bit Long * 5 = Unsigned 2x32-bit Rational * 6 = Signed 8-bit Byte * 7 = Undefined * 8 = Signed 16-bit Short * 9 = Signed 32-bit Long * 10 = Signed 2x32-bit Rational * 11 = 32-bit Float * 12 = 64-bit Double * Byte_Align - Indicates the byte alignment of the data. * MM = Motorola, MSB first, Big Endian * II = Intel, LSB first, Little Endian * * Returns: output - the value of the data (string or numeric) * ******************************************************************************/ function get_IFD_Data_Type( $input_data, $data_type, $Byte_Align ) { // Check if this is a Unsigned Byte, Unsigned Short or Unsigned Long if (( $data_type == 1 ) || ( $data_type == 3 ) || ( $data_type == 4 )) { // This is a Unsigned Byte, Unsigned Short or Unsigned Long // Check the byte alignment to see if the bytes need tp be reversed if ( $Byte_Align == "II" ) { // This is in Intel format, reverse it $input_data = strrev ( $input_data ); } // Convert the binary string to a number and return it return hexdec( bin2hex( $input_data ) ); } // Check if this is a ASCII string type elseif ( $data_type == 2 ) { // Null terminated ASCII string(s) // The input data may represent multiple strings, as the // 'count' field represents the total bytes, not the number of strings // Hence this should not be processed here, as it would have // to return multiple values instead of a single value echo "

Error - ASCII Strings should not be processed in get_IFD_Data_Type

\n"; return "Error Should never get here"; //explode( "\x00", $input_data ); } // Check if this is a Unsigned rational type elseif ( $data_type == 5 ) { // This is a Unsigned rational type // Check the byte alignment to see if the bytes need to be reversed if ( $Byte_Align == "MM" ) { // Motorola MSB first byte aligment // Unpack the Numerator and denominator and return them return unpack( 'NNumerator/NDenominator', $input_data ); } else { // Intel LSB first byte aligment // Unpack the Numerator and denominator and return them return unpack( 'VNumerator/VDenominator', $input_data ); } } // Check if this is a Signed Byte, Signed Short or Signed Long elseif ( ( $data_type == 6 ) || ( $data_type == 8 ) || ( $data_type == 9 ) ) { // This is a Signed Byte, Signed Short or Signed Long // Check the byte alignment to see if the bytes need to be reversed if ( $Byte_Align == "II" ) { //Intel format, reverse the bytes $input_data = strrev ( $input_data ); } // Convert the binary string to an Unsigned number $value = hexdec( bin2hex( $input_data ) ); // Convert to signed number // Check if it is a Byte above 128 (i.e. a negative number) if ( ( $data_type == 6 ) && ( $value > 128 ) ) { // number should be negative - make it negative return $value - 256; } // Check if it is a Short above 32767 (i.e. a negative number) if ( ( $data_type == 8 ) && ( $value > 32767 ) ) { // number should be negative - make it negative return $value - 65536; } // Check if it is a Long above 2147483648 (i.e. a negative number) if ( ( $data_type == 9 ) && ( $value > 2147483648 ) ) { // number should be negative - make it negative return $value - 4294967296; } // Return the signed number return $value; } // Check if this is Undefined type elseif ( $data_type == 7 ) { // Custom Data - Do nothing return $input_data; } // Check if this is a Signed Rational type elseif ( $data_type == 10 ) { // This is a Signed Rational type // Signed Long not available with endian in unpack , use unsigned and convert // Check the byte alignment to see if the bytes need to be reversed if ( $Byte_Align == "MM" ) { // Motorola MSB first byte aligment // Unpack the Numerator and denominator $value = unpack( 'NNumerator/NDenominator', $input_data ); } else { // Intel LSB first byte aligment // Unpack the Numerator and denominator $value = unpack( 'VNumerator/VDenominator', $input_data ); } // Convert the numerator to a signed number // Check if it is above 2147483648 (i.e. a negative number) if ( $value['Numerator'] > 2147483648 ) { // number is negative $value['Numerator'] -= 4294967296; } // Convert the denominator to a signed number // Check if it is above 2147483648 (i.e. a negative number) if ( $value['Denominator'] > 2147483648 ) { // number is negative $value['Denominator'] -= 4294967296; } // Return the Signed Rational value return $value; } // Check if this is a Float type elseif ( $data_type == 11 ) { // IEEE 754 Float // TODO - EXIF - IFD datatype Float not implemented yet return "FLOAT NOT IMPLEMENTED YET"; } // Check if this is a Double type elseif ( $data_type == 12 ) { // IEEE 754 Double // TODO - EXIF - IFD datatype Double not implemented yet return "DOUBLE NOT IMPLEMENTED YET"; } else { // Error - Invalid Datatype return "Invalid Datatype $data_type"; } } /****************************************************************************** * End of Function: get_IFD_Data_Type ******************************************************************************/ /****************************************************************************** * * Function: put_IFD_Data_Type * * Description: Encodes an IFD field from a value to a binary data string, using * information supplied about the data type and byte alignment of * the stored data. * * Parameters: input_data - an IFD data value, numeric or string * data_type - a number representing the IFD datatype as per the * TIFF 6.0 specification: * 1 = Unsigned 8-bit Byte * 2 = ASCII String * 3 = Unsigned 16-bit Short * 4 = Unsigned 32-bit Long * 5 = Unsigned 2x32-bit Rational * 6 = Signed 8-bit Byte * 7 = Undefined * 8 = Signed 16-bit Short * 9 = Signed 32-bit Long * 10 = Signed 2x32-bit Rational * 11 = 32-bit Float * 12 = 64-bit Double * Byte_Align - Indicates the byte alignment of the data. * MM = Motorola, MSB first, Big Endian * II = Intel, LSB first, Little Endian * * Returns: output - the packed binary string of the data * ******************************************************************************/ function put_IFD_Data_Type( $input_data, $data_type, $Byte_Align ) { // Process according to the datatype switch ( $data_type ) { case 1: // Unsigned Byte - return character as is return chr($input_data); break; case 2: // ASCII String // Return the string with terminating null return $input_data . "\x00"; break; case 3: // Unsigned Short // Check byte alignment if ( $Byte_Align == "II" ) { // Intel/Little Endian - pack the short and return return pack( "v", $input_data ); } else { // Motorola/Big Endian - pack the short and return return pack( "n", $input_data ); } break; case 4: // Unsigned Long // Check byte alignment if ( $Byte_Align == "II" ) { // Intel/Little Endian - pack the long and return return pack( "V", $input_data ); } else { // Motorola/Big Endian - pack the long and return return pack( "N", $input_data ); } break; case 5: // Unsigned Rational // Check byte alignment if ( $Byte_Align == "II" ) { // Intel/Little Endian - pack the two longs and return return pack( "VV", $input_data['Numerator'], $input_data['Denominator'] ); } else { // Motorola/Big Endian - pack the two longs and return return pack( "NN", $input_data['Numerator'], $input_data['Denominator'] ); } break; case 6: // Signed Byte // Check if number is negative if ( $input_data < 0 ) { // Number is negative - return signed character return chr( $input_data + 256 ); } else { // Number is positive - return character return chr( $input_data ); } break; case 7: // Unknown - return as is return $input_data; break; case 8: // Signed Short // Check if number is negative if ( $input_data < 0 ) { // Number is negative - make signed value $input_data = $input_data + 65536; } // Check byte alignment if ( $Byte_Align == "II" ) { // Intel/Little Endian - pack the short and return return pack( "v", $input_data ); } else { // Motorola/Big Endian - pack the short and return return pack( "n", $input_data ); } break; case 9: // Signed Long // Check if number is negative if ( $input_data < 0 ) { // Number is negative - make signed value $input_data = $input_data + 4294967296; } // Check byte alignment if ( $Byte_Align == "II" ) { // Intel/Little Endian - pack the long and return return pack( "v", $input_data ); } else { // Motorola/Big Endian - pack the long and return return pack( "n", $input_data ); } break; case 10: // Signed Rational // Check if numerator is negative if ( $input_data['Numerator'] < 0 ) { // Number is numerator - make signed value $input_data['Numerator'] = $input_data['Numerator'] + 4294967296; } // Check if denominator is negative if ( $input_data['Denominator'] < 0 ) { // Number is denominator - make signed value $input_data['Denominator'] = $input_data['Denominator'] + 4294967296; } // Check byte alignment if ( $Byte_Align == "II" ) { // Intel/Little Endian - pack the two longs and return return pack( "VV", $input_data['Numerator'], $input_data['Denominator'] ); } else { // Motorola/Big Endian - pack the two longs and return return pack( "NN", $input_data['Numerator'], $input_data['Denominator'] ); } break; case 11: // Float // IEEE 754 Float // TODO - EXIF - IFD datatype Float not implemented yet return "FLOAT NOT IMPLEMENTED YET"; break; case 12: // Double // IEEE 754 Double // TODO - EXIF - IFD datatype Double not implemented yet return "DOUBLE NOT IMPLEMENTED YET"; break; default: // Error - Invalid Datatype return "Invalid Datatype $data_type"; break; } // Shouldn't get here return FALSE; } /****************************************************************************** * End of Function: put_IFD_Data_Type ******************************************************************************/ /****************************************************************************** * * Function: get_IFD_value_as_text * * Description: Decodes an IFD field value from a binary data string, using * information supplied about the data type and byte alignment of * the stored data. * This function should be used for all datatypes except ASCII strings * * Parameters: input_data - a binary data string containing the IFD value, * must be exact length of the value * data_type - a number representing the IFD datatype as per the * TIFF 6.0 specification: * 1 = Unsigned 8-bit Byte * 2 = ASCII String * 3 = Unsigned 16-bit Short * 4 = Unsigned 32-bit Long * 5 = Unsigned 2x32-bit Rational * 6 = Signed 8-bit Byte * 7 = Undefined * 8 = Signed 16-bit Short * 9 = Signed 32-bit Long * 10 = Signed 2x32-bit Rational * 11 = 32-bit Float * 12 = 64-bit Double * Byte_Align - Indicates the byte alignment of the data. * MM = Motorola, MSB first, Big Endian * II = Intel, LSB first, Little Endian * * Returns: output - the value of the data (string or numeric) * ******************************************************************************/ function get_IFD_value_as_text( $Exif_Tag ) { // Create a string to receive the output text $output_str = ""; // Select Processing method according to the datatype switch ($Exif_Tag['Data Type']) { case 1 : // Unsigned Byte case 3 : // Unsigned Short case 4 : // Unsigned Long case 6 : // Signed Byte case 8 : // Signed Short case 9 : // Signed Long // Cycle through each of the values for this tag foreach ( $Exif_Tag['Data'] as $val ) { // Check that this isn't the first value, if ( $output_str != "" ) { // This isn't the first value, Add a Comma and Newline to the output $output_str .= ",\n"; } // Add the Value to the output $output_str .= $val; } break; case 2 : // ASCII // Append all the strings together, separated by Newlines $output_str .= implode ( "\n", $Exif_Tag['Data']); break; case 5 : // Unsigned Rational case 10: // Signed Rational // Cycle through each of the values for this tag foreach ( $Exif_Tag['Data'] as $val ) { // Check that this isn't the first value, if ( $output_str != "" ) { // This isn't the first value, Add a Comma and Newline to the output $output_str .= ",\n"; } // Add the Full Value to the output $output_str .= $val['Numerator'] ."/" . $val['Denominator']; // Check if division by zero might be a problem if ( $val['Denominator'] != 0 ) { // Denominator is not zero, Add the Decimal Value to the output text $output_str .= " (" . ($val['Numerator'] / $val['Denominator']) . ")"; } } break; case 11: // Float case 12: // Double // TODO - EXIF - IFD datatype Double and Float not implemented yet $output_str .= "Float and Double not implemented yet"; break; case 7 : // Undefined // Unless the User has asked to see the raw binary data, this // type should not be displayed // Check if the user has requested to see the binary data in hex if ( $GLOBALS['SHOW_BINARY_DATA_HEX'] == TRUE) { // User has requested to see the binary data in hex // Add the value in hex $output_str .= "( " . strlen( $Exif_Tag['Data'] ) . " bytes of binary data ): " . bin2hex( $Exif_Tag['Data'] ) ; } // Check if the user has requested to see the binary data as is else if ( $GLOBALS['SHOW_BINARY_DATA_TEXT'] == TRUE) { // User has requested to see the binary data as is // Add the value as is $output_str .= "( " . strlen( $Exif_Tag['Data'] ) . " bytes of binary data ): " . $Exif_Tag['Data'] ; } else { // User has NOT requested to see binary data, // Add a message indicating the number of bytes to the output $output_str .= "( " . strlen( $Exif_Tag['Data'] ) . " bytes of binary data ) " ; } break; default : // Error - Unknown IFD datatype $output_str .= "Error - Exif tag data type (" . $Exif_Tag['Data Type'] .") is invalid"; break; } // Return the resulting text string return $output_str; } /****************************************************************************** * End of Function: get_IFD_value_as_text ******************************************************************************/ /****************************************************************************** * Global Variable: IFD_Data_Sizes * * Contents: The sizes (in bytes) of each EXIF IFD Datatype, indexed by * their datatype number * ******************************************************************************/ $GLOBALS['IFD_Data_Sizes'] = array( 1 => 1, // Unsigned Byte 2 => 1, // ASCII String 3 => 2, // Unsigned Short 4 => 4, // Unsigned Long 5 => 8, // Unsigned Rational 6 => 1, // Signed Byte 7 => 1, // Undefined 8 => 2, // Signed Short 9 => 4, // Signed Long 10 => 8, // Signed Rational 11 => 4, // Float 12 => 8 ); // Double /****************************************************************************** * End of Global Variable: IFD_Data_Sizes ******************************************************************************/ ?> Steptail Photo Expo Warning: This version is old, and server settings have changed since. This program won't work properly.
Steptail Photo Expo
Gallery Name Description
May 2005 Video from Venezia, Italy
September 2005 Ljusdal, Sverige
January 2006 no description
Total: 3

Creative Commons License
This work is licensed under a
Creative Commons Attribution 1.0 Finland License.