<?php

/******************************************************************************
*
* Filename:     JPEG.php
*
* Description:  Provides functions for reading and writing information to/from
*               JPEG format files
*
* Author:       Evan Hunter
*
* Date:         23/7/2004
*
* Project:      PHP JPEG Metadata Toolkit
*
* Revision:     1.10
*
* Changes:      1.00 -> 1.10 : changed put_jpeg_header_data to check if the data
*                              being written exists
*
* URL:          http://electronics.ozhiker.com
*
* Copyright:    Copyright Evan Hunter 2004
*
* License:      This file is part of the PHP JPEG Metadata Toolkit.
*
*               The PHP JPEG Metadata Toolkit is free software; you can
*               redistribute it and/or modify it under the terms of the
*               GNU General Public License as published by the Free Software
*               Foundation; either version 2 of the License, or (at your
*               option) any later version.
*
*               The PHP JPEG Metadata Toolkit is distributed in the hope
*               that it will be useful, but WITHOUT ANY WARRANTY; without
*               even the implied warranty of MERCHANTABILITY or FITNESS
*               FOR A PARTICULAR PURPOSE.  See the GNU General Public License
*               for more details.
*
*               You should have received a copy of the GNU General Public
*               License along with the PHP JPEG Metadata Toolkit; if not,
*               write to the Free Software Foundation, Inc., 59 Temple
*               Place, Suite 330, Boston, MA  02111-1307  USA
*
*               If you require a different license for commercial or other
*               purposes, please contact the author: evan@ozhiker.com
*
******************************************************************************/




/******************************************************************************
*
* Function:     get_jpeg_header_data
*
* Description:  Reads all the JPEG header segments from an JPEG image file into an
*               array
*
* Parameters:   filename - the filename of the file to JPEG file to read
*
* Returns:      headerdata - Array of JPEG header segments
*               FALSE - if headers could not be read
*
******************************************************************************/

function get_jpeg_header_data( $filename )
{

        // prevent refresh from aborting file operations and hosing file
        ignore_user_abort(true);


        // Attempt to open the jpeg file - the at symbol supresses the error message about
        // not being able to open files. The file_exists would have been used, but it
        // does not work with files fetched over http or ftp.
        $filehnd = @fopen($filename, 'rb');

        // Check if the file opened successfully
        if ( ! $filehnd  )
        {
                // Could't open the file - exit
                error_log( "Could not open file $filename" );
                return FALSE;
        }


        // Read the first two characters
        $data = network_safe_fread( $filehnd, 2 );

        // Check that the first two characters are 0xFF 0xDA  (SOI - Start of image)
        if ( $data != "\xFF\xD8" )
        {
                // No SOI (FF D8) at start of file - This probably isn't a JPEG file - close file and return;
                error_log( "This probably is not a JPEG file" );
                fclose($filehnd);
                return FALSE;
        }


        // Read the third character
        $data = network_safe_fread( $filehnd, 2 );

        // Check that the third character is 0xFF (Start of first segment header)
        if ( $data{0} != "\xFF" )
        {
                // NO FF found - close file and return - JPEG is probably corrupted
                fclose($filehnd);
                return FALSE;
        }

        // Flag that we havent yet hit the compressed image data
        $hit_compressed_image_data = FALSE;


        // Cycle through the file until, one of: 1) an EOI (End of image) marker is hit,
        //                                       2) we have hit the compressed image data (no more headers are allowed after data)
        //                                       3) or end of file is hit

        while ( ( $data{1} != "\xD9" ) && (! $hit_compressed_image_data) && ( ! feof( $filehnd ) ))
        {
                // Found a segment to look at.
                // Check that the segment marker is not a Restart marker - restart markers don't have size or data after them
                if (  ( ord($data{1}) < 0xD0 ) || ( ord($data{1}) > 0xD7 ) )
                {
                        // Segment isn't a Restart marker
                        // Read the next two bytes (size)
                        $sizestr = network_safe_fread( $filehnd, 2 );

                        // convert the size bytes to an integer
                        $decodedsize = unpack ("nsize", $sizestr);

                        // Save the start position of the data
                        $segdatastart = ftell( $filehnd );

                        // Read the segment data with length indicated by the previously read size
                        $segdata = network_safe_fread( $filehnd, $decodedsize['size'] - 2 );


                        // Store the segment information in the output array
                        $headerdata[] = array(  "SegType" => ord($data{1}),
                                                "SegName" => $GLOBALS[ "JPEG_Segment_Names" ][ ord($data{1}) ],
                                                "SegDesc" => $GLOBALS[ "JPEG_Segment_Descriptions" ][ ord($data{1}) ],
                                                "SegDataStart" => $segdatastart,
                                                "SegData" => $segdata );
                }

                // If this is a SOS (Start Of Scan) segment, then there is no more header data - the compressed image data follows
                if ( $data{1} == "\xDA" )
                {
                        // Flag that we have hit the compressed image data - exit loop as no more headers available.
                        $hit_compressed_image_data = TRUE;
                }
                else
                {
                        // Not an SOS - Read the next two bytes - should be the segment marker for the next segment
                        $data = network_safe_fread( $filehnd, 2 );

                        // Check that the first byte of the two is 0xFF as it should be for a marker
                        if ( $data{0} != "\xFF" )
                        {
                                // NO FF found - close file and return - JPEG is probably corrupted
                                fclose($filehnd);
                                return FALSE;
                        }
                }
        }

        // Close File
        fclose($filehnd);
        // Alow the user to abort from now on
        ignore_user_abort(false);

        // Return the header data retrieved
        return $headerdata;
}


