Passed
Push — master ( 7e7163...1836d2 )
by Johannes
06:56
created

PelIfd::getOffsetToNextIfd()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.0119

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 12
nc 4
nop 3
dl 0
loc 19
ccs 10
cts 11
cp 0.9091
crap 4.0119
rs 9.8666
c 1
b 0
f 0
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)
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

822
    public function loadSingleMakerNotesValue(/** @scrutinizer ignore-unused */ $type, PelDataWindow $data, $offset, $size, $i, $format)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
823
    {
824 3
        $elemSize = PelFormat::getSize($format);
825 3
        if ($size > 0) {
826 3
            $subdata = $data->getClone($offset + $i * $elemSize, $elemSize);
0 ignored issues
show
Bug introduced by
It seems like $elemSize can also be of type string; however, parameter $size of lsolesen\pel\PelDataWindow::getClone() does only seem to accept integer|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

826
            $subdata = $data->getClone($offset + $i * $elemSize, /** @scrutinizer ignore-type */ $elemSize);
Loading history...
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) {
0 ignored issues
show
introduced by
$e is always a sub-type of lsolesen\pel\PelEntry.
Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $tag can also be of type false; however, parameter $value of lsolesen\pel\PelConvert::shortToBytes() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1537
            $bytes .= PelConvert::shortToBytes(/** @scrutinizer ignore-type */ $tag, $order);
Loading history...
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