1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* PEL: PHP Exif Library. |
5
|
|
|
* A library with support for reading and |
6
|
|
|
* writing all Exif headers in JPEG and TIFF images using PHP. |
7
|
|
|
* |
8
|
|
|
* Copyright (C) 2004, 2005, 2006, 2007, 2008 Martin Geisler. |
9
|
|
|
* |
10
|
|
|
* This program is free software; you can redistribute it and/or modify |
11
|
|
|
* it under the terms of the GNU General Public License as published by |
12
|
|
|
* the Free Software Foundation; either version 2 of the License, or |
13
|
|
|
* (at your option) any later version. |
14
|
|
|
* |
15
|
|
|
* This program is distributed in the hope that it will be useful, |
16
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
17
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18
|
|
|
* GNU General Public License for more details. |
19
|
|
|
* |
20
|
|
|
* You should have received a copy of the GNU General Public License |
21
|
|
|
* along with this program in the file COPYING; if not, write to the |
22
|
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
23
|
|
|
* Boston, MA 02110-1301 USA |
24
|
|
|
*/ |
25
|
|
|
namespace lsolesen\pel; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Classes for dealing with Exif IFDs. |
29
|
|
|
* |
30
|
|
|
* @author Martin Geisler <[email protected]> |
31
|
|
|
* @license http://www.gnu.org/licenses/gpl.html GNU General Public |
32
|
|
|
* License (GPL) |
33
|
|
|
* @package PEL |
34
|
|
|
*/ |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Class representing an Image File Directory (IFD). |
38
|
|
|
* |
39
|
|
|
* {@link PelTiff TIFF data} is structured as a number of Image File |
40
|
|
|
* Directories, IFDs for short. Each IFD contains a number of {@link |
41
|
|
|
* PelEntry entries}, some data and finally a link to the next IFD. |
42
|
|
|
* |
43
|
|
|
* @author Martin Geisler <[email protected]> |
44
|
|
|
* @package PEL |
45
|
|
|
*/ |
46
|
|
|
class PelIfd implements \IteratorAggregate, \ArrayAccess |
47
|
|
|
{ |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Main image IFD. |
51
|
|
|
* |
52
|
|
|
* Pass this to the constructor when creating an IFD which will be |
53
|
|
|
* the IFD of the main image. |
54
|
|
|
*/ |
55
|
|
|
const IFD0 = 0; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Thumbnail image IFD. |
59
|
|
|
* |
60
|
|
|
* Pass this to the constructor when creating an IFD which will be |
61
|
|
|
* the IFD of the thumbnail image. |
62
|
|
|
*/ |
63
|
|
|
const IFD1 = 1; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Exif IFD. |
67
|
|
|
* |
68
|
|
|
* Pass this to the constructor when creating an IFD which will be |
69
|
|
|
* the Exif sub-IFD. |
70
|
|
|
*/ |
71
|
|
|
const EXIF = 2; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* GPS IFD. |
75
|
|
|
* |
76
|
|
|
* Pass this to the constructor when creating an IFD which will be |
77
|
|
|
* the GPS sub-IFD. |
78
|
|
|
*/ |
79
|
|
|
const GPS = 3; |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Interoperability IFD. |
83
|
|
|
* |
84
|
|
|
* Pass this to the constructor when creating an IFD which will be |
85
|
|
|
* the interoperability sub-IFD. |
86
|
|
|
*/ |
87
|
|
|
const INTEROPERABILITY = 4; |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Canon Maker Notes IFD. |
91
|
|
|
* |
92
|
|
|
* Pass this to the constructor when creating an IFD which will be |
93
|
|
|
* the canon maker notes sub-IFD. |
94
|
|
|
*/ |
95
|
|
|
const CANON_MAKER_NOTES = 5; |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Canon Camera Settings IFD. |
99
|
|
|
* |
100
|
|
|
* Pass this to the constructor when creating an IFD which will be |
101
|
|
|
* the canon maker notes sub-IFD. |
102
|
|
|
*/ |
103
|
|
|
const CANON_CAMERA_SETTINGS = 6; |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Canon Shot Info IFD. |
107
|
|
|
* |
108
|
|
|
* Pass this to the constructor when creating an IFD which will be |
109
|
|
|
* the canon maker notes sub-IFD. |
110
|
|
|
*/ |
111
|
|
|
const CANON_SHOT_INFO = 7; |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Canon Shot Info IFD. |
115
|
|
|
* |
116
|
|
|
* Pass this to the constructor when creating an IFD which will be |
117
|
|
|
* the canon maker notes sub-IFD. |
118
|
|
|
*/ |
119
|
|
|
const CANON_PANORAMA = 8; |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Canon Shot Info IFD. |
123
|
|
|
* |
124
|
|
|
* Pass this to the constructor when creating an IFD which will be |
125
|
|
|
* the canon maker notes sub-IFD. |
126
|
|
|
*/ |
127
|
|
|
const CANON_PICTURE_INFO = 9; |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Canon Shot Info IFD. |
131
|
|
|
* |
132
|
|
|
* Pass this to the constructor when creating an IFD which will be |
133
|
|
|
* the canon maker notes sub-IFD. |
134
|
|
|
*/ |
135
|
|
|
const CANON_FILE_INFO = 10; |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Canon Shot Info IFD. |
139
|
|
|
* |
140
|
|
|
* Pass this to the constructor when creating an IFD which will be |
141
|
|
|
* the canon maker notes sub-IFD. |
142
|
|
|
*/ |
143
|
|
|
const CANON_CUSTOM_FUNCTIONS = 11; |
144
|
|
|
|
145
|
|
|
private const TYPE_NAMES = [ |
146
|
|
|
self::IFD0 => '0', |
147
|
|
|
self::IFD1 => '1', |
148
|
|
|
self::EXIF => 'Exif', |
149
|
|
|
self::GPS => 'GPS', |
150
|
|
|
self::INTEROPERABILITY => 'Interoperability', |
151
|
|
|
self::CANON_MAKER_NOTES => 'Canon Maker Notes', |
152
|
|
|
self::CANON_CAMERA_SETTINGS => 'Canon Camera Settings', |
153
|
|
|
self::CANON_SHOT_INFO => 'Canon Shot Information', |
154
|
|
|
self::CANON_PANORAMA => 'Canon Panorama Information', |
155
|
|
|
self::CANON_PICTURE_INFO => 'Canon Picture Information', |
156
|
|
|
self::CANON_FILE_INFO => 'Canon File Information', |
157
|
|
|
self::CANON_CUSTOM_FUNCTIONS => 'Canon Custom Functions' |
158
|
|
|
]; |
159
|
|
|
|
160
|
|
|
private const VALID_TAGS = [ |
161
|
|
|
self::IFD0 => [ |
162
|
|
|
PelTag::IMAGE_WIDTH, |
163
|
|
|
PelTag::IMAGE_LENGTH, |
164
|
|
|
PelTag::BITS_PER_SAMPLE, |
165
|
|
|
PelTag::COMPRESSION, |
166
|
|
|
PelTag::PHOTOMETRIC_INTERPRETATION, |
167
|
|
|
PelTag::DOCUMENT_NAME, |
168
|
|
|
PelTag::IMAGE_DESCRIPTION, |
169
|
|
|
PelTag::MAKE, |
170
|
|
|
PelTag::MODEL, |
171
|
|
|
PelTag::STRIP_OFFSETS, |
172
|
|
|
PelTag::ORIENTATION, |
173
|
|
|
PelTag::SAMPLES_PER_PIXEL, |
174
|
|
|
PelTag::ROWS_PER_STRIP, |
175
|
|
|
PelTag::STRIP_BYTE_COUNTS, |
176
|
|
|
PelTag::X_RESOLUTION, |
177
|
|
|
PelTag::Y_RESOLUTION, |
178
|
|
|
PelTag::PLANAR_CONFIGURATION, |
179
|
|
|
PelTag::RESOLUTION_UNIT, |
180
|
|
|
PelTag::TRANSFER_FUNCTION, |
181
|
|
|
PelTag::SOFTWARE, |
182
|
|
|
PelTag::DATE_TIME, |
183
|
|
|
PelTag::ARTIST, |
184
|
|
|
PelTag::PREDICTOR, |
185
|
|
|
PelTag::WHITE_POINT, |
186
|
|
|
PelTag::PRIMARY_CHROMATICITIES, |
187
|
|
|
PelTag::EXTRA_SAMPLES, |
188
|
|
|
PelTag::SAMPLE_FORMAT, |
189
|
|
|
PelTag::JPEG_INTERCHANGE_FORMAT, |
190
|
|
|
PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH, |
191
|
|
|
PelTag::YCBCR_COEFFICIENTS, |
192
|
|
|
PelTag::YCBCR_SUB_SAMPLING, |
193
|
|
|
PelTag::YCBCR_POSITIONING, |
194
|
|
|
PelTag::REFERENCE_BLACK_WHITE, |
195
|
|
|
PelTag::COPYRIGHT, |
196
|
|
|
PelTag::EXIF_IFD_POINTER, |
197
|
|
|
PelTag::GPS_INFO_IFD_POINTER, |
198
|
|
|
PelTag::PRINT_IM, |
199
|
|
|
PelTag::XP_TITLE, |
200
|
|
|
PelTag::XP_COMMENT, |
201
|
|
|
PelTag::XP_AUTHOR, |
202
|
|
|
PelTag::XP_KEYWORDS, |
203
|
|
|
PelTag::XP_SUBJECT, |
204
|
|
|
PelTag::RATING, |
205
|
|
|
PelTag::RATING_PERCENT, |
206
|
|
|
PelTag::APPLICATION_NOTES |
207
|
|
|
], |
208
|
|
|
self::EXIF => [ |
209
|
|
|
PelTag::EXPOSURE_TIME, |
210
|
|
|
PelTag::FNUMBER, |
211
|
|
|
PelTag::EXPOSURE_PROGRAM, |
212
|
|
|
PelTag::SPECTRAL_SENSITIVITY, |
213
|
|
|
PelTag::ISO_SPEED_RATINGS, |
214
|
|
|
PelTag::OECF, |
215
|
|
|
PelTag::EXIF_VERSION, |
216
|
|
|
PelTag::DATE_TIME_ORIGINAL, |
217
|
|
|
PelTag::DATE_TIME_DIGITIZED, |
218
|
|
|
PelTag::OFFSET_TIME, |
219
|
|
|
PelTag::OFFSET_TIME_ORIGINAL, |
220
|
|
|
PelTag::OFFSET_TIME_DIGITIZED, |
221
|
|
|
PelTag::COMPONENTS_CONFIGURATION, |
222
|
|
|
PelTag::COMPRESSED_BITS_PER_PIXEL, |
223
|
|
|
PelTag::SHUTTER_SPEED_VALUE, |
224
|
|
|
PelTag::APERTURE_VALUE, |
225
|
|
|
PelTag::BRIGHTNESS_VALUE, |
226
|
|
|
PelTag::EXPOSURE_BIAS_VALUE, |
227
|
|
|
PelTag::MAX_APERTURE_VALUE, |
228
|
|
|
PelTag::SUBJECT_DISTANCE, |
229
|
|
|
PelTag::METERING_MODE, |
230
|
|
|
PelTag::LIGHT_SOURCE, |
231
|
|
|
PelTag::FLASH, |
232
|
|
|
PelTag::FOCAL_LENGTH, |
233
|
|
|
PelTag::MAKER_NOTE, |
234
|
|
|
PelTag::USER_COMMENT, |
235
|
|
|
PelTag::SUB_SEC_TIME, |
236
|
|
|
PelTag::SUB_SEC_TIME_ORIGINAL, |
237
|
|
|
PelTag::SUB_SEC_TIME_DIGITIZED, |
238
|
|
|
PelTag::FLASH_PIX_VERSION, |
239
|
|
|
PelTag::COLOR_SPACE, |
240
|
|
|
PelTag::PIXEL_X_DIMENSION, |
241
|
|
|
PelTag::PIXEL_Y_DIMENSION, |
242
|
|
|
PelTag::RELATED_SOUND_FILE, |
243
|
|
|
PelTag::FLASH_ENERGY, |
244
|
|
|
PelTag::SPATIAL_FREQUENCY_RESPONSE, |
245
|
|
|
PelTag::FOCAL_PLANE_X_RESOLUTION, |
246
|
|
|
PelTag::FOCAL_PLANE_Y_RESOLUTION, |
247
|
|
|
PelTag::FOCAL_PLANE_RESOLUTION_UNIT, |
248
|
|
|
PelTag::SUBJECT_LOCATION, |
249
|
|
|
PelTag::EXPOSURE_INDEX, |
250
|
|
|
PelTag::SENSING_METHOD, |
251
|
|
|
PelTag::FILE_SOURCE, |
252
|
|
|
PelTag::SCENE_TYPE, |
253
|
|
|
PelTag::CFA_PATTERN, |
254
|
|
|
PelTag::CUSTOM_RENDERED, |
255
|
|
|
PelTag::EXPOSURE_MODE, |
256
|
|
|
PelTag::WHITE_BALANCE, |
257
|
|
|
PelTag::DIGITAL_ZOOM_RATIO, |
258
|
|
|
PelTag::FOCAL_LENGTH_IN_35MM_FILM, |
259
|
|
|
PelTag::SCENE_CAPTURE_TYPE, |
260
|
|
|
PelTag::GAIN_CONTROL, |
261
|
|
|
PelTag::CONTRAST, |
262
|
|
|
PelTag::SATURATION, |
263
|
|
|
PelTag::SHARPNESS, |
264
|
|
|
PelTag::DEVICE_SETTING_DESCRIPTION, |
265
|
|
|
PelTag::SUBJECT_DISTANCE_RANGE, |
266
|
|
|
PelTag::IMAGE_UNIQUE_ID, |
267
|
|
|
PelTag::INTEROPERABILITY_IFD_POINTER, |
268
|
|
|
PelTag::GAMMA |
269
|
|
|
], |
270
|
|
|
self::GPS => [ |
271
|
|
|
PelTag::GPS_VERSION_ID, |
272
|
|
|
PelTag::GPS_LATITUDE_REF, |
273
|
|
|
PelTag::GPS_LATITUDE, |
274
|
|
|
PelTag::GPS_LONGITUDE_REF, |
275
|
|
|
PelTag::GPS_LONGITUDE, |
276
|
|
|
PelTag::GPS_ALTITUDE_REF, |
277
|
|
|
PelTag::GPS_ALTITUDE, |
278
|
|
|
PelTag::GPS_TIME_STAMP, |
279
|
|
|
PelTag::GPS_SATELLITES, |
280
|
|
|
PelTag::GPS_STATUS, |
281
|
|
|
PelTag::GPS_MEASURE_MODE, |
282
|
|
|
PelTag::GPS_DOP, |
283
|
|
|
PelTag::GPS_SPEED_REF, |
284
|
|
|
PelTag::GPS_SPEED, |
285
|
|
|
PelTag::GPS_TRACK_REF, |
286
|
|
|
PelTag::GPS_TRACK, |
287
|
|
|
PelTag::GPS_IMG_DIRECTION_REF, |
288
|
|
|
PelTag::GPS_IMG_DIRECTION, |
289
|
|
|
PelTag::GPS_MAP_DATUM, |
290
|
|
|
PelTag::GPS_DEST_LATITUDE_REF, |
291
|
|
|
PelTag::GPS_DEST_LATITUDE, |
292
|
|
|
PelTag::GPS_DEST_LONGITUDE_REF, |
293
|
|
|
PelTag::GPS_DEST_LONGITUDE, |
294
|
|
|
PelTag::GPS_DEST_BEARING_REF, |
295
|
|
|
PelTag::GPS_DEST_BEARING, |
296
|
|
|
PelTag::GPS_DEST_DISTANCE_REF, |
297
|
|
|
PelTag::GPS_DEST_DISTANCE, |
298
|
|
|
PelTag::GPS_PROCESSING_METHOD, |
299
|
|
|
PelTag::GPS_AREA_INFORMATION, |
300
|
|
|
PelTag::GPS_DATE_STAMP, |
301
|
|
|
PelTag::GPS_DIFFERENTIAL |
302
|
|
|
], |
303
|
|
|
self::INTEROPERABILITY => [ |
304
|
|
|
PelTag::INTEROPERABILITY_INDEX, |
305
|
|
|
PelTag::INTEROPERABILITY_VERSION, |
306
|
|
|
PelTag::RELATED_IMAGE_FILE_FORMAT, |
307
|
|
|
PelTag::RELATED_IMAGE_WIDTH, |
308
|
|
|
PelTag::RELATED_IMAGE_LENGTH |
309
|
|
|
], |
310
|
|
|
self::CANON_MAKER_NOTES => [ |
311
|
|
|
PelTag::CANON_CAMERA_SETTINGS, |
312
|
|
|
PelTag::CANON_FOCAL_LENGTH, |
313
|
|
|
PelTag::CANON_SHOT_INFO, |
314
|
|
|
PelTag::CANON_PANORAMA, |
315
|
|
|
PelTag::CANON_IMAGE_TYPE, |
316
|
|
|
PelTag::CANON_FIRMWARE_VERSION, |
317
|
|
|
PelTag::CANON_FILE_NUMBER, |
318
|
|
|
PelTag::CANON_OWNER_NAME, |
319
|
|
|
PelTag::CANON_SERIAL_NUMBER, |
320
|
|
|
PelTag::CANON_CAMERA_INFO, |
321
|
|
|
PelTag::CANON_CUSTOM_FUNCTIONS, |
322
|
|
|
PelTag::CANON_MODEL_ID, |
323
|
|
|
PelTag::CANON_PICTURE_INFO, |
324
|
|
|
PelTag::CANON_THUMBNAIL_IMAGE_VALID_AREA, |
325
|
|
|
PelTag::CANON_SERIAL_NUMBER_FORMAT, |
326
|
|
|
PelTag::CANON_SUPER_MACRO, |
327
|
|
|
PelTag::CANON_FIRMWARE_REVISION, |
328
|
|
|
PelTag::CANON_AF_INFO, |
329
|
|
|
PelTag::CANON_ORIGINAL_DECISION_DATA_OFFSET, |
330
|
|
|
PelTag::CANON_WHITE_BALANCE_TABLE, |
331
|
|
|
PelTag::CANON_LENS_MODEL, |
332
|
|
|
PelTag::CANON_INTERNAL_SERIAL_NUMBER, |
333
|
|
|
PelTag::CANON_DUST_REMOVAL_DATA, |
334
|
|
|
PelTag::CANON_CUSTOM_FUNCTIONS_2, |
335
|
|
|
PelTag::CANON_PROCESSING_INFO, |
336
|
|
|
PelTag::CANON_MEASURED_COLOR, |
337
|
|
|
PelTag::CANON_COLOR_SPACE, |
338
|
|
|
PelTag::CANON_VRD_OFFSET, |
339
|
|
|
PelTag::CANON_SENSOR_INFO, |
340
|
|
|
PelTag::CANON_COLOR_DATA |
341
|
|
|
], |
342
|
|
|
self::CANON_CAMERA_SETTINGS => [ |
343
|
|
|
PelTag::CANON_CS_MACRO, |
344
|
|
|
PelTag::CANON_CS_SELF_TIMER, |
345
|
|
|
PelTag::CANON_CS_QUALITY, |
346
|
|
|
PelTag::CANON_CS_FLASH_MODE, |
347
|
|
|
PelTag::CANON_CS_DRIVE_MODE, |
348
|
|
|
PelTag::CANON_CS_FOCUS_MODE, |
349
|
|
|
PelTag::CANON_CS_RECORD_MODE, |
350
|
|
|
PelTag::CANON_CS_IMAGE_SIZE, |
351
|
|
|
PelTag::CANON_CS_EASY_MODE, |
352
|
|
|
PelTag::CANON_CS_DIGITAL_ZOOM, |
353
|
|
|
PelTag::CANON_CS_CONTRAST, |
354
|
|
|
PelTag::CANON_CS_SATURATION, |
355
|
|
|
PelTag::CANON_CS_SHARPNESS, |
356
|
|
|
PelTag::CANON_CS_ISO_SPEED, |
357
|
|
|
PelTag::CANON_CS_METERING_MODE, |
358
|
|
|
PelTag::CANON_CS_FOCUS_TYPE, |
359
|
|
|
PelTag::CANON_CS_AF_POINT, |
360
|
|
|
PelTag::CANON_CS_EXPOSURE_PROGRAM, |
361
|
|
|
PelTag::CANON_CS_LENS_TYPE, |
362
|
|
|
PelTag::CANON_CS_LENS, |
363
|
|
|
PelTag::CANON_CS_SHORT_FOCAL, |
364
|
|
|
PelTag::CANON_CS_FOCAL_UNITS, |
365
|
|
|
PelTag::CANON_CS_MAX_APERTURE, |
366
|
|
|
PelTag::CANON_CS_MIN_APERTURE, |
367
|
|
|
PelTag::CANON_CS_FLASH_ACTIVITY, |
368
|
|
|
PelTag::CANON_CS_FLASH_DETAILS, |
369
|
|
|
PelTag::CANON_CS_FOCUS_CONTINUOUS, |
370
|
|
|
PelTag::CANON_CS_AE_SETTING, |
371
|
|
|
PelTag::CANON_CS_IMAGE_STABILIZATION, |
372
|
|
|
PelTag::CANON_CS_DISPLAY_APERTURE, |
373
|
|
|
PelTag::CANON_CS_ZOOM_SOURCE_WIDTH, |
374
|
|
|
PelTag::CANON_CS_ZOOM_TARGET_WIDTH, |
375
|
|
|
PelTag::CANON_CS_SPOT_METERING_MODE, |
376
|
|
|
PelTag::CANON_CS_PHOTO_EFFECT, |
377
|
|
|
PelTag::CANON_CS_MANUAL_FLASH_OUTPUT, |
378
|
|
|
PelTag::CANON_CS_COLOR_TONE, |
379
|
|
|
PelTag::CANON_CS_SRAW_QUALITY |
380
|
|
|
], |
381
|
|
|
self::CANON_SHOT_INFO => [ |
382
|
|
|
PelTag::CANON_SI_ISO_SPEED, |
383
|
|
|
PelTag::CANON_SI_MEASURED_EV, |
384
|
|
|
PelTag::CANON_SI_TARGET_APERTURE, |
385
|
|
|
PelTag::CANON_SI_TARGET_SHUTTER_SPEED, |
386
|
|
|
PelTag::CANON_SI_WHITE_BALANCE, |
387
|
|
|
PelTag::CANON_SI_SLOW_SHUTTER, |
388
|
|
|
PelTag::CANON_SI_SEQUENCE, |
389
|
|
|
PelTag::CANON_SI_AF_POINT_USED, |
390
|
|
|
PelTag::CANON_SI_FLASH_BIAS, |
391
|
|
|
PelTag::CANON_SI_AUTO_EXPOSURE_BRACKETING, |
392
|
|
|
PelTag::CANON_SI_SUBJECT_DISTANCE, |
393
|
|
|
PelTag::CANON_SI_APERTURE_VALUE, |
394
|
|
|
PelTag::CANON_SI_SHUTTER_SPEED_VALUE, |
395
|
|
|
PelTag::CANON_SI_MEASURED_EV2, |
396
|
|
|
PelTag::CANON_SI_CAMERA_TYPE, |
397
|
|
|
PelTag::CANON_SI_AUTO_ROTATE, |
398
|
|
|
PelTag::CANON_SI_ND_FILTER |
399
|
|
|
], |
400
|
|
|
self::CANON_PANORAMA => [ |
401
|
|
|
PelTag::CANON_PA_PANORAMA_FRAME, |
402
|
|
|
PelTag::CANON_PA_PANORAMA_DIRECTION |
403
|
|
|
], |
404
|
|
|
self::CANON_PICTURE_INFO => [ |
405
|
|
|
PelTag::CANON_PI_IMAGE_WIDTH, |
406
|
|
|
PelTag::CANON_PI_IMAGE_HEIGHT, |
407
|
|
|
PelTag::CANON_PI_IMAGE_WIDTH_AS_SHOT, |
408
|
|
|
PelTag::CANON_PI_IMAGE_HEIGHT_AS_SHOT, |
409
|
|
|
PelTag::CANON_PI_AF_POINTS_USED, |
410
|
|
|
PelTag::CANON_PI_AF_POINTS_USED_20D |
411
|
|
|
], |
412
|
|
|
self::CANON_FILE_INFO => [ |
413
|
|
|
PelTag::CANON_FI_FILE_NUMBER, |
414
|
|
|
PelTag::CANON_FI_BRACKET_MODE, |
415
|
|
|
PelTag::CANON_FI_BRACKET_VALUE, |
416
|
|
|
PelTag::CANON_FI_BRACKET_SHOT_NUMBER, |
417
|
|
|
PelTag::CANON_FI_RAW_JPG_QUALITY, |
418
|
|
|
PelTag::CANON_FI_RAW_JPG_SIZE, |
419
|
|
|
PelTag::CANON_FI_NOISE_REDUCTION, |
420
|
|
|
PelTag::CANON_FI_WB_BRACKET_MODE, |
421
|
|
|
PelTag::CANON_FI_WB_BRACKET_VALUE_AB, |
422
|
|
|
PelTag::CANON_FI_WB_BRACKET_VALUE_GM, |
423
|
|
|
PelTag::CANON_FI_FILTER_EFFECT, |
424
|
|
|
PelTag::CANON_FI_TONING_EFFECT, |
425
|
|
|
PelTag::CANON_FI_MACRO_MAGNIFICATION, |
426
|
|
|
PelTag::CANON_FI_LIVE_VIEW_SHOOTING, |
427
|
|
|
PelTag::CANON_FI_FOCUS_DISTANCE_UPPER, |
428
|
|
|
PelTag::CANON_FI_FOCUS_DISTANCE_LOWER, |
429
|
|
|
PelTag::CANON_FI_FLASH_EXPOSURE_LOCK |
430
|
|
|
] |
431
|
|
|
/* |
432
|
|
|
* TODO: Where do these tags belong? |
433
|
|
|
* PelTag::FILL_ORDER, |
434
|
|
|
* PelTag::TRANSFER_RANGE, |
435
|
|
|
* PelTag::JPEG_PROC, |
436
|
|
|
* PelTag::BATTERY_LEVEL, |
437
|
|
|
* PelTag::IPTC_NAA, |
438
|
|
|
* PelTag::INTER_COLOR_PROFILE, |
439
|
|
|
* PelTag::CFA_REPEAT_PATTERN_DIM, |
440
|
|
|
*/ |
441
|
|
|
]; |
442
|
|
|
|
443
|
|
|
/** |
444
|
|
|
* The maker notes held by this directory. |
445
|
|
|
* |
446
|
|
|
* Stores information of the MakerNotes IFD. |
447
|
|
|
* Available and required keys are: parent, data, components and offset |
448
|
|
|
* |
449
|
|
|
* @var array |
450
|
|
|
*/ |
451
|
|
|
private $maker_notes = []; |
452
|
|
|
|
453
|
|
|
/** |
454
|
|
|
* The entries held by this directory. |
455
|
|
|
* |
456
|
|
|
* Each tag in the directory is represented by a {@link PelEntry} |
457
|
|
|
* object in this array. |
458
|
|
|
* |
459
|
|
|
* @var array |
460
|
|
|
*/ |
461
|
|
|
private $entries = []; |
462
|
|
|
|
463
|
|
|
/** |
464
|
|
|
* The type of this directory. |
465
|
|
|
* |
466
|
|
|
* Initialized in the constructor. Must be one of {@link IFD0}, |
467
|
|
|
* {@link IFD1}, {@link EXIF}, {@link GPS}, or {@link |
468
|
|
|
* INTEROPERABILITY}. |
469
|
|
|
* |
470
|
|
|
* @var int |
471
|
|
|
*/ |
472
|
|
|
private $type; |
473
|
|
|
|
474
|
|
|
/** |
475
|
|
|
* The next directory. |
476
|
|
|
* |
477
|
|
|
* This will be initialized in the constructor, or be left as null |
478
|
|
|
* if this is the last directory. |
479
|
|
|
* |
480
|
|
|
* @var PelIfd |
481
|
|
|
*/ |
482
|
|
|
private $next = null; |
483
|
|
|
|
484
|
|
|
/** |
485
|
|
|
* Sub-directories pointed to by this directory. |
486
|
|
|
* |
487
|
|
|
* This will be an array of ({@link PelTag}, {@link PelIfd}) pairs. |
488
|
|
|
* |
489
|
|
|
* @var array |
490
|
|
|
*/ |
491
|
|
|
private $sub = []; |
492
|
|
|
|
493
|
|
|
/** |
494
|
|
|
* The thumbnail data. |
495
|
|
|
* |
496
|
|
|
* This will be initialized in the constructor, or be left as null |
497
|
|
|
* if there are no thumbnail as part of this directory. |
498
|
|
|
* |
499
|
|
|
* @var PelDataWindow |
500
|
|
|
*/ |
501
|
|
|
private $thumb_data = null; |
502
|
|
|
|
503
|
|
|
// TODO: use this format to choose between the |
504
|
|
|
// JPEG_INTERCHANGE_FORMAT and STRIP_OFFSETS tags. |
505
|
|
|
// private $thumb_format; |
506
|
|
|
|
507
|
|
|
/** |
508
|
|
|
* Construct a new Image File Directory (IFD). |
509
|
|
|
* |
510
|
|
|
* The IFD will be empty, use the {@link addEntry()} method to add |
511
|
|
|
* an {@link PelEntry}. Use the {@link setNext()} method to link |
512
|
|
|
* this IFD to another. |
513
|
|
|
* |
514
|
|
|
* @param integer $type |
515
|
|
|
* the type of this IFD. Must be one of {@link |
516
|
|
|
* IFD0}, {@link IFD1}, {@link EXIF}, {@link GPS}, or {@link |
517
|
|
|
* INTEROPERABILITY}. An {@link PelIfdException} will be thrown |
518
|
|
|
* otherwise. |
519
|
|
|
* @throws PelIfdException |
520
|
|
|
*/ |
521
|
32 |
|
public function __construct($type) |
522
|
|
|
{ |
523
|
32 |
|
if (! array_key_exists($type, self::TYPE_NAMES)) { |
524
|
|
|
throw new PelIfdException('Unknown IFD type: %d', $type); |
525
|
|
|
} |
526
|
32 |
|
$this->type = $type; |
527
|
32 |
|
} |
528
|
|
|
|
529
|
|
|
/** |
530
|
|
|
* Stores Maker Notes data for an IFD (Probably PelIfd::EXIF only). |
531
|
|
|
* |
532
|
|
|
* @param PelIfd $parent |
533
|
|
|
* the parent PelIfd of the current PelIfd |
534
|
|
|
* @param PelDataWindow $data |
535
|
|
|
* the data window that will provide the data. |
536
|
|
|
* @param PelIfd $parent |
537
|
|
|
* the components in the entry. |
538
|
|
|
* @param integer $offset |
539
|
|
|
* the offset within the window where the directory will |
540
|
|
|
* be found. |
541
|
|
|
*/ |
542
|
14 |
|
public function setMakerNotes($parent, $data, $components, $offset) |
543
|
|
|
{ |
544
|
14 |
|
$this->maker_notes = [ |
545
|
14 |
|
'parent' => $parent, |
546
|
14 |
|
'data' => $data, |
547
|
14 |
|
'components' => $components, |
548
|
14 |
|
'offset' => $offset |
549
|
|
|
]; |
550
|
14 |
|
} |
551
|
|
|
|
552
|
|
|
/** |
553
|
|
|
* Returns the Maker Notes data for an IFD (Probably PelIfd::EXIF only). |
554
|
|
|
* |
555
|
|
|
* @return array The maker_notes of IDF |
556
|
|
|
*/ |
557
|
17 |
|
public function getMakerNotes() |
558
|
|
|
{ |
559
|
17 |
|
return $this->maker_notes; |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
/** |
563
|
|
|
* Load data into a Image File Directory (IFD). |
564
|
|
|
* |
565
|
|
|
* @param PelDataWindow $d |
566
|
|
|
* the data window that will provide the data. |
567
|
|
|
* @param integer $offset |
568
|
|
|
* the offset within the window where the directory will |
569
|
|
|
* be found. |
570
|
|
|
* @throws PelException |
571
|
|
|
* @throws PelUnexpectedFormatException |
572
|
|
|
* @throws PelWrongComponentCountException |
573
|
|
|
*/ |
574
|
30 |
|
public function load(PelDataWindow $d, $offset) |
575
|
|
|
{ |
576
|
30 |
|
$starting_offset = $offset; |
577
|
30 |
|
$thumb_offset = 0; |
578
|
30 |
|
$thumb_length = 0; |
579
|
|
|
|
580
|
30 |
|
Pel::debug('Constructing IFD at offset %d from %d bytes...', $offset, $d->getSize()); |
581
|
|
|
|
582
|
|
|
/* Read the number of entries */ |
583
|
30 |
|
$n = $d->getShort($offset); |
584
|
30 |
|
Pel::debug('Loading %d entries...', $n); |
585
|
|
|
|
586
|
30 |
|
$offset += 2; |
587
|
|
|
|
588
|
|
|
/* Check if we have enough data. */ |
589
|
30 |
|
if ($offset + 12 * $n > $d->getSize()) { |
590
|
1 |
|
$n = floor(($offset - $d->getSize()) / 12); |
591
|
1 |
|
Pel::maybeThrow(new PelIfdException('Adjusted to: %d.', $n)); |
592
|
|
|
} |
593
|
|
|
|
594
|
30 |
|
for ($i = 0; $i < $n; $i ++) { |
595
|
|
|
// TODO: increment window start instead of using offsets. |
596
|
30 |
|
$tag = $d->getShort($offset + 12 * $i); |
597
|
30 |
|
Pel::debug('Loading entry with tag 0x%04X: %s (%d of %d)...', $tag, PelTag::getName($this->type, $tag), $i + 1, $n); |
598
|
|
|
|
599
|
30 |
|
switch ($tag) { |
600
|
|
|
case PelTag::EXIF_IFD_POINTER: |
601
|
|
|
case PelTag::GPS_INFO_IFD_POINTER: |
602
|
|
|
case PelTag::INTEROPERABILITY_IFD_POINTER: |
603
|
|
|
case PelTag::MAKER_NOTE: |
604
|
19 |
|
$o = $d->getLong($offset + 12 * $i + 8); |
605
|
19 |
|
Pel::debug('Found sub IFD at offset %d', $o); |
606
|
19 |
|
$components = $d->getLong($offset + 12 * $i + 4); |
607
|
|
|
|
608
|
19 |
|
$ifdType = $this->mapTagToIfdType($d, $offset, $tag, $components, $i, $o); |
609
|
|
|
|
610
|
19 |
|
if ($ifdType === null) { |
611
|
14 |
|
break; |
612
|
19 |
|
} elseif ($starting_offset == $o) { |
613
|
1 |
|
Pel::maybeThrow(new PelIfdException('Bogus offset to next IFD: %d, same as offset being loaded from.', $o)); |
614
|
|
|
} else { |
615
|
19 |
|
$ifd = new PelIfd($ifdType); |
616
|
|
|
try { |
617
|
19 |
|
$ifd->load($d, $o); |
618
|
18 |
|
$this->sub[$ifdType] = $ifd; |
619
|
2 |
|
} catch (PelDataWindowOffsetException $e) { |
620
|
1 |
|
Pel::maybeThrow(new PelIfdException($e->getMessage())); |
621
|
|
|
} |
622
|
|
|
} |
623
|
19 |
|
break; |
624
|
|
|
case PelTag::JPEG_INTERCHANGE_FORMAT: |
625
|
15 |
|
$thumb_offset = $d->getLong($offset + 12 * $i + 8); |
626
|
15 |
|
$this->safeSetThumbnail($d, $thumb_offset, $thumb_length); |
627
|
15 |
|
break; |
628
|
|
|
case PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH: |
629
|
15 |
|
$thumb_length = $d->getLong($offset + 12 * $i + 8); |
630
|
15 |
|
$this->safeSetThumbnail($d, $thumb_offset, $thumb_length); |
631
|
15 |
|
break; |
632
|
|
|
default: |
633
|
30 |
|
$this->loadSingleValue($d, $offset, $i, $tag); |
634
|
30 |
|
break; |
635
|
|
|
} |
636
|
|
|
} |
637
|
|
|
|
638
|
30 |
|
$this->getOffsetToNextIfd($d, $offset, $n); |
639
|
30 |
|
$this->checkIfLoadingFinished(); |
640
|
30 |
|
} |
641
|
|
|
|
642
|
|
|
/** |
643
|
|
|
* Map tag to IFD type |
644
|
|
|
* |
645
|
|
|
* @param PelDataWindow $d |
646
|
|
|
* @param integer $offset |
647
|
|
|
* @param integer $tag |
648
|
|
|
* @param integer $components |
649
|
|
|
* @param integer $i |
650
|
|
|
* @param integer $o |
651
|
|
|
* @return NULL|mixed|number[][] |
652
|
|
|
*/ |
653
|
19 |
|
private function mapTagToIfdType(PelDataWindow $d, $offset, $tag, $components, $i, $o) |
654
|
|
|
{ |
655
|
19 |
|
$ifdType = null; |
656
|
|
|
|
657
|
|
|
/* Map tag to IFD type. */ |
658
|
19 |
|
if ($tag == PelTag::EXIF_IFD_POINTER) { |
659
|
19 |
|
$ifdType = PelIfd::EXIF; |
660
|
16 |
|
} elseif ($tag == PelTag::GPS_INFO_IFD_POINTER) { |
661
|
4 |
|
$ifdType = PelIfd::GPS; |
662
|
15 |
|
} elseif ($tag == PelTag::INTEROPERABILITY_IFD_POINTER) { |
663
|
13 |
|
$ifdType = PelIfd::INTEROPERABILITY; |
664
|
14 |
|
} elseif ($tag == PelTag::MAKER_NOTE) { |
665
|
|
|
// Store maker notes infos, because we need PelTag::MAKE of PelIfd::IFD0 for MakerNotes |
666
|
|
|
// Thus MakerNotes will be loaded at the end of loading PelIfd::IFD0 |
667
|
14 |
|
$this->setMakerNotes($this, $d, $components, $o); |
668
|
14 |
|
$this->loadSingleValue($d, $offset, $i, $tag); |
669
|
|
|
} |
670
|
19 |
|
return $ifdType; |
671
|
|
|
} |
672
|
|
|
|
673
|
|
|
/** |
674
|
|
|
* Offset to next IFD |
675
|
|
|
* |
676
|
|
|
* @param PelDataWindow $d |
677
|
|
|
* @param integer $offset |
678
|
|
|
* @param float $n |
679
|
|
|
*/ |
680
|
30 |
|
private function getOffsetToNextIfd(PelDataWindow $d, $offset, $n) |
681
|
|
|
{ |
682
|
30 |
|
$o = $d->getLong((int) ($offset + 12 * $n)); |
683
|
30 |
|
Pel::debug('Current offset is %d, link at %d points to %d.', $offset, $offset + 12 * $n, $o); |
684
|
|
|
|
685
|
30 |
|
if ($o > 0) { |
686
|
|
|
/* Sanity check: we need 6 bytes */ |
687
|
17 |
|
if ($o > $d->getSize() - 6) { |
688
|
2 |
|
Pel::maybeThrow(new PelIfdException('Bogus offset to next IFD: ' . '%d > %d!', $o, $d->getSize() - 6)); |
689
|
|
|
} else { |
690
|
16 |
|
if ($this->type == PelIfd::IFD1) { |
691
|
|
|
// IFD1 shouldn't link further... |
692
|
|
|
Pel::maybeThrow(new PelIfdException('IFD1 links to another IFD!')); |
693
|
|
|
} |
694
|
16 |
|
$this->next = new PelIfd(PelIfd::IFD1); |
695
|
17 |
|
$this->next->load($d, $o); |
696
|
|
|
} |
697
|
|
|
} else { |
698
|
29 |
|
Pel::debug('Last IFD.'); |
699
|
|
|
} |
700
|
30 |
|
} |
701
|
|
|
|
702
|
|
|
/** |
703
|
|
|
* Check if we finished loading IFD0 and EXIF IFD is set (EXIF IFD holds the MakerNotes) |
704
|
|
|
*/ |
705
|
30 |
|
private function checkIfLoadingFinished() |
706
|
|
|
{ |
707
|
30 |
|
if ($this->type == PelIfd::IFD0 && isset($this->sub[PelIfd::EXIF])) { |
708
|
|
|
// Get MakerNotes from EXIF IFD and check if they are set |
709
|
17 |
|
$mk = $this->sub[PelIfd::EXIF]->getMakerNotes(); |
710
|
17 |
|
if (! empty($mk)) { |
711
|
|
|
// get Make tag and load maker notes if tag is valid |
712
|
13 |
|
$manufacturer = $this->getEntry(PelTag::MAKE); |
713
|
13 |
|
if ($manufacturer !== null) { |
714
|
13 |
|
$manufacturer = $manufacturer->getValue(); |
715
|
13 |
|
$mkNotes = PelMakerNotes::createMakerNotesFromManufacturer($manufacturer, $mk['parent'], $mk['data'], $mk['components'], $mk['offset']); |
716
|
13 |
|
if ($mkNotes !== null) { |
717
|
|
|
// remove pre-loaded undefined MakerNotes |
718
|
3 |
|
$mk['parent']->offsetUnset(PelTag::MAKER_NOTE); |
719
|
3 |
|
$mkNotes->load(); |
720
|
|
|
} |
721
|
|
|
} |
722
|
|
|
} |
723
|
|
|
} |
724
|
30 |
|
} |
725
|
|
|
|
726
|
|
|
/** |
727
|
|
|
* Load a single value which didn't match any special {@link PelTag}. |
728
|
|
|
* |
729
|
|
|
* This method will add a single value given by the {@link PelDataWindow} and it's offset ($offset) and element counter ($i). |
730
|
|
|
* |
731
|
|
|
* Please note that the data you pass to this method should come |
732
|
|
|
* from an image, that is, it should be raw bytes. If instead you |
733
|
|
|
* want to create an entry for holding, say, an short integer, then |
734
|
|
|
* create a {@link PelEntryShort} object directly and load the data |
735
|
|
|
* into it. |
736
|
|
|
* |
737
|
|
|
* @param PelDataWindow $d |
738
|
|
|
* the data window that will provide the data. |
739
|
|
|
* @param integer $offset |
740
|
|
|
* the offset within the window where the directory will |
741
|
|
|
* be found. |
742
|
|
|
* @param integer $i |
743
|
|
|
* the element's position in the {@link PelDataWindow} $d. |
744
|
|
|
* @param integer $tag |
745
|
|
|
* the tag of the entry as defined in {@link PelTag}. |
746
|
|
|
* @throws PelException |
747
|
|
|
* @throws PelUnexpectedFormatException |
748
|
|
|
* @throws PelWrongComponentCountException |
749
|
|
|
*/ |
750
|
30 |
|
public function loadSingleValue($d, $offset, $i, $tag) |
751
|
|
|
{ |
752
|
30 |
|
$format = $d->getShort($offset + 12 * $i + 2); |
753
|
30 |
|
$components = $d->getLong($offset + 12 * $i + 4); |
754
|
30 |
|
$size = PelFormat::getSize($format); |
755
|
30 |
|
if (is_string($size)) { |
756
|
|
|
Pel::maybeThrow(new PelException('Invalid format %s', $format)); |
757
|
|
|
return; |
758
|
|
|
} |
759
|
|
|
|
760
|
|
|
try { |
761
|
|
|
/* |
762
|
|
|
* The data size. If bigger than 4 bytes, the actual data is |
763
|
|
|
* not in the entry but somewhere else, with the offset stored |
764
|
|
|
* in the entry. |
765
|
|
|
*/ |
766
|
30 |
|
$s = $size * $components; |
767
|
30 |
|
if ($s > 0) { |
768
|
30 |
|
$doff = $offset + 12 * $i + 8; |
769
|
30 |
|
if ($s > 4) { |
770
|
30 |
|
$doff = $d->getLong($doff); |
771
|
|
|
} |
772
|
30 |
|
$data = $d->getClone($doff, $s); |
773
|
|
|
} else { |
774
|
6 |
|
$data = new PelDataWindow(); |
775
|
|
|
} |
776
|
|
|
|
777
|
30 |
|
$entry = $this->newEntryFromData($tag, $format, $components, $data); |
778
|
30 |
|
$this->addEntry($entry); |
779
|
5 |
|
} catch (PelException $e) { |
780
|
|
|
/* |
781
|
|
|
* Throw the exception when running in strict mode, store |
782
|
|
|
* otherwise. |
783
|
|
|
*/ |
784
|
5 |
|
Pel::maybeThrow($e); |
785
|
|
|
} |
786
|
|
|
|
787
|
|
|
/* The format of the thumbnail is stored in this tag. */ |
788
|
|
|
// TODO: handle TIFF thumbnail. |
789
|
|
|
// if ($tag == PelTag::COMPRESSION) { |
790
|
|
|
// $this->thumb_format = $data->getShort(); |
791
|
|
|
// } |
792
|
30 |
|
} |
793
|
|
|
|
794
|
|
|
/** |
795
|
|
|
* Load a single value which didn't match any special {@link PelTag}. |
796
|
|
|
* |
797
|
|
|
* This method will add a single value given by the {@link PelDataWindow} and it's offset ($offset) and element counter ($i). |
798
|
|
|
* |
799
|
|
|
* Please note that the data you pass to this method should come |
800
|
|
|
* from an image, that is, it should be raw bytes. If instead you |
801
|
|
|
* want to create an entry for holding, say, an short integer, then |
802
|
|
|
* create a {@link PelEntryShort} object directly and load the data |
803
|
|
|
* into it. |
804
|
|
|
* |
805
|
|
|
* @param integer $type |
806
|
|
|
* the type of the ifd |
807
|
|
|
* @param PelDataWindow $data |
808
|
|
|
* the data window that will provide the data. |
809
|
|
|
* @param integer $offset |
810
|
|
|
* the offset within the window where the directory will |
811
|
|
|
* be found. |
812
|
|
|
* @param integer $size |
813
|
|
|
* the size in bytes of the maker notes section |
814
|
|
|
* @param integer $i |
815
|
|
|
* the element's position in the {@link PelDataWindow} $data. |
816
|
|
|
* @param integer $format |
817
|
|
|
* the format {@link PelFormat} of the entry. |
818
|
|
|
* @throws PelException |
819
|
|
|
* @throws PelDataWindowWindowException |
820
|
|
|
* @throws PelInvalidArgumentException |
821
|
|
|
*/ |
822
|
3 |
|
public function loadSingleMakerNotesValue($type, PelDataWindow $data, $offset, $size, $i, $format) |
|
|
|
|
823
|
|
|
{ |
824
|
3 |
|
$elemSize = PelFormat::getSize($format); |
825
|
3 |
|
if ($size > 0) { |
826
|
3 |
|
$subdata = $data->getClone($offset + $i * $elemSize, $elemSize); |
|
|
|
|
827
|
|
|
} else { |
828
|
|
|
$subdata = new PelDataWindow(); |
829
|
|
|
} |
830
|
|
|
|
831
|
|
|
try { |
832
|
3 |
|
$entry = $this->newEntryFromData($i + 1, $format, 1, $subdata); |
833
|
3 |
|
$this->addEntry($entry); |
834
|
|
|
} catch (PelException $e) { |
835
|
|
|
// Throw the exception when running in strict mode, store otherwise. |
836
|
|
|
Pel::maybeThrow($e); |
837
|
|
|
} |
838
|
|
|
|
839
|
|
|
/* The format of the thumbnail is stored in this tag. */ |
840
|
|
|
// TODO: handle TIFF thumbnail. |
841
|
|
|
// if ($tag == PelTag::COMPRESSION) { |
842
|
|
|
// $this->thumb_format = $data->getShort(); |
843
|
|
|
// } |
844
|
3 |
|
} |
845
|
|
|
|
846
|
|
|
/** |
847
|
|
|
* Make a new entry from a bunch of bytes. |
848
|
|
|
* |
849
|
|
|
* This method will create the proper subclass of {@link PelEntry} |
850
|
|
|
* corresponding to the {@link PelTag} and {@link PelFormat} given. |
851
|
|
|
* The entry will be initialized with the data given. |
852
|
|
|
* |
853
|
|
|
* Please note that the data you pass to this method should come |
854
|
|
|
* from an image, that is, it should be raw bytes. If instead you |
855
|
|
|
* want to create an entry for holding, say, an short integer, then |
856
|
|
|
* create a {@link PelEntryShort} object directly and load the data |
857
|
|
|
* into it. |
858
|
|
|
* |
859
|
|
|
* A {@link PelUnexpectedFormatException} is thrown if a mismatch is |
860
|
|
|
* discovered between the tag and format, and likewise a {@link |
861
|
|
|
* PelWrongComponentCountException} is thrown if the number of |
862
|
|
|
* components does not match the requirements of the tag. The |
863
|
|
|
* requirements for a given tag (if any) can be found in the |
864
|
|
|
* documentation for {@link PelTag}. |
865
|
|
|
* |
866
|
|
|
* @param integer $tag |
867
|
|
|
* the tag of the entry as defined in {@link PelTag}. |
868
|
|
|
* @param integer $format |
869
|
|
|
* the format of the entry as defined in {@link PelFormat}. |
870
|
|
|
* @param integer $components |
871
|
|
|
* the components in the entry. |
872
|
|
|
* @param PelDataWindow $data |
873
|
|
|
* the data which will be used to construct the |
874
|
|
|
* entry. |
875
|
|
|
* @return PelEntry a newly created entry, holding the data given. |
876
|
|
|
* @throws PelException |
877
|
|
|
* @throws PelUnexpectedFormatException |
878
|
|
|
* @throws PelWrongComponentCountException |
879
|
|
|
*/ |
880
|
30 |
|
public function newEntryFromData($tag, $format, $components, PelDataWindow $data) |
881
|
|
|
{ |
882
|
|
|
|
883
|
|
|
/* |
884
|
|
|
* First handle tags for which we have a specific PelEntryXXX |
885
|
|
|
* class. |
886
|
|
|
*/ |
887
|
30 |
|
switch ($this->type) { |
888
|
30 |
|
case self::IFD0: |
889
|
19 |
|
case self::IFD1: |
890
|
19 |
|
case self::EXIF: |
891
|
12 |
|
case self::INTEROPERABILITY: |
892
|
30 |
|
switch ($tag) { |
893
|
|
|
case PelTag::DATE_TIME: |
894
|
|
|
case PelTag::DATE_TIME_ORIGINAL: |
895
|
|
|
case PelTag::DATE_TIME_DIGITIZED: |
896
|
19 |
|
if ($format != PelFormat::ASCII) { |
897
|
|
|
throw new PelUnexpectedFormatException($this->type, $tag, $format, PelFormat::ASCII); |
898
|
|
|
} |
899
|
19 |
|
if ($components != 20) { |
900
|
3 |
|
throw new PelWrongComponentCountException($this->type, $tag, $components, 20); |
901
|
|
|
} |
902
|
|
|
// TODO: handle timezones. |
903
|
16 |
|
return new PelEntryTime($tag, $data->getBytes(0, - 1), PelEntryTime::EXIF_STRING); |
904
|
|
|
|
905
|
|
|
case PelTag::COPYRIGHT: |
906
|
3 |
|
if ($format != PelFormat::ASCII) { |
907
|
|
|
throw new PelUnexpectedFormatException($this->type, $tag, $format, PelFormat::ASCII); |
908
|
|
|
} |
909
|
3 |
|
$v = explode("\0", trim($data->getBytes(), ' ')); |
910
|
3 |
|
if (! isset($v[1])) { |
911
|
1 |
|
Pel::maybeThrow(new PelException('Invalid copyright: %s', $data->getBytes())); |
912
|
|
|
// when not in strict mode, set empty copyright and continue |
913
|
1 |
|
$v[1] = ''; |
914
|
|
|
} |
915
|
3 |
|
return new PelEntryCopyright($v[0], $v[1]); |
916
|
|
|
|
917
|
|
|
case PelTag::EXIF_VERSION: |
918
|
|
|
case PelTag::FLASH_PIX_VERSION: |
919
|
|
|
case PelTag::INTEROPERABILITY_VERSION: |
920
|
19 |
|
if ($format != PelFormat::UNDEFINED) { |
921
|
1 |
|
throw new PelUnexpectedFormatException($this->type, $tag, $format, PelFormat::UNDEFINED); |
922
|
|
|
} |
923
|
18 |
|
return new PelEntryVersion($tag, (float) $data->getBytes() / 100); |
924
|
|
|
|
925
|
|
|
case PelTag::USER_COMMENT: |
926
|
10 |
|
if ($format != PelFormat::UNDEFINED) { |
927
|
|
|
throw new PelUnexpectedFormatException($this->type, $tag, $format, PelFormat::UNDEFINED); |
928
|
|
|
} |
929
|
10 |
|
if ($data->getSize() < 8) { |
930
|
|
|
return new PelEntryUserComment(); |
931
|
|
|
} else { |
932
|
10 |
|
return new PelEntryUserComment($data->getBytes(8), rtrim($data->getBytes(0, 8))); |
933
|
|
|
} |
934
|
|
|
// this point can not be reached |
935
|
|
|
case PelTag::XP_TITLE: |
936
|
|
|
case PelTag::XP_COMMENT: |
937
|
|
|
case PelTag::XP_AUTHOR: |
938
|
|
|
case PelTag::XP_KEYWORDS: |
939
|
|
|
case PelTag::XP_SUBJECT: |
940
|
3 |
|
if ($format != PelFormat::BYTE) { |
941
|
|
|
throw new PelUnexpectedFormatException($this->type, $tag, $format, PelFormat::BYTE); |
942
|
|
|
} |
943
|
3 |
|
return new PelEntryWindowsString($tag, $data->getBytes(), true); |
944
|
|
|
} |
945
|
|
|
// This point can be reached! Continue with default. |
946
|
4 |
|
case self::GPS: |
947
|
|
|
default: |
948
|
|
|
/* Then handle the basic formats. */ |
949
|
28 |
|
switch ($format) { |
950
|
|
|
case PelFormat::BYTE: |
951
|
4 |
|
$v = new PelEntryByte($tag); |
952
|
4 |
|
for ($i = 0; $i < $components; $i ++) { |
953
|
4 |
|
$v->addNumber($data->getByte($i)); |
954
|
|
|
} |
955
|
4 |
|
return $v; |
956
|
|
|
|
957
|
|
|
case PelFormat::SBYTE: |
958
|
1 |
|
$v = new PelEntrySByte($tag); |
959
|
1 |
|
for ($i = 0; $i < $components; $i ++) { |
960
|
1 |
|
$v->addNumber($data->getSByte($i)); |
961
|
|
|
} |
962
|
1 |
|
return $v; |
963
|
|
|
|
964
|
|
|
case PelFormat::ASCII: |
965
|
|
|
// cut off string after the first nul byte |
966
|
21 |
|
$canonicalString = strstr($data->getBytes(0), "\0", true); |
967
|
21 |
|
if ($canonicalString !== false) { |
968
|
20 |
|
return new PelEntryAscii($tag, $canonicalString); |
969
|
|
|
} |
970
|
|
|
// TODO throw exception if string isn't nul-terminated |
971
|
1 |
|
return new PelEntryAscii($tag, $data->getBytes(0)); |
972
|
|
|
|
973
|
|
|
case PelFormat::SHORT: |
974
|
20 |
|
$v = new PelEntryShort($tag); |
975
|
20 |
|
for ($i = 0; $i < $components; $i ++) { |
976
|
20 |
|
$v->addNumber($data->getShort($i * 2)); |
977
|
|
|
} |
978
|
20 |
|
return $v; |
979
|
|
|
|
980
|
|
|
case PelFormat::SSHORT: |
981
|
4 |
|
$v = new PelEntrySShort($tag); |
982
|
4 |
|
for ($i = 0; $i < $components; $i ++) { |
983
|
4 |
|
$v->addNumber($data->getSShort($i * 2)); |
984
|
|
|
} |
985
|
4 |
|
return $v; |
986
|
|
|
|
987
|
|
|
case PelFormat::LONG: |
988
|
16 |
|
$v = new PelEntryLong($tag); |
989
|
16 |
|
for ($i = 0; $i < $components; $i ++) { |
990
|
16 |
|
$v->addNumber($data->getLong($i * 4)); |
991
|
|
|
} |
992
|
16 |
|
return $v; |
993
|
|
|
|
994
|
|
|
case PelFormat::SLONG: |
995
|
1 |
|
$v = new PelEntrySLong($tag); |
996
|
1 |
|
for ($i = 0; $i < $components; $i ++) { |
997
|
1 |
|
$v->addNumber($data->getSLong($i * 4)); |
998
|
|
|
} |
999
|
1 |
|
return $v; |
1000
|
|
|
|
1001
|
|
|
case PelFormat::RATIONAL: |
1002
|
17 |
|
$v = new PelEntryRational($tag); |
1003
|
17 |
|
for ($i = 0; $i < $components; $i ++) { |
1004
|
17 |
|
$v->addNumber($data->getRational($i * 8)); |
1005
|
|
|
} |
1006
|
17 |
|
return $v; |
1007
|
|
|
|
1008
|
|
|
case PelFormat::SRATIONAL: |
1009
|
16 |
|
$v = new PelEntrySRational($tag); |
1010
|
16 |
|
for ($i = 0; $i < $components; $i ++) { |
1011
|
16 |
|
$v->addNumber($data->getSRational($i * 8)); |
1012
|
|
|
} |
1013
|
16 |
|
return $v; |
1014
|
|
|
|
1015
|
|
|
case PelFormat::UNDEFINED: |
1016
|
16 |
|
return new PelEntryUndefined($tag, $data->getBytes()); |
1017
|
|
|
|
1018
|
|
|
default: |
1019
|
|
|
throw new PelException('Unsupported format: %s', PelFormat::getName($format)); |
1020
|
|
|
} |
1021
|
|
|
} |
1022
|
|
|
} |
1023
|
|
|
|
1024
|
|
|
/** |
1025
|
|
|
* Extract thumbnail data safely. |
1026
|
|
|
* |
1027
|
|
|
* It is safe to call this method repeatedly with either the offset |
1028
|
|
|
* or the length set to zero, since it requires both of these |
1029
|
|
|
* arguments to be positive before the thumbnail is extracted. |
1030
|
|
|
* |
1031
|
|
|
* When both parameters are set it will check the length against the |
1032
|
|
|
* available data and adjust as necessary. Only then is the |
1033
|
|
|
* thumbnail data loaded. |
1034
|
|
|
* |
1035
|
|
|
* @param PelDataWindow $d |
1036
|
|
|
* the data from which the thumbnail will be |
1037
|
|
|
* extracted. |
1038
|
|
|
* @param integer $offset |
1039
|
|
|
* the offset into the data. |
1040
|
|
|
* @param integer $length |
1041
|
|
|
* the length of the thumbnail. |
1042
|
|
|
* @throws PelIfdException |
1043
|
|
|
* @throws PelDataWindowWindowException |
1044
|
|
|
*/ |
1045
|
15 |
|
private function safeSetThumbnail(PelDataWindow $d, $offset, $length) |
1046
|
|
|
{ |
1047
|
|
|
/* |
1048
|
|
|
* Load the thumbnail if both the offset and the length is |
1049
|
|
|
* available. |
1050
|
|
|
*/ |
1051
|
15 |
|
if ($offset > 0 && $length > 0) { |
1052
|
|
|
/* |
1053
|
|
|
* Some images have a broken length, so we try to carefully |
1054
|
|
|
* check the length before we store the thumbnail. |
1055
|
|
|
*/ |
1056
|
15 |
|
if ($offset + $length > $d->getSize()) { |
1057
|
1 |
|
Pel::maybeThrow(new PelIfdException('Thumbnail length %d bytes ' . 'adjusted to %d bytes.', $length, $d->getSize() - $offset)); |
1058
|
1 |
|
$length = $d->getSize() - $offset; |
1059
|
|
|
} |
1060
|
|
|
|
1061
|
|
|
/* Now set the thumbnail normally. */ |
1062
|
|
|
try { |
1063
|
15 |
|
$this->setThumbnail($d->getClone($offset, $length)); |
1064
|
1 |
|
} catch (PelDataWindowWindowException $e) { |
1065
|
1 |
|
Pel::maybeThrow(new PelIfdException($e->getMessage())); |
1066
|
|
|
} |
1067
|
|
|
} |
1068
|
15 |
|
} |
1069
|
|
|
|
1070
|
|
|
/** |
1071
|
|
|
* Set thumbnail data. |
1072
|
|
|
* |
1073
|
|
|
* Use this to embed an arbitrary JPEG image within this IFD. The |
1074
|
|
|
* data will be checked to ensure that it has a proper {@link |
1075
|
|
|
* PelJpegMarker::EOI} at the end. If not, then the length is |
1076
|
|
|
* adjusted until one if found. An {@link PelIfdException} might be |
1077
|
|
|
* thrown (depending on {@link Pel::$strict}) this case. |
1078
|
|
|
* |
1079
|
|
|
* @param PelDataWindow $d |
1080
|
|
|
* the thumbnail data. |
1081
|
|
|
* @throws PelIfdException |
1082
|
|
|
*/ |
1083
|
14 |
|
public function setThumbnail(PelDataWindow $d) |
1084
|
|
|
{ |
1085
|
14 |
|
$size = $d->getSize(); |
1086
|
|
|
/* Now move backwards until we find the EOI JPEG marker. */ |
1087
|
14 |
|
while ($d->getByte($size - 2) != 0xFF || $d->getByte($size - 1) != PelJpegMarker::EOI) { |
1088
|
1 |
|
$size --; |
1089
|
|
|
} |
1090
|
|
|
|
1091
|
14 |
|
if ($size != $d->getSize()) { |
1092
|
1 |
|
Pel::maybeThrow(new PelIfdException('Decrementing thumbnail size ' . 'to %d bytes', $size)); |
1093
|
|
|
} |
1094
|
14 |
|
$this->thumb_data = $d->getClone(0, $size); |
1095
|
14 |
|
} |
1096
|
|
|
|
1097
|
|
|
/** |
1098
|
|
|
* Get the type of this directory. |
1099
|
|
|
* |
1100
|
|
|
* @return int of {@link PelIfd::IFD0}, {@link PelIfd::IFD1}, {@link |
1101
|
|
|
* PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link |
1102
|
|
|
* PelIfd::INTEROPERABILITY}. |
1103
|
|
|
*/ |
1104
|
8 |
|
public function getType() |
1105
|
|
|
{ |
1106
|
8 |
|
return $this->type; |
1107
|
|
|
} |
1108
|
|
|
|
1109
|
|
|
/** |
1110
|
|
|
* Is a given tag valid for this IFD? |
1111
|
|
|
* |
1112
|
|
|
* Different types of IFDs can contain different kinds of tags --- |
1113
|
|
|
* the {@link IFD0} type, for example, cannot contain a {@link |
1114
|
|
|
* PelTag::GPS_LONGITUDE} tag. |
1115
|
|
|
* |
1116
|
|
|
* A special exception is tags with values above 0xF000. They are |
1117
|
|
|
* treated as private tags and will be allowed everywhere (use this |
1118
|
|
|
* for testing or for implementing your own types of tags). |
1119
|
|
|
* |
1120
|
|
|
* @param integer $tag |
1121
|
|
|
* the tag. |
1122
|
|
|
* @return boolean true if the tag is considered valid in this IFD, |
1123
|
|
|
* false otherwise. |
1124
|
|
|
* @see getValidTags() |
1125
|
|
|
*/ |
1126
|
31 |
|
public function isValidTag($tag) |
1127
|
|
|
{ |
1128
|
31 |
|
return $tag > 0xF000 || in_array($tag, $this->getValidTags()); |
1129
|
|
|
} |
1130
|
|
|
|
1131
|
|
|
/** |
1132
|
|
|
* Returns a list of valid tags for this IFD. |
1133
|
|
|
* |
1134
|
|
|
* @return array an array of {@link PelTag}s which are valid for |
1135
|
|
|
* this IFD. |
1136
|
|
|
*/ |
1137
|
24 |
|
public function getValidTags() |
1138
|
|
|
{ |
1139
|
24 |
|
$tp = $this->type; |
1140
|
24 |
|
if ($tp === self::IFD1) { |
1141
|
|
|
// return the same for IFD0 and IFD1 |
1142
|
13 |
|
$tp = self::IFD0; |
1143
|
|
|
} |
1144
|
24 |
|
if (array_key_exists($tp, self::VALID_TAGS)) { |
1145
|
24 |
|
return self::VALID_TAGS[$tp]; |
1146
|
|
|
} |
1147
|
|
|
return []; |
1148
|
|
|
} |
1149
|
|
|
|
1150
|
|
|
/** |
1151
|
|
|
* Get the name of an IFD type. |
1152
|
|
|
* |
1153
|
|
|
* @param integer $type |
1154
|
|
|
* one of {@link PelIfd::IFD0}, {@link PelIfd::IFD1}, |
1155
|
|
|
* {@link PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link |
1156
|
|
|
* PelIfd::INTEROPERABILITY}. |
1157
|
|
|
* @return string the name of type. |
1158
|
|
|
*/ |
1159
|
2 |
|
public static function getTypeName($type) |
1160
|
|
|
{ |
1161
|
2 |
|
if (array_key_exists($type, self::TYPE_NAMES)) { |
1162
|
2 |
|
return self::TYPE_NAMES[$type]; |
1163
|
|
|
} |
1164
|
|
|
throw new PelIfdException('Unknown IFD type: %d', $type); |
1165
|
|
|
} |
1166
|
|
|
|
1167
|
|
|
/** |
1168
|
|
|
* Get the name of this directory. |
1169
|
|
|
* |
1170
|
|
|
* @return string the name of this directory. |
1171
|
|
|
*/ |
1172
|
2 |
|
public function getName() |
1173
|
|
|
{ |
1174
|
2 |
|
return $this->getTypeName($this->type); |
1175
|
|
|
} |
1176
|
|
|
|
1177
|
|
|
/** |
1178
|
|
|
* Adds an entry to the directory. |
1179
|
|
|
* |
1180
|
|
|
* @param PelEntry $e |
1181
|
|
|
* the entry that will be added. If the entry is not |
1182
|
|
|
* valid in this IFD (as per {@link isValidTag()}) an |
1183
|
|
|
* {@link PelInvalidDataException} is thrown. |
1184
|
|
|
* @todo The entry will be identified with its tag, so each |
1185
|
|
|
* directory can only contain one entry with each tag. Is this a |
1186
|
|
|
* bug? |
1187
|
|
|
*/ |
1188
|
31 |
|
public function addEntry(PelEntry $e) |
1189
|
|
|
{ |
1190
|
31 |
|
if ($this->isValidTag($e->getTag())) { |
1191
|
31 |
|
$e->setIfdType($this->type); |
1192
|
31 |
|
$this->entries[$e->getTag()] = $e; |
1193
|
|
|
} else { |
1194
|
2 |
|
throw new PelInvalidDataException("IFD %s cannot hold\n%s", $this->getName(), $e->__toString()); |
1195
|
|
|
} |
1196
|
31 |
|
} |
1197
|
|
|
|
1198
|
|
|
/** |
1199
|
|
|
* Does a given tag exist in this IFD? |
1200
|
|
|
* |
1201
|
|
|
* This methods is part of the ArrayAccess SPL interface for |
1202
|
|
|
* overriding array access of objects, it allows you to check for |
1203
|
|
|
* existance of an entry in the IFD: |
1204
|
|
|
* |
1205
|
|
|
* <code> |
1206
|
|
|
* if (isset($ifd[PelTag::FNUMBER])) |
1207
|
|
|
* // ... do something with the F-number. |
1208
|
|
|
* </code> |
1209
|
|
|
* |
1210
|
|
|
* @param integer $tag |
1211
|
|
|
* the offset to check. |
1212
|
|
|
* @return boolean whether the tag exists. |
1213
|
|
|
*/ |
1214
|
1 |
|
public function offsetExists($tag): bool |
1215
|
|
|
{ |
1216
|
1 |
|
return isset($this->entries[$tag]); |
1217
|
|
|
} |
1218
|
|
|
|
1219
|
|
|
/** |
1220
|
|
|
* Retrieve a given tag from this IFD. |
1221
|
|
|
* |
1222
|
|
|
* This methods is part of the ArrayAccess SPL interface for |
1223
|
|
|
* overriding array access of objects, it allows you to read entries |
1224
|
|
|
* from the IFD the same was as for an array: |
1225
|
|
|
* |
1226
|
|
|
* <code> |
1227
|
|
|
* $entry = $ifd[PelTag::FNUMBER]; |
1228
|
|
|
* </code> |
1229
|
|
|
* |
1230
|
|
|
* @param integer $tag |
1231
|
|
|
* the tag to return. It is an error to ask for a tag |
1232
|
|
|
* which is not in the IFD, just like asking for a non-existant |
1233
|
|
|
* array entry. |
1234
|
|
|
* @return PelEntry the entry. |
1235
|
|
|
*/ |
1236
|
1 |
|
public function offsetGet($tag): PelEntry |
1237
|
|
|
{ |
1238
|
1 |
|
return $this->entries[$tag]; |
1239
|
|
|
} |
1240
|
|
|
|
1241
|
|
|
/** |
1242
|
|
|
* Set or update a given tag in this IFD. |
1243
|
|
|
* |
1244
|
|
|
* This methods is part of the ArrayAccess SPL interface for |
1245
|
|
|
* overriding array access of objects, it allows you to add new |
1246
|
|
|
* entries or replace esisting entries by doing: |
1247
|
|
|
* |
1248
|
|
|
* <code> |
1249
|
|
|
* $ifd[PelTag::EXPOSURE_BIAS_VALUE] = $entry; |
1250
|
|
|
* </code> |
1251
|
|
|
* |
1252
|
|
|
* Note that the actual array index passed is ignored! Instead the |
1253
|
|
|
* {@link PelTag} from the entry is used. |
1254
|
|
|
* |
1255
|
|
|
* @param integer $tag |
1256
|
|
|
* unused. |
1257
|
|
|
* @param PelEntry $e |
1258
|
|
|
* the new value. |
1259
|
|
|
* @throws PelInvalidArgumentException |
1260
|
|
|
*/ |
1261
|
1 |
|
public function offsetSet($tag, $e): void |
1262
|
|
|
{ |
1263
|
1 |
|
if ($e instanceof PelEntry) { |
|
|
|
|
1264
|
1 |
|
$tag = $e->getTag(); |
1265
|
1 |
|
$this->entries[$tag] = $e; |
1266
|
|
|
} else { |
1267
|
|
|
throw new PelInvalidArgumentException('Argument "%s" must be a PelEntry.', $e); |
1268
|
|
|
} |
1269
|
1 |
|
} |
1270
|
|
|
|
1271
|
|
|
/** |
1272
|
|
|
* Unset a given tag in this IFD. |
1273
|
|
|
* |
1274
|
|
|
* This methods is part of the ArrayAccess SPL interface for |
1275
|
|
|
* overriding array access of objects, it allows you to delete |
1276
|
|
|
* entries in the IFD by doing: |
1277
|
|
|
* |
1278
|
|
|
* <code> |
1279
|
|
|
* unset($ifd[PelTag::EXPOSURE_BIAS_VALUE]) |
1280
|
|
|
* </code> |
1281
|
|
|
* |
1282
|
|
|
* @param integer $offset |
1283
|
|
|
* the offset to delete. |
1284
|
|
|
*/ |
1285
|
|
|
# [\ReturnTypeWillChange] |
1286
|
4 |
|
public function offsetUnset($offset): void |
1287
|
|
|
{ |
1288
|
4 |
|
unset($this->entries[$offset]); |
1289
|
4 |
|
} |
1290
|
|
|
|
1291
|
|
|
/** |
1292
|
|
|
* Retrieve an entry. |
1293
|
|
|
* |
1294
|
|
|
* @param integer $tag |
1295
|
|
|
* the tag identifying the entry. |
1296
|
|
|
* @return PelEntry the entry associated with the tag, or null if no |
1297
|
|
|
* such entry exists. |
1298
|
|
|
*/ |
1299
|
25 |
|
public function getEntry($tag) |
1300
|
|
|
{ |
1301
|
25 |
|
if (isset($this->entries[$tag])) { |
1302
|
24 |
|
return $this->entries[$tag]; |
1303
|
|
|
} else { |
1304
|
4 |
|
return null; |
1305
|
|
|
} |
1306
|
|
|
} |
1307
|
|
|
|
1308
|
|
|
/** |
1309
|
|
|
* Returns all entries contained in this IFD. |
1310
|
|
|
* |
1311
|
|
|
* @return array an array of {@link PelEntry} objects, or rather |
1312
|
|
|
* descendant classes. The array has {@link PelTag}s as keys |
1313
|
|
|
* and the entries as values. |
1314
|
|
|
* @see PelIfd::getEntry |
1315
|
|
|
* @see PelIfd::getIterator |
1316
|
|
|
*/ |
1317
|
12 |
|
public function getEntries() |
1318
|
|
|
{ |
1319
|
12 |
|
return $this->entries; |
1320
|
|
|
} |
1321
|
|
|
|
1322
|
|
|
/** |
1323
|
|
|
* Return an iterator for all entries contained in this IFD. |
1324
|
|
|
* |
1325
|
|
|
* Used with foreach as in |
1326
|
|
|
* |
1327
|
|
|
* <code> |
1328
|
|
|
* foreach ($ifd as $tag => $entry) { |
1329
|
|
|
* // $tag is now a PelTag and $entry is a PelEntry object. |
1330
|
|
|
* } |
1331
|
|
|
* </code> |
1332
|
|
|
* |
1333
|
|
|
* @return \ArrayIterator an iterator using the {@link PelTag tags} as |
1334
|
|
|
* keys and the entries as values. |
1335
|
|
|
*/ |
1336
|
2 |
|
public function getIterator(): \ArrayIterator |
1337
|
|
|
{ |
1338
|
2 |
|
return new \ArrayIterator($this->entries); |
1339
|
|
|
} |
1340
|
|
|
|
1341
|
|
|
/** |
1342
|
|
|
* Returns available thumbnail data. |
1343
|
|
|
* |
1344
|
|
|
* @return string the bytes in the thumbnail, if any. If the IFD |
1345
|
|
|
* does not contain any thumbnail data, the empty string is |
1346
|
|
|
* returned. |
1347
|
|
|
* @throws PelDataWindowOffsetException |
1348
|
|
|
* @todo Throw an exception instead when no data is available? |
1349
|
|
|
* @todo Return the $this->thumb_data object instead of the bytes? |
1350
|
|
|
*/ |
1351
|
12 |
|
public function getThumbnailData() |
1352
|
|
|
{ |
1353
|
12 |
|
if ($this->thumb_data !== null) { |
1354
|
11 |
|
return $this->thumb_data->getBytes(); |
1355
|
|
|
} else { |
1356
|
12 |
|
return ''; |
1357
|
|
|
} |
1358
|
|
|
} |
1359
|
|
|
|
1360
|
|
|
/** |
1361
|
|
|
* Make this directory point to a new directory. |
1362
|
|
|
* |
1363
|
|
|
* @param PelIfd $i |
1364
|
|
|
* the IFD that this directory will point to. |
1365
|
|
|
*/ |
1366
|
|
|
public function setNextIfd(PelIfd $i) |
1367
|
|
|
{ |
1368
|
|
|
$this->next = $i; |
1369
|
|
|
} |
1370
|
|
|
|
1371
|
|
|
/** |
1372
|
|
|
* Return the IFD pointed to by this directory. |
1373
|
|
|
* |
1374
|
|
|
* @return PelIfd the next IFD, following this IFD. If this is the |
1375
|
|
|
* last IFD, null is returned. |
1376
|
|
|
*/ |
1377
|
12 |
|
public function getNextIfd() |
1378
|
|
|
{ |
1379
|
12 |
|
return $this->next; |
1380
|
|
|
} |
1381
|
|
|
|
1382
|
|
|
/** |
1383
|
|
|
* Check if this is the last IFD. |
1384
|
|
|
* |
1385
|
|
|
* @return boolean true if there are no following IFD, false |
1386
|
|
|
* otherwise. |
1387
|
|
|
*/ |
1388
|
11 |
|
public function isLastIfd() |
1389
|
|
|
{ |
1390
|
11 |
|
return $this->next === null; |
1391
|
|
|
} |
1392
|
|
|
|
1393
|
|
|
/** |
1394
|
|
|
* Add a sub-IFD. |
1395
|
|
|
* |
1396
|
|
|
* Any previous sub-IFD of the same type will be overwritten. |
1397
|
|
|
* |
1398
|
|
|
* @param PelIfd $sub |
1399
|
|
|
* the sub IFD. The type of must be one of {@link |
1400
|
|
|
* PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link |
1401
|
|
|
* PelIfd::INTEROPERABILITY}. |
1402
|
|
|
*/ |
1403
|
3 |
|
public function addSubIfd(PelIfd $sub) |
1404
|
|
|
{ |
1405
|
3 |
|
$this->sub[$sub->type] = $sub; |
1406
|
3 |
|
} |
1407
|
|
|
|
1408
|
|
|
/** |
1409
|
|
|
* Return a sub IFD. |
1410
|
|
|
* |
1411
|
|
|
* @param integer $type |
1412
|
|
|
* the type of the sub IFD. This must be one of {@link |
1413
|
|
|
* PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link |
1414
|
|
|
* PelIfd::INTEROPERABILITY}. |
1415
|
|
|
* @return PelIfd the IFD associated with the type, or null if that |
1416
|
|
|
* sub IFD does not exist. |
1417
|
|
|
*/ |
1418
|
13 |
|
public function getSubIfd($type) |
1419
|
|
|
{ |
1420
|
13 |
|
if (isset($this->sub[$type])) { |
1421
|
13 |
|
return $this->sub[$type]; |
1422
|
|
|
} else { |
1423
|
|
|
return null; |
1424
|
|
|
} |
1425
|
|
|
} |
1426
|
|
|
|
1427
|
|
|
/** |
1428
|
|
|
* Get all sub IFDs. |
1429
|
|
|
* |
1430
|
|
|
* @return array an associative array with (IFD-type, {@link |
1431
|
|
|
* PelIfd}) pairs. |
1432
|
|
|
*/ |
1433
|
12 |
|
public function getSubIfds() |
1434
|
|
|
{ |
1435
|
12 |
|
return $this->sub; |
1436
|
|
|
} |
1437
|
|
|
|
1438
|
|
|
/** |
1439
|
|
|
* Turn this directory into bytes. |
1440
|
|
|
* |
1441
|
|
|
* This directory will be turned into a byte string, with the |
1442
|
|
|
* specified byte order. The offsets will be calculated from the |
1443
|
|
|
* offset given. |
1444
|
|
|
* |
1445
|
|
|
* @param integer $offset |
1446
|
|
|
* the offset of the first byte of this directory. |
1447
|
|
|
* @param boolean $order |
1448
|
|
|
* the byte order that should be used when |
1449
|
|
|
* turning integers into bytes. This should be one of {@link |
1450
|
|
|
* PelConvert::LITTLE_ENDIAN} and {@link PelConvert::BIG_ENDIAN}. |
1451
|
|
|
*/ |
1452
|
11 |
|
public function getBytes($offset, $order) |
1453
|
|
|
{ |
1454
|
11 |
|
$bytes = ''; |
1455
|
11 |
|
$extra_bytes = ''; |
1456
|
|
|
|
1457
|
11 |
|
Pel::debug('Bytes from IDF will start at offset %d within Exif data', $offset); |
1458
|
|
|
|
1459
|
11 |
|
$n = count($this->entries) + count($this->sub); |
1460
|
11 |
|
if ($this->thumb_data !== null) { |
1461
|
|
|
/* |
1462
|
|
|
* We need two extra entries for the thumbnail offset and |
1463
|
|
|
* length. |
1464
|
|
|
*/ |
1465
|
|
|
$n += 2; |
1466
|
|
|
} |
1467
|
|
|
|
1468
|
11 |
|
$bytes .= PelConvert::shortToBytes($n, $order); |
1469
|
|
|
|
1470
|
|
|
/* |
1471
|
|
|
* Initialize offset of extra data. This included the bytes |
1472
|
|
|
* preceding this IFD, the bytes needed for the count of entries, |
1473
|
|
|
* the entries themselves (and sub entries), the extra data in the |
1474
|
|
|
* entries, and the IFD link. |
1475
|
|
|
*/ |
1476
|
11 |
|
$end = $offset + 2 + 12 * $n + 4; |
1477
|
|
|
|
1478
|
11 |
|
foreach ($this->entries as $tag => $entry) { |
1479
|
|
|
/* Each entry is 12 bytes long. */ |
1480
|
11 |
|
$bytes .= PelConvert::shortToBytes($entry->getTag(), $order); |
1481
|
11 |
|
$bytes .= PelConvert::shortToBytes($entry->getFormat(), $order); |
1482
|
11 |
|
$bytes .= PelConvert::longToBytes($entry->getComponents(), $order); |
1483
|
|
|
|
1484
|
|
|
/* |
1485
|
|
|
* Size? If bigger than 4 bytes, the actual data is not in |
1486
|
|
|
* the entry but somewhere else. |
1487
|
|
|
*/ |
1488
|
11 |
|
$data = $entry->getBytes($order); |
1489
|
11 |
|
$s = strlen($data); |
1490
|
11 |
|
if ($s > 4) { |
1491
|
11 |
|
Pel::debug('Data size %d too big, storing at offset %d instead.', $s, $end); |
1492
|
11 |
|
$bytes .= PelConvert::longToBytes($end, $order); |
1493
|
11 |
|
$extra_bytes .= $data; |
1494
|
11 |
|
$end += $s; |
1495
|
|
|
} else { |
1496
|
8 |
|
Pel::debug('Data size %d fits.', $s); |
1497
|
|
|
/* |
1498
|
|
|
* Copy data directly, pad with NULL bytes as necessary to |
1499
|
|
|
* fill out the four bytes available. |
1500
|
|
|
*/ |
1501
|
11 |
|
$bytes .= $data . str_repeat(chr(0), 4 - $s); |
1502
|
|
|
} |
1503
|
|
|
} |
1504
|
|
|
|
1505
|
11 |
|
if ($this->thumb_data !== null) { |
1506
|
|
|
Pel::debug('Appending %d bytes of thumbnail data at %d', $this->thumb_data->getSize(), $end); |
1507
|
|
|
// TODO: make PelEntry a class that can be constructed with |
1508
|
|
|
// arguments corresponding to the newt four lines. |
1509
|
|
|
$bytes .= PelConvert::shortToBytes(PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH, $order); |
1510
|
|
|
$bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order); |
1511
|
|
|
$bytes .= PelConvert::longToBytes(1, $order); |
1512
|
|
|
$bytes .= PelConvert::longToBytes($this->thumb_data->getSize(), $order); |
1513
|
|
|
|
1514
|
|
|
$bytes .= PelConvert::shortToBytes(PelTag::JPEG_INTERCHANGE_FORMAT, $order); |
1515
|
|
|
$bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order); |
1516
|
|
|
$bytes .= PelConvert::longToBytes(1, $order); |
1517
|
|
|
$bytes .= PelConvert::longToBytes($end, $order); |
1518
|
|
|
|
1519
|
|
|
$extra_bytes .= $this->thumb_data->getBytes(); |
1520
|
|
|
$end += $this->thumb_data->getSize(); |
1521
|
|
|
} |
1522
|
|
|
|
1523
|
|
|
/* Find bytes from sub IFDs. */ |
1524
|
11 |
|
$sub_bytes = ''; |
1525
|
11 |
|
foreach ($this->sub as $type => $sub) { |
1526
|
|
|
if ($type == PelIfd::EXIF) { |
1527
|
|
|
$tag = PelTag::EXIF_IFD_POINTER; |
1528
|
|
|
} elseif ($type == PelIfd::GPS) { |
1529
|
|
|
$tag = PelTag::GPS_INFO_IFD_POINTER; |
1530
|
|
|
} elseif ($type == PelIfd::INTEROPERABILITY) { |
1531
|
|
|
$tag = PelTag::INTEROPERABILITY_IFD_POINTER; |
1532
|
|
|
} else { |
1533
|
|
|
// PelConvert::BIG_ENDIAN is the default used by PelConvert |
1534
|
|
|
$tag = PelConvert::BIG_ENDIAN; |
1535
|
|
|
} |
1536
|
|
|
/* Make an aditional entry with the pointer. */ |
1537
|
|
|
$bytes .= PelConvert::shortToBytes($tag, $order); |
|
|
|
|
1538
|
|
|
/* Next the format, which is always unsigned long. */ |
1539
|
|
|
$bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order); |
1540
|
|
|
/* There is only one component. */ |
1541
|
|
|
$bytes .= PelConvert::longToBytes(1, $order); |
1542
|
|
|
|
1543
|
|
|
$data = $sub->getBytes($end, $order); |
1544
|
|
|
$s = strlen($data); |
1545
|
|
|
$sub_bytes .= $data; |
1546
|
|
|
|
1547
|
|
|
$bytes .= PelConvert::longToBytes($end, $order); |
1548
|
|
|
$end += $s; |
1549
|
|
|
} |
1550
|
|
|
|
1551
|
|
|
/* Make link to next IFD, if any */ |
1552
|
11 |
|
if ($this->isLastIFD()) { |
1553
|
11 |
|
$link = 0; |
1554
|
|
|
} else { |
1555
|
|
|
$link = $end; |
1556
|
|
|
} |
1557
|
|
|
|
1558
|
11 |
|
Pel::debug('Link to next IFD: %d', $link); |
1559
|
|
|
|
1560
|
11 |
|
$bytes .= PelConvert::longtoBytes($link, $order); |
1561
|
|
|
|
1562
|
11 |
|
$bytes .= $extra_bytes . $sub_bytes; |
1563
|
|
|
|
1564
|
11 |
|
if (! $this->isLastIfd()) { |
1565
|
|
|
$bytes .= $this->next->getBytes($end, $order); |
1566
|
|
|
} |
1567
|
11 |
|
return $bytes; |
1568
|
|
|
} |
1569
|
|
|
|
1570
|
|
|
/** |
1571
|
|
|
* Turn this directory into text. |
1572
|
|
|
* |
1573
|
|
|
* @return string information about the directory, mainly for |
1574
|
|
|
* debugging. |
1575
|
|
|
*/ |
1576
|
|
|
public function __toString() |
1577
|
|
|
{ |
1578
|
|
|
$str = Pel::fmt("Dumping IFD %s with %d entries...\n", $this->getName(), count($this->entries)); |
1579
|
|
|
|
1580
|
|
|
foreach ($this->entries as $entry) { |
1581
|
|
|
$str .= $entry->__toString(); |
1582
|
|
|
} |
1583
|
|
|
$str .= Pel::fmt("Dumping %d sub IFDs...\n", count($this->sub)); |
1584
|
|
|
|
1585
|
|
|
foreach ($this->sub as $ifd) { |
1586
|
|
|
$str .= $ifd->__toString(); |
1587
|
|
|
} |
1588
|
|
|
if ($this->next !== null) { |
1589
|
|
|
$str .= $this->next->__toString(); |
1590
|
|
|
} |
1591
|
|
|
return $str; |
1592
|
|
|
} |
1593
|
|
|
} |
1594
|
|
|
|
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.