/******************************************************************************
* End of Function:     get_jpeg_header_data
******************************************************************************/




/******************************************************************************
*
* Function:     put_jpeg_header_data
*
* Description:  Writes JPEG header data into a JPEG file. Takes an array in the
*               same format as from get_jpeg_header_data, and combines it with
*               the image data of an existing JPEG file, to create a new JPEG file
*               WARNING: As this function will replace all JPEG headers,
*                        including SOF etc, it is best to read the jpeg headers
*                        from a file, alter them, then put them back on the same
*                        file. If a SOF segment wer to be transfered from one
*                        file to another, the image could become unreadable unless
*                        the images were idenical size and configuration
*
*
* Parameters:   old_filename - the JPEG file from which the image data will be retrieved
*               new_filename - the name of the new JPEG to create (can be same as old_filename)
*               jpeg_header_data - a JPEG header data array in the same format
*                                  as from get_jpeg_header_data
*
* Returns:      TRUE - on Success
*               FALSE - on Failure
*
******************************************************************************/

function put_jpeg_header_data( $old_filename, $new_filename, $jpeg_header_data )
{

        // Change: added check to ensure data exists, as of revision 1.10
        // Check if the data to be written exists
        if ( $jpeg_header_data == FALSE )
        {
                // Data to be written not valid - abort
                return FALSE;
        }

        // extract the compressed image data from the old file
        $compressed_image_data = get_jpeg_image_data( $old_filename );

        // Check if the extraction worked
        if ( ( $compressed_image_data === FALSE ) || ( $compressed_image_data === NULL ) )
        {
                // Couldn't get image data from old file
                return FALSE;
        }


        // Cycle through new headers
        foreach ($jpeg_header_data as $segno => $segment)
        {
                // Check that this header is smaller than the maximum size
                if ( strlen($segment['SegData']) > 0xfffd )
                {
                        // Could't open the file - exit
                        error_log( "A Header is too large to fit in JPEG segment" );
                        return FALSE;
                }
        }

        ignore_user_abort(true);    ## prevent refresh from aborting file operations and hosing file


        // Attempt to open the new jpeg file
        $newfilehnd = @fopen($new_filename, 'wb');
        // Check if the file opened successfully
        if ( ! $newfilehnd  )
        {
                // Could't open the file - exit
                error_log( "Could not open file $new_filename" );
                return FALSE;
        }

        // Write SOI
        fwrite( $newfilehnd, "\xFF\xD8" );

        // Cycle through new headers, writing them to the new file
        foreach ($jpeg_header_data as $segno => $segment)
        {

                // Write segment marker
                fwrite( $newfilehnd, sprintf( "\xFF%c", $segment['SegType'] ) );

                // Write segment size
                fwrite( $newfilehnd, pack( "n", strlen($segment['SegData']) + 2 ) );

                // Write segment data
                fwrite( $newfilehnd, $segment['SegData'] );
        }

        // Write the compressed image data
        fwrite( $newfilehnd, $compressed_image_data );

        // Write EOI
        fwrite( $newfilehnd, "\xFF\xD9" );

        // Close File
        fclose($newfilehnd);

        // Alow the user to abort from now on
        ignore_user_abort(false);


        return TRUE;

}

