1 | <?php |
---|
2 | |
---|
3 | /****************************************************************************** |
---|
4 | * |
---|
5 | * Filename: JPEG.php |
---|
6 | * |
---|
7 | * Description: Provides functions for reading and writing information to/from |
---|
8 | * JPEG format files |
---|
9 | * |
---|
10 | * Author: Evan Hunter |
---|
11 | * |
---|
12 | * Date: 23/7/2004 |
---|
13 | * |
---|
14 | * Project: PHP JPEG Metadata Toolkit |
---|
15 | * |
---|
16 | * Revision: 1.10 |
---|
17 | * |
---|
18 | * Changes: 1.00 -> 1.10 : changed put_jpeg_header_data to check if the data |
---|
19 | * being written exists |
---|
20 | * |
---|
21 | * URL: http://electronics.ozhiker.com |
---|
22 | * |
---|
23 | * Copyright: Copyright " . $auteur . " 2004 |
---|
24 | * |
---|
25 | * License: This file is part of the PHP JPEG Metadata Toolkit. |
---|
26 | * |
---|
27 | * The PHP JPEG Metadata Toolkit is free software; you can |
---|
28 | * redistribute it and/or modify it under the terms of the |
---|
29 | * GNU General Public License as published by the Free Software |
---|
30 | * Foundation; either version 2 of the License, or (at your |
---|
31 | * option) any later version. |
---|
32 | * |
---|
33 | * The PHP JPEG Metadata Toolkit is distributed in the hope |
---|
34 | * that it will be useful, but WITHOUT ANY WARRANTY; without |
---|
35 | * even the implied warranty of MERCHANTABILITY or FITNESS |
---|
36 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License |
---|
37 | * for more details. |
---|
38 | * |
---|
39 | * You should have received a copy of the GNU General Public |
---|
40 | * License along with the PHP JPEG Metadata Toolkit; if not, |
---|
41 | * write to the Free Software Foundation, Inc., 59 Temple |
---|
42 | * Place, Suite 330, Boston, MA 02111-1307 USA |
---|
43 | * |
---|
44 | * If you require a different license for commercial or other |
---|
45 | * purposes, please contact the author: evan@ozhiker.com |
---|
46 | * |
---|
47 | ******************************************************************************/ |
---|
48 | |
---|
49 | |
---|
50 | |
---|
51 | |
---|
52 | /****************************************************************************** |
---|
53 | * |
---|
54 | * Function: get_jpeg_header_data |
---|
55 | * |
---|
56 | * Description: Reads all the JPEG header segments from an JPEG image file into an |
---|
57 | * array |
---|
58 | * |
---|
59 | * Parameters: filename - the filename of the file to JPEG file to read |
---|
60 | * |
---|
61 | * Returns: headerdata - Array of JPEG header segments |
---|
62 | * FALSE - if headers could not be read |
---|
63 | * |
---|
64 | ******************************************************************************/ |
---|
65 | |
---|
66 | function get_jpeg_header_data( $filename ) |
---|
67 | { |
---|
68 | |
---|
69 | // prevent refresh from aborting file operations and hosing file |
---|
70 | ignore_user_abort(true); |
---|
71 | |
---|
72 | |
---|
73 | // Attempt to open the jpeg file - the at symbol supresses the error message about |
---|
74 | // not being able to open files. The file_exists would have been used, but it |
---|
75 | // does not work with files fetched over http or ftp. |
---|
76 | $filehnd = @fopen($filename, 'rb'); |
---|
77 | |
---|
78 | // Check if the file opened successfully |
---|
79 | if ( ! $filehnd ) |
---|
80 | { |
---|
81 | // Could't open the file - exit |
---|
82 | echo "<p>Could not open file $filename</p>\n"; |
---|
83 | return FALSE; |
---|
84 | } |
---|
85 | |
---|
86 | |
---|
87 | // Read the first two characters |
---|
88 | $data = network_safe_fread( $filehnd, 2 ); |
---|
89 | |
---|
90 | // Check that the first two characters are 0xFF 0xDA (SOI - Start of image) |
---|
91 | if ( $data != "\xFF\xD8" ) |
---|
92 | { |
---|
93 | // No SOI (FF D8) at start of file - This probably isn't a JPEG file - close file and return; |
---|
94 | echo "<p>This probably is not a JPEG file</p>\n"; |
---|
95 | fclose($filehnd); |
---|
96 | return FALSE; |
---|
97 | } |
---|
98 | |
---|
99 | |
---|
100 | // Read the third character |
---|
101 | $data = network_safe_fread( $filehnd, 2 ); |
---|
102 | |
---|
103 | // Check that the third character is 0xFF (Start of first segment header) |
---|
104 | if ( $data{0} != "\xFF" ) |
---|
105 | { |
---|
106 | // NO FF found - close file and return - JPEG is probably corrupted |
---|
107 | fclose($filehnd); |
---|
108 | return FALSE; |
---|
109 | } |
---|
110 | |
---|
111 | // Flag that we havent yet hit the compressed image data |
---|
112 | $hit_compressed_image_data = FALSE; |
---|
113 | |
---|
114 | |
---|
115 | // Cycle through the file until, one of: 1) an EOI (End of image) marker is hit, |
---|
116 | // 2) we have hit the compressed image data (no more headers are allowed after data) |
---|
117 | // 3) or end of file is hit |
---|
118 | |
---|
119 | while ( ( $data{1} != "\xD9" ) && (! $hit_compressed_image_data) && ( ! feof( $filehnd ) )) |
---|
120 | { |
---|
121 | // Found a segment to look at. |
---|
122 | // Check that the segment marker is not a Restart marker - restart markers don't have size or data after them |
---|
123 | if ( ( ord($data{1}) < 0xD0 ) || ( ord($data{1}) > 0xD7 ) ) |
---|
124 | { |
---|
125 | // Segment isn't a Restart marker |
---|
126 | // Read the next two bytes (size) |
---|
127 | $sizestr = network_safe_fread( $filehnd, 2 ); |
---|
128 | |
---|
129 | // convert the size bytes to an integer |
---|
130 | $decodedsize = unpack ("nsize", $sizestr); |
---|
131 | |
---|
132 | // Save the start position of the data |
---|
133 | $segdatastart = ftell( $filehnd ); |
---|
134 | |
---|
135 | // Read the segment data with length indicated by the previously read size |
---|
136 | $segdata = network_safe_fread( $filehnd, $decodedsize['size'] - 2 ); |
---|
137 | |
---|
138 | |
---|
139 | // Store the segment information in the output array |
---|
140 | $headerdata[] = array( "SegType" => ord($data{1}), |
---|
141 | "SegName" => $GLOBALS[ "JPEG_Segment_Names" ][ ord($data{1}) ], |
---|
142 | "SegDesc" => $GLOBALS[ "JPEG_Segment_Descriptions" ][ ord($data{1}) ], |
---|
143 | "SegDataStart" => $segdatastart, |
---|
144 | "SegData" => $segdata ); |
---|
145 | } |
---|
146 | |
---|
147 | // If this is a SOS (Start Of Scan) segment, then there is no more header data - the compressed image data follows |
---|
148 | if ( $data{1} == "\xDA" ) |
---|
149 | { |
---|
150 | // Flag that we have hit the compressed image data - exit loop as no more headers available. |
---|
151 | $hit_compressed_image_data = TRUE; |
---|
152 | } |
---|
153 | else |
---|
154 | { |
---|
155 | // Not an SOS - Read the next two bytes - should be the segment marker for the next segment |
---|
156 | $data = network_safe_fread( $filehnd, 2 ); |
---|
157 | |
---|
158 | // Check that the first byte of the two is 0xFF as it should be for a marker |
---|
159 | if ( $data{0} != "\xFF" ) |
---|
160 | { |
---|
161 | // NO FF found - close file and return - JPEG is probably corrupted |
---|
162 | fclose($filehnd); |
---|
163 | return FALSE; |
---|
164 | } |
---|
165 | } |
---|
166 | } |
---|
167 | |
---|
168 | // Close File |
---|
169 | fclose($filehnd); |
---|
170 | // Alow the user to abort from now on |
---|
171 | ignore_user_abort(false); |
---|
172 | |
---|
173 | // Return the header data retrieved |
---|
174 | return $headerdata; |
---|
175 | } |
---|
176 | |
---|
177 | |
---|
178 | /****************************************************************************** |
---|
179 | * End of Function: get_jpeg_header_data |
---|
180 | ******************************************************************************/ |
---|
181 | |
---|
182 | |
---|
183 | |
---|
184 | |
---|
185 | /****************************************************************************** |
---|
186 | * |
---|
187 | * Function: put_jpeg_header_data |
---|
188 | * |
---|
189 | * Description: Writes JPEG header data into a JPEG file. Takes an array in the |
---|
190 | * same format as from get_jpeg_header_data, and combines it with |
---|
191 | * the image data of an existing JPEG file, to create a new JPEG file |
---|
192 | * WARNING: As this function will replace all JPEG headers, |
---|
193 | * including SOF etc, it is best to read the jpeg headers |
---|
194 | * from a file, alter them, then put them back on the same |
---|
195 | * file. If a SOF segment wer to be transfered from one |
---|
196 | * file to another, the image could become unreadable unless |
---|
197 | * the images were idenical size and configuration |
---|
198 | * |
---|
199 | * |
---|
200 | * Parameters: old_filename - the JPEG file from which the image data will be retrieved |
---|
201 | * new_filename - the name of the new JPEG to create (can be same as old_filename) |
---|
202 | * jpeg_header_data - a JPEG header data array in the same format |
---|
203 | * as from get_jpeg_header_data |
---|
204 | * |
---|
205 | * Returns: TRUE - on Success |
---|
206 | * FALSE - on Failure |
---|
207 | * |
---|
208 | ******************************************************************************/ |
---|
209 | |
---|
210 | function put_jpeg_header_data( $old_filename, $new_filename, $jpeg_header_data ) |
---|
211 | { |
---|
212 | |
---|
213 | // Change: added check to ensure data exists, as of revision 1.10 |
---|
214 | // Check if the data to be written exists |
---|
215 | if ( $jpeg_header_data == FALSE ) |
---|
216 | { |
---|
217 | // Data to be written not valid - abort |
---|
218 | return FALSE; |
---|
219 | } |
---|
220 | |
---|
221 | // extract the compressed image data from the old file |
---|
222 | $compressed_image_data = get_jpeg_image_data( $old_filename ); |
---|
223 | |
---|
224 | // Check if the extraction worked |
---|
225 | if ( ( $compressed_image_data === FALSE ) || ( $compressed_image_data === NULL ) ) |
---|
226 | { |
---|
227 | // Couldn't get image data from old file |
---|
228 | return FALSE; |
---|
229 | } |
---|
230 | |
---|
231 | |
---|
232 | // Cycle through new headers |
---|
233 | foreach ($jpeg_header_data as $segno => $segment) |
---|
234 | { |
---|
235 | // Check that this header is smaller than the maximum size |
---|
236 | if ( strlen($segment['SegData']) > 0xfffd ) |
---|
237 | { |
---|
238 | // Could't open the file - exit |
---|
239 | echo "<p>A Header is too large to fit in JPEG segment</p>\n"; |
---|
240 | return FALSE; |
---|
241 | } |
---|
242 | } |
---|
243 | |
---|
244 | ignore_user_abort(true); ## prevent refresh from aborting file operations and hosing file |
---|
245 | |
---|
246 | |
---|
247 | // Attempt to open the new jpeg file |
---|
248 | $newfilehnd = @fopen($new_filename, 'wb'); |
---|
249 | // Check if the file opened successfully |
---|
250 | if ( ! $newfilehnd ) |
---|
251 | { |
---|
252 | // Could't open the file - exit |
---|
253 | echo "<p>Could not open file $new_filename</p>\n"; |
---|
254 | return FALSE; |
---|
255 | } |
---|
256 | |
---|
257 | // Write SOI |
---|
258 | fwrite( $newfilehnd, "\xFF\xD8" ); |
---|
259 | |
---|
260 | // Cycle through new headers, writing them to the new file |
---|
261 | foreach ($jpeg_header_data as $segno => $segment) |
---|
262 | { |
---|
263 | |
---|
264 | // Write segment marker |
---|
265 | fwrite( $newfilehnd, sprintf( "\xFF%c", $segment['SegType'] ) ); |
---|
266 | |
---|
267 | // Write segment size |
---|
268 | fwrite( $newfilehnd, pack( "n", strlen($segment['SegData']) + 2 ) ); |
---|
269 | |
---|
270 | // Write segment data |
---|
271 | fwrite( $newfilehnd, $segment['SegData'] ); |
---|
272 | } |
---|
273 | |
---|
274 | // Write the compressed image data |
---|
275 | fwrite( $newfilehnd, $compressed_image_data ); |
---|
276 | |
---|
277 | // Write EOI |
---|
278 | fwrite( $newfilehnd, "\xFF\xD9" ); |
---|
279 | |
---|
280 | // Close File |
---|
281 | fclose($newfilehnd); |
---|
282 | |
---|
283 | // Alow the user to abort from now on |
---|
284 | ignore_user_abort(false); |
---|
285 | |
---|
286 | |
---|
287 | return TRUE; |
---|
288 | |
---|
289 | } |
---|
290 | |
---|
291 | /****************************************************************************** |
---|
292 | * End of Function: put_jpeg_header_data |
---|
293 | ******************************************************************************/ |
---|
294 | |
---|
295 | |
---|
296 | |
---|
297 | /****************************************************************************** |
---|
298 | * |
---|
299 | * Function: get_jpeg_Comment |
---|
300 | * |
---|
301 | * Description: Retreives the contents of the JPEG Comment (COM = 0xFFFE) segment if one |
---|
302 | * exists |
---|
303 | * |
---|
304 | * Parameters: jpeg_header_data - the JPEG header data, as retrieved |
---|
305 | * from the get_jpeg_header_data function |
---|
306 | * |
---|
307 | * Returns: string - Contents of the Comment segement |
---|
308 | * FALSE - if the comment segment couldnt be found |
---|
309 | * |
---|
310 | ******************************************************************************/ |
---|
311 | |
---|
312 | function get_jpeg_Comment( $jpeg_header_data ) |
---|
313 | { |
---|
314 | //Cycle through the header segments until COM is found or we run out of segments |
---|
315 | $i = 0; |
---|
316 | while ( ( $i < count( $jpeg_header_data) ) && ( $jpeg_header_data[$i]['SegName'] != "COM" ) ) |
---|
317 | { |
---|
318 | $i++; |
---|
319 | } |
---|
320 | |
---|
321 | // Check if a COM segment has been found |
---|
322 | if ( $i < count( $jpeg_header_data) ) |
---|
323 | { |
---|
324 | // A COM segment was found, return it's contents |
---|
325 | return $jpeg_header_data[$i]['SegData']; |
---|
326 | } |
---|
327 | else |
---|
328 | { |
---|
329 | // No COM segment found |
---|
330 | return FALSE; |
---|
331 | } |
---|
332 | } |
---|
333 | |
---|
334 | /****************************************************************************** |
---|
335 | * End of Function: get_jpeg_Comment |
---|
336 | ******************************************************************************/ |
---|
337 | |
---|
338 | |
---|
339 | /****************************************************************************** |
---|
340 | * |
---|
341 | * Function: put_jpeg_Comment |
---|
342 | * |
---|
343 | * Description: Creates a new JPEG Comment segment from a string, and inserts |
---|
344 | * this segment into the supplied JPEG header array |
---|
345 | * |
---|
346 | * Parameters: jpeg_header_data - a JPEG header data array in the same format |
---|
347 | * as from get_jpeg_header_data, into which the |
---|
348 | * new Comment segment will be put |
---|
349 | * $new_Comment - a string containing the new Comment |
---|
350 | * |
---|
351 | * Returns: jpeg_header_data - the JPEG header data array with the new |
---|
352 | * JPEG Comment segment added |
---|
353 | * |
---|
354 | ******************************************************************************/ |
---|
355 | |
---|
356 | function put_jpeg_Comment( $jpeg_header_data, $new_Comment ) |
---|
357 | { |
---|
358 | //Cycle through the header segments |
---|
359 | for( $i = 0; $i < count( $jpeg_header_data ); $i++ ) |
---|
360 | { |
---|
361 | // If we find an COM header, |
---|
362 | if ( strcmp ( $jpeg_header_data[$i]['SegName'], "COM" ) == 0 ) |
---|
363 | { |
---|
364 | // Found a preexisting Comment block - Replace it with the new one and return. |
---|
365 | $jpeg_header_data[$i]['SegData'] = $new_Comment; |
---|
366 | return $jpeg_header_data; |
---|
367 | } |
---|
368 | } |
---|
369 | |
---|
370 | |
---|
371 | |
---|
372 | // No preexisting Comment block found, find where to put it by searching for the highest app segment |
---|
373 | $i = 0; |
---|
374 | while ( ( $i < count( $jpeg_header_data ) ) && ( $jpeg_header_data[$i]["SegType"] >= 0xE0 ) ) |
---|
375 | { |
---|
376 | $i++; |
---|
377 | } |
---|
378 | |
---|
379 | |
---|
380 | // insert a Comment segment new at the position found of the header data. |
---|
381 | array_splice($jpeg_header_data, $i , 0, array( array( "SegType" => 0xFE, |
---|
382 | "SegName" => $GLOBALS[ "JPEG_Segment_Names" ][ 0xFE ], |
---|
383 | "SegDesc" => $GLOBALS[ "JPEG_Segment_Descriptions" ][ 0xFE ], |
---|
384 | "SegData" => $new_Comment ) ) ); |
---|
385 | return $jpeg_header_data; |
---|
386 | } |
---|
387 | |
---|
388 | /****************************************************************************** |
---|
389 | * End of Function: put_jpeg_Comment |
---|
390 | ******************************************************************************/ |
---|
391 | |
---|
392 | |
---|
393 | |
---|
394 | |
---|
395 | /****************************************************************************** |
---|
396 | * |
---|
397 | * Function: Interpret_Comment_to_HTML |
---|
398 | * |
---|
399 | * Description: Generates html showing the contents of any JPEG Comment segment |
---|
400 | * |
---|
401 | * Parameters: jpeg_header_data - the JPEG header data, as retrieved |
---|
402 | * from the get_jpeg_header_data function |
---|
403 | * |
---|
404 | * Returns: output - the HTML |
---|
405 | * |
---|
406 | ******************************************************************************/ |
---|
407 | |
---|
408 | function Interpret_Comment_to_HTML( $jpeg_header_data ) |
---|
409 | { |
---|
410 | // Create a string to receive the output |
---|
411 | $output = ""; |
---|
412 | |
---|
413 | // read the comment segment |
---|
414 | $comment = get_jpeg_Comment( $jpeg_header_data ); |
---|
415 | |
---|
416 | // Check if the comment segment was valid |
---|
417 | if ( $comment !== FALSE ) |
---|
418 | { |
---|
419 | // Comment exists - add it to the output |
---|
420 | $output .= "<fieldset id='comment' class='fieldset' ><legend>JPEG Comment</legend>"; |
---|
421 | $output .= "<p class=\"JPEG_Comment_Text\">$comment</p>\n"; |
---|
422 | $output .= "</fieldset>"; |
---|
423 | } |
---|
424 | |
---|
425 | // Return the result |
---|
426 | return $output; |
---|
427 | } |
---|
428 | |
---|
429 | /****************************************************************************** |
---|
430 | * End of Function: Interpret_Comment_to_HTML |
---|
431 | ******************************************************************************/ |
---|
432 | |
---|
433 | |
---|
434 | |
---|
435 | |
---|
436 | /****************************************************************************** |
---|
437 | * |
---|
438 | * Function: get_jpeg_intrinsic_values |
---|
439 | * |
---|
440 | * Description: Retreives information about the intrinsic characteristics of the |
---|
441 | * jpeg image, such as Bits per Component, Height and Width. |
---|
442 | * |
---|
443 | * Parameters: jpeg_header_data - the JPEG header data, as retrieved |
---|
444 | * from the get_jpeg_header_data function |
---|
445 | * |
---|
446 | * Returns: array - An array containing the intrinsic JPEG values |
---|
447 | * FALSE - if the comment segment couldnt be found |
---|
448 | * |
---|
449 | ******************************************************************************/ |
---|
450 | |
---|
451 | function get_jpeg_intrinsic_values( $jpeg_header_data ) |
---|
452 | { |
---|
453 | // Create a blank array for the output |
---|
454 | $Outputarray = array( ); |
---|
455 | |
---|
456 | //Cycle through the header segments until Start Of Frame (SOF) is found or we run out of segments |
---|
457 | $i = 0; |
---|
458 | while ( ( $i < count( $jpeg_header_data) ) && ( substr( $jpeg_header_data[$i]['SegName'], 0, 3 ) != "SOF" ) ) |
---|
459 | { |
---|
460 | $i++; |
---|
461 | } |
---|
462 | |
---|
463 | // Check if a SOF segment has been found |
---|
464 | if ( substr( $jpeg_header_data[$i]['SegName'], 0, 3 ) == "SOF" ) |
---|
465 | { |
---|
466 | // SOF segment was found, extract the information |
---|
467 | |
---|
468 | $data = $jpeg_header_data[$i]['SegData']; |
---|
469 | |
---|
470 | // First byte is Bits per component |
---|
471 | $Outputarray['Bits per Component'] = ord( $data{0} ); |
---|
472 | |
---|
473 | // Second and third bytes are Image Height |
---|
474 | $Outputarray['Image Height'] = ord( $data{ 1 } ) * 256 + ord( $data{ 2 } ); |
---|
475 | |
---|
476 | // Forth and fifth bytes are Image Width |
---|
477 | $Outputarray['Image Width'] = ord( $data{ 3 } ) * 256 + ord( $data{ 4 } ); |
---|
478 | |
---|
479 | // Sixth byte is number of components |
---|
480 | $numcomponents = ord( $data{ 5 } ); |
---|
481 | |
---|
482 | // Following this is a table containing information about the components |
---|
483 | for( $i = 0; $i < $numcomponents; $i++ ) |
---|
484 | { |
---|
485 | $Outputarray['Components'][] = array ( 'Component Identifier' => ord( $data{ 6 + $i * 3 } ), |
---|
486 | 'Horizontal Sampling Factor' => ( ord( $data{ 7 + $i * 3 } ) & 0xF0 ) / 16, |
---|
487 | 'Vertical Sampling Factor' => ( ord( $data{ 7 + $i * 3 } ) & 0x0F ), |
---|
488 | 'Quantization table destination selector' => ord( $data{ 8 + $i * 3 } ) ); |
---|
489 | } |
---|
490 | } |
---|
491 | else |
---|
492 | { |
---|
493 | // Couldn't find Start Of Frame segment, hence can't retrieve info |
---|
494 | return FALSE; |
---|
495 | } |
---|
496 | |
---|
497 | return $Outputarray; |
---|
498 | } |
---|
499 | |
---|
500 | |
---|
501 | /****************************************************************************** |
---|
502 | * End of Function: get_jpeg_intrinsic_values |
---|
503 | ******************************************************************************/ |
---|
504 | |
---|
505 | |
---|
506 | |
---|
507 | |
---|
508 | |
---|
509 | /****************************************************************************** |
---|
510 | * |
---|
511 | * Function: Interpret_intrinsic_values_to_HTML |
---|
512 | * |
---|
513 | * Description: Generates html showing some of the intrinsic JPEG values which |
---|
514 | * were retrieved with the get_jpeg_intrinsic_values function |
---|
515 | * |
---|
516 | * Parameters: values - the JPEG intrinsic values, as read from get_jpeg_intrinsic_values |
---|
517 | * |
---|
518 | * Returns: OutputStr - A string containing the HTML |
---|
519 | * |
---|
520 | ******************************************************************************/ |
---|
521 | |
---|
522 | function Interpret_intrinsic_values_to_HTML( $values ) |
---|
523 | { |
---|
524 | // Check values are valid |
---|
525 | if ( $values != FALSE ) |
---|
526 | { |
---|
527 | // Write Heading |
---|
528 | $OutputStr = "<fieldset class='fieldset' id='Intrinsic_JPEG_Information' ><legend>Intrinsic JPEG Information</legend>"; |
---|
529 | |
---|
530 | // Create Table |
---|
531 | $OutputStr .= "<table class=\"JPEG_Intrinsic_Table\" border=1>\n"; |
---|
532 | |
---|
533 | |
---|
534 | $tabval=$IPTC_Record; |
---|
535 | $tabval['Type_src']="Intrinsic_JPEG"; |
---|
536 | |
---|
537 | // Put image height and width into table |
---|
538 | $tabval['Tag Name']="Image Height"; |
---|
539 | $tabval['Text Value']= $values['Image Height'] . " pixels"; |
---|
540 | $OutputStr .=OutPut_exif($tabval); |
---|
541 | $tabval['Tag Name']="Image Width'"; |
---|
542 | $tabval['Text Value']= $values['Image Width'] . " pixels"; |
---|
543 | $OutputStr .=OutPut_exif($tabval); |
---|
544 | // Put colour depth into table |
---|
545 | $tabval['Tag Name']="Colour Depth"; |
---|
546 | |
---|
547 | if ( count( $values['Components'] ) == 1 ) |
---|
548 | { |
---|
549 | $tabval['Text Value']=$values['Bits per Component'] . " bit Monochrome"; |
---|
550 | |
---|
551 | } |
---|
552 | else |
---|
553 | { |
---|
554 | $tabval['Text Value']= ($values['Bits per Component'] * count( $values['Components'] ) ) . " bit"; |
---|
555 | } |
---|
556 | $OutputStr .=OutPut_exif($tabval); |
---|
557 | // Close Table |
---|
558 | $OutputStr .= "</table></fieldset>"; |
---|
559 | |
---|
560 | // Return html |
---|
561 | return $OutputStr; |
---|
562 | } |
---|
563 | } |
---|
564 | |
---|
565 | /****************************************************************************** |
---|
566 | * End of Function: Interpret_intrinsic_values_to_HTML |
---|
567 | ******************************************************************************/ |
---|
568 | |
---|
569 | |
---|
570 | |
---|
571 | |
---|
572 | |
---|
573 | |
---|
574 | |
---|
575 | /****************************************************************************** |
---|
576 | * |
---|
577 | * Function: get_jpeg_image_data |
---|
578 | * |
---|
579 | * Description: Retrieves the compressed image data part of the JPEG file |
---|
580 | * |
---|
581 | * Parameters: filename - the filename of the JPEG file to read |
---|
582 | * |
---|
583 | * Returns: compressed_data - A string containing the compressed data |
---|
584 | * FALSE - if retrieval failed |
---|
585 | * |
---|
586 | ******************************************************************************/ |
---|
587 | |
---|
588 | function get_jpeg_image_data( $filename ) |
---|
589 | { |
---|
590 | |
---|
591 | // prevent refresh from aborting file operations and hosing file |
---|
592 | ignore_user_abort(true); |
---|
593 | |
---|
594 | // Attempt to open the jpeg file |
---|
595 | $filehnd = @fopen($filename, 'rb'); |
---|
596 | |
---|
597 | // Check if the file opened successfully |
---|
598 | if ( ! $filehnd ) |
---|
599 | { |
---|
600 | // Could't open the file - exit |
---|
601 | return FALSE; |
---|
602 | } |
---|
603 | |
---|
604 | |
---|
605 | // Read the first two characters |
---|
606 | $data = network_safe_fread( $filehnd, 2 ); |
---|
607 | |
---|
608 | // Check that the first two characters are 0xFF 0xDA (SOI - Start of image) |
---|
609 | if ( $data != "\xFF\xD8" ) |
---|
610 | { |
---|
611 | // No SOI (FF D8) at start of file - close file and return; |
---|
612 | fclose($filehnd); |
---|
613 | return FALSE; |
---|
614 | } |
---|
615 | |
---|
616 | |
---|
617 | |
---|
618 | // Read the third character |
---|
619 | $data = network_safe_fread( $filehnd, 2 ); |
---|
620 | |
---|
621 | // Check that the third character is 0xFF (Start of first segment header) |
---|
622 | if ( $data{0} != "\xFF" ) |
---|
623 | { |
---|
624 | // NO FF found - close file and return |
---|
625 | fclose($filehnd); |
---|
626 | return; |
---|
627 | } |
---|
628 | |
---|
629 | // Flag that we havent yet hit the compressed image data |
---|
630 | $hit_compressed_image_data = FALSE; |
---|
631 | |
---|
632 | |
---|
633 | // Cycle through the file until, one of: 1) an EOI (End of image) marker is hit, |
---|
634 | // 2) we have hit the compressed image data (no more headers are allowed after data) |
---|
635 | // 3) or end of file is hit |
---|
636 | |
---|
637 | while ( ( $data{1} != "\xD9" ) && (! $hit_compressed_image_data) && ( ! feof( $filehnd ) )) |
---|
638 | { |
---|
639 | // Found a segment to look at. |
---|
640 | // Check that the segment marker is not a Restart marker - restart markers don't have size or data after them |
---|
641 | if ( ( ord($data{1}) < 0xD0 ) || ( ord($data{1}) > 0xD7 ) ) |
---|
642 | { |
---|
643 | // Segment isn't a Restart marker |
---|
644 | // Read the next two bytes (size) |
---|
645 | $sizestr = network_safe_fread( $filehnd, 2 ); |
---|
646 | |
---|
647 | // convert the size bytes to an integer |
---|
648 | $decodedsize = unpack ("nsize", $sizestr); |
---|
649 | |
---|
650 | // Read the segment data with length indicated by the previously read size |
---|
651 | $segdata = network_safe_fread( $filehnd, $decodedsize['size'] - 2 ); |
---|
652 | } |
---|
653 | |
---|
654 | // If this is a SOS (Start Of Scan) segment, then there is no more header data - the compressed image data follows |
---|
655 | if ( $data{1} == "\xDA" ) |
---|
656 | { |
---|
657 | // Flag that we have hit the compressed image data - exit loop after reading the data |
---|
658 | $hit_compressed_image_data = TRUE; |
---|
659 | |
---|
660 | // read the rest of the file in |
---|
661 | // Can't use the filesize function to work out |
---|
662 | // how much to read, as it won't work for files being read by http or ftp |
---|
663 | // So instead read 1Mb at a time till EOF |
---|
664 | |
---|
665 | $compressed_data = ""; |
---|
666 | do |
---|
667 | { |
---|
668 | $compressed_data .= network_safe_fread( $filehnd, 1048576 ); |
---|
669 | } while( ! feof( $filehnd ) ); |
---|
670 | |
---|
671 | // Strip off EOI and anything after |
---|
672 | $EOI_pos = strpos( $compressed_data, "\xFF\xD9" ); |
---|
673 | $compressed_data = substr( $compressed_data, 0, $EOI_pos ); |
---|
674 | } |
---|
675 | else |
---|
676 | { |
---|
677 | // Not an SOS - Read the next two bytes - should be the segment marker for the next segment |
---|
678 | $data = network_safe_fread( $filehnd, 2 ); |
---|
679 | |
---|
680 | // Check that the first byte of the two is 0xFF as it should be for a marker |
---|
681 | if ( $data{0} != "\xFF" ) |
---|
682 | { |
---|
683 | // Problem - NO FF foundclose file and return"; |
---|
684 | fclose($filehnd); |
---|
685 | return; |
---|
686 | } |
---|
687 | } |
---|
688 | } |
---|
689 | |
---|
690 | // Close File |
---|
691 | fclose($filehnd); |
---|
692 | |
---|
693 | // Alow the user to abort from now on |
---|
694 | ignore_user_abort(false); |
---|
695 | |
---|
696 | |
---|
697 | // Return the compressed data if it was found |
---|
698 | if ( $hit_compressed_image_data ) |
---|
699 | { |
---|
700 | return $compressed_data; |
---|
701 | } |
---|
702 | else |
---|
703 | { |
---|
704 | return FALSE; |
---|
705 | } |
---|
706 | } |
---|
707 | |
---|
708 | |
---|
709 | /****************************************************************************** |
---|
710 | * End of Function: get_jpeg_image_data |
---|
711 | ******************************************************************************/ |
---|
712 | |
---|
713 | |
---|
714 | |
---|
715 | |
---|
716 | |
---|
717 | |
---|
718 | |
---|
719 | /****************************************************************************** |
---|
720 | * |
---|
721 | * Function: Generate_JPEG_APP_Segment_HTML |
---|
722 | * |
---|
723 | * Description: Generates html showing information about the Application (APP) |
---|
724 | * segments which are present in the JPEG file |
---|
725 | * |
---|
726 | * Parameters: jpeg_header_data - the JPEG header data, as retrieved |
---|
727 | * from the get_jpeg_header_data function |
---|
728 | * |
---|
729 | * Returns: output - A string containing the HTML |
---|
730 | * |
---|
731 | ******************************************************************************/ |
---|
732 | |
---|
733 | function Generate_JPEG_APP_Segment_HTML( $jpeg_header_data ) |
---|
734 | { |
---|
735 | if ( $jpeg_header_data == FALSE ) |
---|
736 | { |
---|
737 | return ""; |
---|
738 | } |
---|
739 | |
---|
740 | |
---|
741 | // Write Heading |
---|
742 | $output = "<fieldset class='fieldset' id='Application_Metadata_Segments' ><legend>Application Metadata Segments(JPEG)</legend>"; |
---|
743 | |
---|
744 | // Create table |
---|
745 | $output .= "<table class=\"JPEG_APP_Segments_Table\" border=1>\n"; |
---|
746 | // Cycle through each segment in the array |
---|
747 | |
---|
748 | |
---|
749 | foreach( $jpeg_header_data as $jpeg_header ) |
---|
750 | { |
---|
751 | $tabval=$jpeg_header ; |
---|
752 | $tabval['Type_src']="jpeg"; |
---|
753 | $tabval['Tag Name']=""; |
---|
754 | $tabval['Text Value']= "N.C"; |
---|
755 | // Check if the segment is a APP segment |
---|
756 | |
---|
757 | if ( ( $jpeg_header['SegType'] >= 0xE0 ) && ( $jpeg_header['SegType'] <= 0xEF ) ) |
---|
758 | { |
---|
759 | // This is an APP segment |
---|
760 | |
---|
761 | // Read APP Segment Name - a Null terminated string at the start of the segment |
---|
762 | $seg_name = strtok($jpeg_header['SegData'], "\x00"); |
---|
763 | |
---|
764 | // Some Segment names are either too long or not meaningfull, so |
---|
765 | // we should clean them up |
---|
766 | |
---|
767 | if ( $seg_name == "http://ns.adobe.com/xap/1.0/" ) |
---|
768 | { |
---|
769 | $seg_name = "XAP/RDF (\"http://ns.adobe.com/xap/1.0/\")"; |
---|
770 | } |
---|
771 | elseif ( $seg_name == "Photoshop 3.0" ) |
---|
772 | { |
---|
773 | $seg_name = "Photoshop IRB (\"Photoshop 3.0\")"; |
---|
774 | } |
---|
775 | elseif ( ( strncmp ( $seg_name, "[picture info]", 14) == 0 ) || |
---|
776 | ( strncmp ( $seg_name, "\x0a\x09\x09\x09\x09[picture info]", 19) == 0 ) ) |
---|
777 | { |
---|
778 | $seg_name = "[picture info]"; |
---|
779 | } |
---|
780 | elseif ( strncmp ( $seg_name, "Type=", 5) == 0 ) |
---|
781 | { |
---|
782 | $seg_name = "Epson Info"; |
---|
783 | } |
---|
784 | elseif ( ( strncmp ( $seg_name, "HHHHHHHHHHHHHHH", 15) == 0 ) || |
---|
785 | ( strncmp ( $seg_name, "@s33", 5) == 0 ) ) |
---|
786 | { |
---|
787 | $seg_name = "HP segment full of \"HHHHH\""; |
---|
788 | } |
---|
789 | |
---|
790 | |
---|
791 | // Clean the segment name so it doesn't cause problems with HTML |
---|
792 | $seg_name = htmlentities( $seg_name ); |
---|
793 | |
---|
794 | // Output a Table row containing this APP segment |
---|
795 | $tabval['Type_src']=$seg_name; |
---|
796 | $tabval['Tag Name']= $jpeg_header['SegName']; |
---|
797 | $tabval['Text Value']= strlen( $jpeg_header['SegData']). " bytes" ; |
---|
798 | $output .= OutPut_exif($tabval) ; |
---|
799 | // $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"; |
---|
800 | } |
---|
801 | } |
---|
802 | |
---|
803 | // Close the table |
---|
804 | $output .= "</table></fieldset>"; |
---|
805 | |
---|
806 | // Return the HTML |
---|
807 | return $output; |
---|
808 | } |
---|
809 | |
---|
810 | |
---|
811 | /****************************************************************************** |
---|
812 | * End of Function: Generate_JPEG_APP_Segment_HTML |
---|
813 | ******************************************************************************/ |
---|
814 | |
---|
815 | |
---|
816 | |
---|
817 | |
---|
818 | /****************************************************************************** |
---|
819 | * |
---|
820 | * Function: network_safe_fread |
---|
821 | * |
---|
822 | * Description: Retrieves data from a file. This function is required since |
---|
823 | * the fread function will not always return the requested number |
---|
824 | * of characters when reading from a network stream or pipe |
---|
825 | * |
---|
826 | * Parameters: file_handle - the handle of a file to read from |
---|
827 | * length - the number of bytes requested |
---|
828 | * |
---|
829 | * Returns: data - the data read from the file. may be less than the number |
---|
830 | * requested if EOF was hit |
---|
831 | * |
---|
832 | ******************************************************************************/ |
---|
833 | |
---|
834 | function network_safe_fread( $file_handle, $length ) |
---|
835 | { |
---|
836 | // Create blank string to receive data |
---|
837 | $data = ""; |
---|
838 | |
---|
839 | // Keep reading data from the file until either EOF occurs or we have |
---|
840 | // retrieved the requested number of bytes |
---|
841 | |
---|
842 | while ( ( !feof( $file_handle ) ) && ( strlen($data) < $length ) ) |
---|
843 | { |
---|
844 | $data .= fread( $file_handle, $length-strlen($data) ); |
---|
845 | } |
---|
846 | |
---|
847 | // return the data read |
---|
848 | return $data; |
---|
849 | } |
---|
850 | |
---|
851 | /****************************************************************************** |
---|
852 | * End of Function: network_safe_fread |
---|
853 | ******************************************************************************/ |
---|
854 | |
---|
855 | |
---|
856 | |
---|
857 | |
---|
858 | /****************************************************************************** |
---|
859 | * Global Variable: JPEG_Segment_Names |
---|
860 | * |
---|
861 | * Contents: The names of the JPEG segment markers, indexed by their marker number |
---|
862 | * |
---|
863 | ******************************************************************************/ |
---|
864 | |
---|
865 | $GLOBALS[ "JPEG_Segment_Names" ] = array( |
---|
866 | |
---|
867 | 0xC0 => "SOF0", 0xC1 => "SOF1", 0xC2 => "SOF2", 0xC3 => "SOF4", |
---|
868 | 0xC5 => "SOF5", 0xC6 => "SOF6", 0xC7 => "SOF7", 0xC8 => "JPG", |
---|
869 | 0xC9 => "SOF9", 0xCA => "SOF10", 0xCB => "SOF11", 0xCD => "SOF13", |
---|
870 | 0xCE => "SOF14", 0xCF => "SOF15", |
---|
871 | 0xC4 => "DHT", 0xCC => "DAC", |
---|
872 | |
---|
873 | 0xD0 => "RST0", 0xD1 => "RST1", 0xD2 => "RST2", 0xD3 => "RST3", |
---|
874 | 0xD4 => "RST4", 0xD5 => "RST5", 0xD6 => "RST6", 0xD7 => "RST7", |
---|
875 | |
---|
876 | 0xD8 => "SOI", 0xD9 => "EOI", 0xDA => "SOS", 0xDB => "DQT", |
---|
877 | 0xDC => "DNL", 0xDD => "DRI", 0xDE => "DHP", 0xDF => "EXP", |
---|
878 | |
---|
879 | 0xE0 => "APP0", 0xE1 => "APP1", 0xE2 => "APP2", 0xE3 => "APP3", |
---|
880 | 0xE4 => "APP4", 0xE5 => "APP5", 0xE6 => "APP6", 0xE7 => "APP7", |
---|
881 | 0xE8 => "APP8", 0xE9 => "APP9", 0xEA => "APP10", 0xEB => "APP11", |
---|
882 | 0xEC => "APP12", 0xED => "APP13", 0xEE => "APP14", 0xEF => "APP15", |
---|
883 | |
---|
884 | |
---|
885 | 0xF0 => "JPG0", 0xF1 => "JPG1", 0xF2 => "JPG2", 0xF3 => "JPG3", |
---|
886 | 0xF4 => "JPG4", 0xF5 => "JPG5", 0xF6 => "JPG6", 0xF7 => "JPG7", |
---|
887 | 0xF8 => "JPG8", 0xF9 => "JPG9", 0xFA => "JPG10", 0xFB => "JPG11", |
---|
888 | 0xFC => "JPG12", 0xFD => "JPG13", |
---|
889 | |
---|
890 | 0xFE => "COM", 0x01 => "TEM", 0x02 => "RES", |
---|
891 | |
---|
892 | ); |
---|
893 | |
---|
894 | /****************************************************************************** |
---|
895 | * End of Global Variable: JPEG_Segment_Names |
---|
896 | ******************************************************************************/ |
---|
897 | |
---|
898 | |
---|
899 | /****************************************************************************** |
---|
900 | * Global Variable: JPEG_Segment_Descriptions |
---|
901 | * |
---|
902 | * Contents: The descriptions of the JPEG segment markers, indexed by their marker number |
---|
903 | * |
---|
904 | ******************************************************************************/ |
---|
905 | |
---|
906 | $GLOBALS[ "JPEG_Segment_Descriptions" ] = array( |
---|
907 | |
---|
908 | /* JIF Marker byte pairs in JPEG Interchange Format sequence */ |
---|
909 | 0xC0 => "Start Of Frame (SOF) Huffman - Baseline DCT", |
---|
910 | 0xC1 => "Start Of Frame (SOF) Huffman - Extended sequential DCT", |
---|
911 | 0xC2 => "Start Of Frame Huffman - Progressive DCT (SOF2)", |
---|
912 | 0xC3 => "Start Of Frame Huffman - Spatial (sequential) lossless (SOF3)", |
---|
913 | 0xC5 => "Start Of Frame Huffman - Differential sequential DCT (SOF5)", |
---|
914 | 0xC6 => "Start Of Frame Huffman - Differential progressive DCT (SOF6)", |
---|
915 | 0xC7 => "Start Of Frame Huffman - Differential spatial (SOF7)", |
---|
916 | 0xC8 => "Start Of Frame Arithmetic - Reserved for JPEG extensions (JPG)", |
---|
917 | 0xC9 => "Start Of Frame Arithmetic - Extended sequential DCT (SOF9)", |
---|
918 | 0xCA => "Start Of Frame Arithmetic - Progressive DCT (SOF10)", |
---|
919 | 0xCB => "Start Of Frame Arithmetic - Spatial (sequential) lossless (SOF11)", |
---|
920 | 0xCD => "Start Of Frame Arithmetic - Differential sequential DCT (SOF13)", |
---|
921 | 0xCE => "Start Of Frame Arithmetic - Differential progressive DCT (SOF14)", |
---|
922 | 0xCF => "Start Of Frame Arithmetic - Differential spatial (SOF15)", |
---|
923 | 0xC4 => "Define Huffman Table(s) (DHT)", |
---|
924 | 0xCC => "Define Arithmetic coding conditioning(s) (DAC)", |
---|
925 | |
---|
926 | 0xD0 => "Restart with modulo 8 count 0 (RST0)", |
---|
927 | 0xD1 => "Restart with modulo 8 count 1 (RST1)", |
---|
928 | 0xD2 => "Restart with modulo 8 count 2 (RST2)", |
---|
929 | 0xD3 => "Restart with modulo 8 count 3 (RST3)", |
---|
930 | 0xD4 => "Restart with modulo 8 count 4 (RST4)", |
---|
931 | 0xD5 => "Restart with modulo 8 count 5 (RST5)", |
---|
932 | 0xD6 => "Restart with modulo 8 count 6 (RST6)", |
---|
933 | 0xD7 => "Restart with modulo 8 count 7 (RST7)", |
---|
934 | |
---|
935 | 0xD8 => "Start of Image (SOI)", |
---|
936 | 0xD9 => "End of Image (EOI)", |
---|
937 | 0xDA => "Start of Scan (SOS)", |
---|
938 | 0xDB => "Define quantization Table(s) (DQT)", |
---|
939 | 0xDC => "Define Number of Lines (DNL)", |
---|
940 | 0xDD => "Define Restart Interval (DRI)", |
---|
941 | 0xDE => "Define Hierarchical progression (DHP)", |
---|
942 | 0xDF => "Expand Reference Component(s) (EXP)", |
---|
943 | |
---|
944 | 0xE0 => "Application Field 0 (APP0) - usually JFIF or JFXX", |
---|
945 | 0xE1 => "Application Field 1 (APP1) - usually EXIF or XMP/RDF", |
---|
946 | 0xE2 => "Application Field 2 (APP2) - usually Flashpix", |
---|
947 | 0xE3 => "Application Field 3 (APP3)", |
---|
948 | 0xE4 => "Application Field 4 (APP4)", |
---|
949 | 0xE5 => "Application Field 5 (APP5)", |
---|
950 | 0xE6 => "Application Field 6 (APP6)", |
---|
951 | 0xE7 => "Application Field 7 (APP7)", |
---|
952 | |
---|
953 | 0xE8 => "Application Field 8 (APP8)", |
---|
954 | 0xE9 => "Application Field 9 (APP9)", |
---|
955 | 0xEA => "Application Field 10 (APP10)", |
---|
956 | 0xEB => "Application Field 11 (APP11)", |
---|
957 | 0xEC => "Application Field 12 (APP12) - usually [picture info]", |
---|
958 | 0xED => "Application Field 13 (APP13) - usually photoshop IRB / IPTC", |
---|
959 | 0xEE => "Application Field 14 (APP14)", |
---|
960 | 0xEF => "Application Field 15 (APP15)", |
---|
961 | |
---|
962 | |
---|
963 | 0xF0 => "Reserved for JPEG extensions (JPG0)", |
---|
964 | 0xF1 => "Reserved for JPEG extensions (JPG1)", |
---|
965 | 0xF2 => "Reserved for JPEG extensions (JPG2)", |
---|
966 | 0xF3 => "Reserved for JPEG extensions (JPG3)", |
---|
967 | 0xF4 => "Reserved for JPEG extensions (JPG4)", |
---|
968 | 0xF5 => "Reserved for JPEG extensions (JPG5)", |
---|
969 | 0xF6 => "Reserved for JPEG extensions (JPG6)", |
---|
970 | 0xF7 => "Reserved for JPEG extensions (JPG7)", |
---|
971 | 0xF8 => "Reserved for JPEG extensions (JPG8)", |
---|
972 | 0xF9 => "Reserved for JPEG extensions (JPG9)", |
---|
973 | 0xFA => "Reserved for JPEG extensions (JPG10)", |
---|
974 | 0xFB => "Reserved for JPEG extensions (JPG11)", |
---|
975 | 0xFC => "Reserved for JPEG extensions (JPG12)", |
---|
976 | 0xFD => "Reserved for JPEG extensions (JPG13)", |
---|
977 | |
---|
978 | 0xFE => "Comment (COM)", |
---|
979 | 0x01 => "For temp private use arith code (TEM)", |
---|
980 | 0x02 => "Reserved (RES)", |
---|
981 | |
---|
982 | ); |
---|
983 | |
---|
984 | /****************************************************************************** |
---|
985 | * End of Global Variable: JPEG_Segment_Descriptions |
---|
986 | ******************************************************************************/ |
---|
987 | |
---|
988 | |
---|
989 | |
---|
990 | ?> |
---|