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 ******************************************************************************/ ?>
Notice: Use of undefined constant m - assumed 'm' in /home/www/omolini/photoexpo/photoexpo-0.8.5/index.php on line 79

Notice: Undefined index: m in /home/www/omolini/photoexpo/photoexpo-0.8.5/index.php on line 79

Notice: Use of undefined constant g - assumed 'g' in /home/www/omolini/photoexpo/photoexpo-0.8.5/index.php on line 80

Notice: Undefined index: g in /home/www/omolini/photoexpo/photoexpo-0.8.5/index.php on line 80

Notice: Use of undefined constant i - assumed 'i' in /home/www/omolini/photoexpo/photoexpo-0.8.5/index.php on line 81

Notice: Undefined index: i in /home/www/omolini/photoexpo/photoexpo-0.8.5/index.php on line 81
Steptail Photo Expo
Steptail Photo Expo


Notice: Undefined variable: grayselect in /home/www/omolini/photoexpo/photoexpo-0.8.5/index.php on line 135
Gallery Name Description
September 2005 Photographs from Ljusdal, Sweden
Total: 1

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