/******************************************************************************
* End of Function:     put_jpeg_header_data
******************************************************************************/



/******************************************************************************
*
* Function:     get_jpeg_Comment
*
* Description:  Retreives the contents of the JPEG Comment (COM = 0xFFFE) segment if one
*               exists
*
* Parameters:   jpeg_header_data - the JPEG header data, as retrieved
*                                  from the get_jpeg_header_data function
*
* Returns:      string - Contents of the Comment segement
*               FALSE - if the comment segment couldnt be found
*
******************************************************************************/

function get_jpeg_Comment( $jpeg_header_data )
{
        //Cycle through the header segments until COM is found or we run out of segments
        $i = 0;
        while ( ( $i < count( $jpeg_header_data) )  && ( $jpeg_header_data[$i]['SegName'] != "COM" ) )
        {
                $i++;
        }

        // Check if a COM segment has been found
        if (  $i < count( $jpeg_header_data) )
        {
                // A COM segment was found, return it's contents
                return $jpeg_header_data[$i]['SegData'];
        }
        else
        {
                // No COM segment found
                return FALSE;
        }
}

/******************************************************************************
* End of Function:     get_jpeg_Comment
******************************************************************************/


/******************************************************************************
*
* Function:     put_jpeg_Comment
*
* Description:  Creates a new JPEG Comment segment from a string, and inserts
*               this segment into the supplied JPEG header array
*
* Parameters:   jpeg_header_data - a JPEG header data array in the same format
*                                  as from get_jpeg_header_data, into which the
*                                  new Comment segment will be put
*               $new_Comment - a string containing the new Comment
*
* Returns:      jpeg_header_data - the JPEG header data array with the new
*                                  JPEG Comment segment added
*
******************************************************************************/

function put_jpeg_Comment( $jpeg_header_data, $new_Comment )
{
        //Cycle through the header segments
        for( $i = 0; $i < count( $jpeg_header_data ); $i++ )
        {
                // If we find an COM header,
                if ( strcmp ( $jpeg_header_data[$i]['SegName'], "COM" ) == 0 )
                {
                        // Found a preexisting Comment block - Replace it with the new one and return.
                        $jpeg_header_data[$i]['SegData'] = $new_Comment;
                        return $jpeg_header_data;
                }
        }



        // No preexisting Comment block found, find where to put it by searching for the highest app segment
        $i = 0;
        while ( ( $i < count( $jpeg_header_data ) ) && ( $jpeg_header_data[$i]["SegType"] >= 0xE0 ) )
        {
                $i++;
        }


        // insert a Comment segment new at the position found of the header data.
        array_splice($jpeg_header_data, $i , 0, array( array(   "SegType" => 0xFE,
                                                                "SegName" => $GLOBALS[ "JPEG_Segment_Names" ][ 0xFE ],
                                                                "SegDesc" => $GLOBALS[ "JPEG_Segment_Descriptions" ][ 0xFE ],
                                                                "SegData" => $new_Comment ) ) );
        return $jpeg_header_data;
}

/******************************************************************************
* End of Function:     put_jpeg_Comment
******************************************************************************/




/******************************************************************************
*
* Function:     Interpret_Comment_to_HTML
*
* Description:  Generates html showing the contents of any JPEG Comment segment
*
* Parameters:   jpeg_header_data - the JPEG header data, as retrieved
*                                  from the get_jpeg_header_data function
*
* Returns:      output - the HTML
*
******************************************************************************/

function Interpret_Comment_to_HTML( $jpeg_header_data )
{
        // Create a string to receive the output
        $output = "";

        // read the comment segment
        $comment = get_jpeg_Comment( $jpeg_header_data );

        // Check if the comment segment was valid
        if ( $comment !== FALSE )
        {
                // Comment exists - add it to the output
                $output .= "<h2 class=\"JPEG_Comment_Main_Heading\">JPEG Comment</h2>\n";
                $output .= "<p class=\"JPEG_Comment_Text\">$comment</p>\n";
        }

        // Return the result
        return $output;
}

/******************************************************************************
* End of Function:     Interpret_Comment_to_HTML
******************************************************************************/




/******************************************************************************
*
* Function:     get_jpeg_intrinsic_values
*
* Description:  Retreives information about the intrinsic characteristics of the
*               jpeg image, such as Bits per Component, Height and Width.
*
* Parameters:   jpeg_header_data - the JPEG header data, as retrieved
*                                  from the get_jpeg_header_data function
*
* Returns:      array - An array containing the intrinsic JPEG values
*               FALSE - if the comment segment couldnt be found
*
******************************************************************************/

function get_jpeg_intrinsic_values( $jpeg_header_data )
{
        // Create a blank array for the output
        $Outputarray = array( );

        //Cycle through the header segments until Start Of Frame (SOF) is found or we run out of segments
        $i = 0;
        while ( ( $i < count( $jpeg_header_data) )  && ( substr( $jpeg_header_data[$i]['SegName'], 0, 3 ) != "SOF" ) )
        {
                $i++;
        }

        // Check if a SOF segment has been found
        if ( substr( $jpeg_header_data[$i]['SegName'], 0, 3 ) == "SOF" )
        {
                // SOF segment was found, extract the information

                $data = $jpeg_header_data[$i]['SegData'];

                // First byte is Bits per component
                $Outputarray['Bits per Component'] = ord( $data{0} );

                // Second and third bytes are Image Height
                $Outputarray['Image Height'] = ord( $data{ 1 } ) * 256 + ord( $data{ 2 } );

                // Forth and fifth bytes are Image Width
                $Outputarray['Image Width'] = ord( $data{ 3 } ) * 256 + ord( $data{ 4 } );

                // Sixth byte is number of components
                $numcomponents = ord( $data{ 5 } );

                // Following this is a table containing information about the components
                for( $i = 0; $i < $numcomponents; $i++ )
                {
                        $Outputarray['Components'][] = array (  'Component Identifier' => ord( $data{ 6 + $i * 3 } ),
                                                                'Horizontal Sampling Factor' => ( ord( $data{ 7 + $i * 3 } ) & 0xF0 ) / 16,
                                                                'Vertical Sampling Factor' => ( ord( $data{ 7 + $i * 3 } ) & 0x0F ),
                                                                'Quantization table destination selector' => ord( $data{ 8 + $i * 3 } ) );
                }
        }
        else
        {
                // Couldn't find Start Of Frame segment, hence can't retrieve info
                return FALSE;
        }

        return $Outputarray;
}


/******************************************************************************
* End of Function:     get_jpeg_intrinsic_values
******************************************************************************/





/******************************************************************************
*
* Function:     Interpret_intrinsic_values_to_HTML
*
* Description:  Generates html showing some of the intrinsic JPEG values which
*               were retrieved with the get_jpeg_intrinsic_values function
*
* Parameters:   values - the JPEG intrinsic values, as read from get_jpeg_intrinsic_values
*
* Returns:      OutputStr - A string containing the HTML
*
******************************************************************************/

function Interpret_intrinsic_values_to_HTML( $values )
{
        // Check values are valid
        if ( $values != FALSE )
        {
                // Write Heading
                $OutputStr = "<h2 class=\"JPEG_Intrinsic_Main_Heading\">Intrinsic JPEG Information</h2>\n";

                // Create Table
                $OutputStr .= "<table class=\"JPEG_Intrinsic_Table\" border=1>\n";

                // Put image height and width into table
                $OutputStr .= "<tr class=\"JPEG_Intrinsic_Table_Row\"><td class=\"JPEG_Intrinsic_Caption_Cell\">Image Height</td><td class=\"JPEG_Intrinsic_Value_Cell\">" . $values['Image Height'] . " pixels</td></tr>\n";
                $OutputStr .= "<tr class=\"JPEG_Intrinsic_Table_Row\"><td class=\"JPEG_Intrinsic_Caption_Cell\">Image Width</td><td class=\"JPEG_Intrinsic_Value_Cell\">" . $values['Image Width'] . " pixels</td></tr>\n";

                // Put colour depth into table
                if ( count( $values['Components'] ) == 1 )
                {
                        $OutputStr .= "<tr class=\"JPEG_Intrinsic_Table_Row\"><td class=\"JPEG_Intrinsic_Caption_Cell\">Colour Depth</td><td class=\"JPEG_Intrinsic_Value_Cell\">" . $values['Bits per Component'] . " bit Monochrome</td></tr>\n";
                }
                else
                {
                        $OutputStr .= "<tr class=\"JPEG_Intrinsic_Table_Row\"><td class=\"JPEG_Intrinsic_Caption_Cell\">Colour Depth</td><td class=\"JPEG_Intrinsic_Value_Cell\">" . ($values['Bits per Component'] * count( $values['Components'] ) ) . " bit</td></tr>\n";
                }

                // Close Table
                $OutputStr .= "</table>\n";

                // Return html
                return $OutputStr;
        }
}

/******************************************************************************
* End of Function:     Interpret_intrinsic_values_to_HTML
******************************************************************************/







/******************************************************************************
*
* Function:     get_jpeg_image_data
*
* Description:  Retrieves the compressed image data part of the JPEG file
*
* Parameters:   filename - the filename of the JPEG file to read
*
* Returns:      compressed_data - A string containing the compressed data
*               FALSE - if retrieval failed
*
******************************************************************************/

function get_jpeg_image_data( $filename )
{

        // prevent refresh from aborting file operations and hosing file
        ignore_user_abort(true);

        // Attempt to open the jpeg file
        $filehnd = @fopen($filename, 'rb');

        // Check if the file opened successfully
        if ( ! $filehnd  )
        {
                // Could't open the file - exit
                return FALSE;
        }


        // Read the first two characters
        $data = network_safe_fread( $filehnd, 2 );

        // Check that the first two characters are 0xFF 0xDA  (SOI - Start of image)
        if ( $data != "\xFF\xD8" )
        {
                // No SOI (FF D8) at start of file - close file and return;
                fclose($filehnd);
                return FALSE;
        }



        // Read the third character
        $data = network_safe_fread( $filehnd, 2 );

        // Check that the third character is 0xFF (Start of first segment header)
        if ( $data{0} != "\xFF" )
        {
                // NO FF found - close file and return
                fclose($filehnd);
                return;
        }

        // Flag that we havent yet hit the compressed image data
        $hit_compressed_image_data = FALSE;


        // Cycle through the file until, one of: 1) an EOI (End of image) marker is hit,
        //                                       2) we have hit the compressed image data (no more headers are allowed after data)
        //                                       3) or end of file is hit

        while ( ( $data{1} != "\xD9" ) && (! $hit_compressed_image_data) && ( ! feof( $filehnd ) ))
        {
                // Found a segment to look at.
                // Check that the segment marker is not a Restart marker - restart markers don't have size or data after them
                if (  ( ord($data{1}) < 0xD0 ) || ( ord($data{1}) > 0xD7 ) )
                {
                        // Segment isn't a Restart marker
                        // Read the next two bytes (size)
                        $sizestr = network_safe_fread( $filehnd, 2 );

                        // convert the size bytes to an integer
                        $decodedsize = unpack ("nsize", $sizestr);

                         // Read the segment data with length indicated by the previously read size
                        $segdata = network_safe_fread( $filehnd, $decodedsize['size'] - 2 );
                }

                // If this is a SOS (Start Of Scan) segment, then there is no more header data - the compressed image data follows
                if ( $data{1} == "\xDA" )
                {
                        // Flag that we have hit the compressed image data - exit loop after reading the data
                        $hit_compressed_image_data = TRUE;

                        // read the rest of the file in
                        // Can't use the filesize function to work out
                        // how much to read, as it won't work for files being read by http or ftp
                        // So instead read 1Mb at a time till EOF

                        $compressed_data = "";
                        do
                        {
                                $compressed_data .= network_safe_fread( $filehnd, 1048576 );
                        } while( ! feof( $filehnd ) );

                        // Strip off EOI and anything after
                        $EOI_pos = strpos( $compressed_data, "\xFF\xD9" );
                        $compressed_data = substr( $compressed_data, 0, $EOI_pos );
                }
                else
                {
                        // Not an SOS - Read the next two bytes - should be the segment marker for the next segment
                        $data = network_safe_fread( $filehnd, 2 );

                        // Check that the first byte of the two is 0xFF as it should be for a marker
                        if ( $data{0} != "\xFF" )
                        {
                                // Problem - NO FF foundclose file and return";
                                fclose($filehnd);
                                return;
                        }
                }
        }

        // Close File
        fclose($filehnd);

        // Alow the user to abort from now on
        ignore_user_abort(false);


        // Return the compressed data if it was found
        if ( $hit_compressed_image_data )
        {
                return $compressed_data;
        }
        else
        {
                return FALSE;
        }
}


/******************************************************************************
* End of Function:     get_jpeg_image_data
******************************************************************************/







/******************************************************************************
*
* Function:     Generate_JPEG_APP_Segment_HTML
*
* Description:  Generates html showing information about the Application (APP)
*               segments which are present in the JPEG file
*
* Parameters:   jpeg_header_data - the JPEG header data, as retrieved
*                                  from the get_jpeg_header_data function
*
* Returns:      output - A string containing the HTML
*
******************************************************************************/

function Generate_JPEG_APP_Segment_HTML( $jpeg_header_data )
{
        if ( $jpeg_header_data == FALSE )
        {
                return "";
        }


        // Write Heading
        $output = "<h2 class=\"JPEG_APP_Segments_Main_Heading\">Application Metadata Segments</h2>\n";

        // Create table
        $output .= "<table class=\"JPEG_APP_Segments_Table\" border=1>\n";


        // Cycle through each segment in the array

        foreach( $jpeg_header_data as $jpeg_header )
        {

                // Check if the segment is a APP segment

                if ( ( $jpeg_header['SegType'] >= 0xE0 ) && ( $jpeg_header['SegType'] <= 0xEF ) )
                {
                        // This is an APP segment

                        // Read APP Segment Name - a Null terminated string at the start of the segment
                        $seg_name = strtok($jpeg_header['SegData'], "\x00");

                        // Some Segment names are either too long or not meaningfull, so
                        // we should clean them up

                        if ( $seg_name == "http://ns.adobe.com/xap/1.0/" )
                        {
                                $seg_name = "XAP/RDF (\"http://ns.adobe.com/xap/1.0/\")";
                        }
                        elseif ( $seg_name == "Photoshop 3.0" )
                        {
                                $seg_name = "Photoshop IRB (\"Photoshop 3.0\")";
                        }
                        elseif ( ( strncmp ( $seg_name, "[picture info]", 14) == 0 ) ||
                                 ( strncmp ( $seg_name, "\x0a\x09\x09\x09\x09[picture info]", 19) == 0 ) )
                        {
                                $seg_name = "[picture info]";
                        }
                        elseif (  strncmp ( $seg_name, "Type=", 5) == 0 )
                        {
                                $seg_name = "Epson Info";
                        }
                        elseif ( ( strncmp ( $seg_name, "HHHHHHHHHHHHHHH", 15) == 0 ) ||
                                 ( strncmp ( $seg_name, "@s33", 5) == 0 ) )
                        {
                                $seg_name = "HP segment full of \"HHHHH\"";
                        }


                        // Clean the segment name so it doesn't cause problems with HTML
                        $seg_name = htmlentities( $seg_name );

                        // Output a Table row containing this APP segment
                        $output .= "<tr class=\"JPEG_APP_Segments_Table_Row\"><td class=\"JPEG_APP_Segments_Caption_Cell\">$seg_name</td><td class=\"JPEG_APP_Segments_Type_Cell\">" . $jpeg_header['SegName'] . "</td><td  class=\"JPEG_APP_Segments_Size_Cell\" align=\"right\">" . strlen( $jpeg_header['SegData']). " bytes</td></tr>\n";
                }
        }

        // Close the table
        $output .= "</table>\n";

        // Return the HTML
        return $output;
}


/******************************************************************************
* End of Function:     Generate_JPEG_APP_Segment_HTML
******************************************************************************/




/******************************************************************************
*
* Function:     network_safe_fread
*
* Description:  Retrieves data from a file. This function is required since
*               the fread function will not always return the requested number
*               of characters when reading from a network stream or pipe
*
* Parameters:   file_handle - the handle of a file to read from
*               length - the number of bytes requested
*
* Returns:      data - the data read from the file. may be less than the number
*                      requested if EOF was hit
*
******************************************************************************/

function network_safe_fread( $file_handle, $length )
{
        // Create blank string to receive data
        $data = "";

        // Keep reading data from the file until either EOF occurs or we have
        // retrieved the requested number of bytes

        while ( ( !feof( $file_handle ) ) && ( strlen($data) < $length ) )
        {
                $data .= fread( $file_handle, $length-strlen($data) );
        }

        // return the data read
        return $data;
}

/******************************************************************************
* End of Function:     network_safe_fread
******************************************************************************/




/******************************************************************************
* Global Variable:      JPEG_Segment_Names
*
* Contents:     The names of the JPEG segment markers, indexed by their marker number
*
******************************************************************************/

$GLOBALS[ "JPEG_Segment_Names" ] = array(

0xC0 =>  "SOF0",  0xC1 =>  "SOF1",  0xC2 =>  "SOF2",  0xC3 =>  "SOF4",
0xC5 =>  "SOF5",  0xC6 =>  "SOF6",  0xC7 =>  "SOF7",  0xC8 =>  "JPG",
0xC9 =>  "SOF9",  0xCA =>  "SOF10", 0xCB =>  "SOF11", 0xCD =>  "SOF13",
0xCE =>  "SOF14", 0xCF =>  "SOF15",
0xC4 =>  "DHT",   0xCC =>  "DAC",

0xD0 =>  "RST0",  0xD1 =>  "RST1",  0xD2 =>  "RST2",  0xD3 =>  "RST3",
0xD4 =>  "RST4",  0xD5 =>  "RST5",  0xD6 =>  "RST6",  0xD7 =>  "RST7",

0xD8 =>  "SOI",   0xD9 =>  "EOI",   0xDA =>  "SOS",   0xDB =>  "DQT",
0xDC =>  "DNL",   0xDD =>  "DRI",   0xDE =>  "DHP",   0xDF =>  "EXP",

0xE0 =>  "APP0",  0xE1 =>  "APP1",  0xE2 =>  "APP2",  0xE3 =>  "APP3",
0xE4 =>  "APP4",  0xE5 =>  "APP5",  0xE6 =>  "APP6",  0xE7 =>  "APP7",
0xE8 =>  "APP8",  0xE9 =>  "APP9",  0xEA =>  "APP10", 0xEB =>  "APP11",
0xEC =>  "APP12", 0xED =>  "APP13", 0xEE =>  "APP14", 0xEF =>  "APP15",


0xF0 =>  "JPG0",  0xF1 =>  "JPG1",  0xF2 =>  "JPG2",  0xF3 =>  "JPG3",
0xF4 =>  "JPG4",  0xF5 =>  "JPG5",  0xF6 =>  "JPG6",  0xF7 =>  "JPG7",
0xF8 =>  "JPG8",  0xF9 =>  "JPG9",  0xFA =>  "JPG10", 0xFB =>  "JPG11",
0xFC =>  "JPG12", 0xFD =>  "JPG13",

0xFE =>  "COM",   0x01 =>  "TEM",   0x02 =>  "RES",

);

/******************************************************************************
* End of Global Variable:     JPEG_Segment_Names
******************************************************************************/


/******************************************************************************
* Global Variable:      JPEG_Segment_Descriptions
*
* Contents:     The descriptions of the JPEG segment markers, indexed by their marker number
*
******************************************************************************/

$GLOBALS[ "JPEG_Segment_Descriptions" ] = array(

/* JIF Marker byte pairs in JPEG Interchange Format sequence */
0xC0 => "Start Of Frame (SOF) Huffman  - Baseline DCT",
0xC1 =>  "Start Of Frame (SOF) Huffman  - Extended sequential DCT",
0xC2 =>  "Start Of Frame Huffman  - Progressive DCT (SOF2)",
0xC3 =>  "Start Of Frame Huffman  - Spatial (sequential) lossless (SOF3)",
0xC5 =>  "Start Of Frame Huffman  - Differential sequential DCT (SOF5)",
0xC6 =>  "Start Of Frame Huffman  - Differential progressive DCT (SOF6)",
0xC7 =>  "Start Of Frame Huffman  - Differential spatial (SOF7)",
0xC8 =>  "Start Of Frame Arithmetic - Reserved for JPEG extensions (JPG)",
0xC9 =>  "Start Of Frame Arithmetic - Extended sequential DCT (SOF9)",
0xCA =>  "Start Of Frame Arithmetic - Progressive DCT (SOF10)",
0xCB =>  "Start Of Frame Arithmetic - Spatial (sequential) lossless (SOF11)",
0xCD =>  "Start Of Frame Arithmetic - Differential sequential DCT (SOF13)",
0xCE =>  "Start Of Frame Arithmetic - Differential progressive DCT (SOF14)",
0xCF =>  "Start Of Frame Arithmetic - Differential spatial (SOF15)",
0xC4 =>  "Define Huffman Table(s) (DHT)",
0xCC =>  "Define Arithmetic coding conditioning(s) (DAC)",

0xD0 =>  "Restart with modulo 8 count 0 (RST0)",
0xD1 =>  "Restart with modulo 8 count 1 (RST1)",
0xD2 =>  "Restart with modulo 8 count 2 (RST2)",
0xD3 =>  "Restart with modulo 8 count 3 (RST3)",
0xD4 =>  "Restart with modulo 8 count 4 (RST4)",
0xD5 =>  "Restart with modulo 8 count 5 (RST5)",
0xD6 =>  "Restart with modulo 8 count 6 (RST6)",
0xD7 =>  "Restart with modulo 8 count 7 (RST7)",

0xD8 =>  "Start of Image (SOI)",
0xD9 =>  "End of Image (EOI)",
0xDA =>  "Start of Scan (SOS)",
0xDB =>  "Define quantization Table(s) (DQT)",
0xDC =>  "Define Number of Lines (DNL)",
0xDD =>  "Define Restart Interval (DRI)",
0xDE =>  "Define Hierarchical progression (DHP)",
0xDF =>  "Expand Reference Component(s) (EXP)",

0xE0 =>  "Application Field 0 (APP0) - usually JFIF or JFXX",
0xE1 =>  "Application Field 1 (APP1) - usually EXIF or XMP/RDF",
0xE2 =>  "Application Field 2 (APP2) - usually Flashpix",
0xE3 =>  "Application Field 3 (APP3)",
0xE4 =>  "Application Field 4 (APP4)",
0xE5 =>  "Application Field 5 (APP5)",
0xE6 =>  "Application Field 6 (APP6)",
0xE7 =>  "Application Field 7 (APP7)",

0xE8 =>  "Application Field 8 (APP8)",
0xE9 =>  "Application Field 9 (APP9)",
0xEA =>  "Application Field 10 (APP10)",
0xEB =>  "Application Field 11 (APP11)",
0xEC =>  "Application Field 12 (APP12) - usually [picture info]",
0xED =>  "Application Field 13 (APP13) - usually photoshop IRB / IPTC",
0xEE =>  "Application Field 14 (APP14)",
0xEF =>  "Application Field 15 (APP15)",


0xF0 =>  "Reserved for JPEG extensions (JPG0)",
0xF1 =>  "Reserved for JPEG extensions (JPG1)",
0xF2 =>  "Reserved for JPEG extensions (JPG2)",
0xF3 =>  "Reserved for JPEG extensions (JPG3)",
0xF4 =>  "Reserved for JPEG extensions (JPG4)",
0xF5 =>  "Reserved for JPEG extensions (JPG5)",
0xF6 =>  "Reserved for JPEG extensions (JPG6)",
0xF7 =>  "Reserved for JPEG extensions (JPG7)",
0xF8 =>  "Reserved for JPEG extensions (JPG8)",
0xF9 =>  "Reserved for JPEG extensions (JPG9)",
0xFA =>  "Reserved for JPEG extensions (JPG10)",
0xFB =>  "Reserved for JPEG extensions (JPG11)",
0xFC =>  "Reserved for JPEG extensions (JPG12)",
0xFD =>  "Reserved for JPEG extensions (JPG13)",

0xFE =>  "Comment (COM)",
0x01 =>  "For temp private use arith code (TEM)",
0x02 =>  "Reserved (RES)",

);

/******************************************************************************
* End of Global Variable:     JPEG_Segment_Descriptions
******************************************************************************/



?>
