summaryrefslogtreecommitdiff
path: root/modules/autorotate/lib/pel/PelTiff.php
blob: 6f4af75b50bd9e4cf2e8a6660cc8ab1d62f0907f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
<?php

/**
 *  PEL: PHP Exif Library.  A library with support for reading and
 *  writing all Exif headers in JPEG and TIFF images using PHP.
 *
 *  Copyright (C) 2004, 2005, 2006  Martin Geisler.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program in the file COPYING; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 *  Boston, MA 02110-1301 USA
 */

/* $Id$ */


/**
 * Classes for dealing with TIFF data.
 *
 * @author Martin Geisler <mgeisler@users.sourceforge.net>
 * @version $Revision$
 * @date $Date$
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public
 * License (GPL)
 * @package PEL
 */

/**#@+ Required class definitions. */
require_once('PelDataWindow.php');
require_once('PelIfd.php');
require_once('Pel.php');
/**#@-*/


/**
 * Class for handling TIFF data.
 *
 * Exif data is actually an extension of the TIFF file format.  TIFF
 * images consist of a number of {@link PelIfd Image File Directories}
 * (IFDs), each containing a number of {@link PelEntry entries}.  The
 * IFDs are linked to each other --- one can get hold of the first one
 * with the {@link getIfd()} method.
 *
 * To parse a TIFF image for Exif data one would do:
 *
 * <code>
 * $tiff = new PelTiff($data);
 * $ifd0 = $tiff->getIfd();
 * $exif = $ifd0->getSubIfd(PelIfd::EXIF);
 * $ifd1 = $ifd0->getNextIfd();
 * </code>
 *
 * Should one have some image data of an unknown type, then the {@link
 * PelTiff::isValid()} function is handy: it will quickly test if the
 * data could be valid TIFF data.  The {@link PelJpeg::isValid()}
 * function does the same for JPEG images.
 *
 * @author Martin Geisler <mgeisler@users.sourceforge.net>
 * @package PEL
 */
class PelTiff {

    /**
     * TIFF header.
     *
     * This must follow after the two bytes indicating the byte order.
     */
    const TIFF_HEADER = 0x002A;

    /**
     * The first Image File Directory, if any.
     *
     * If set, then the type of the IFD must be {@link PelIfd::IFD0}.
     *
     * @var PelIfd
     */
    private $ifd = null;


    /**
     * Construct a new object for holding TIFF data.
     *
     * The new object will be empty (with no {@link PelIfd}) unless an
     * argument is given from which it can initialize itself. This can
     * either be the filename of a TIFF image or a {@link PelDataWindow}
     * object.
     *
     * Use {@link setIfd()} to explicitly set the IFD.
     */
    function __construct($data = false) {
        if ($data === false)
        return;

        if (is_string($data)) {
            Pel::debug('Initializing PelTiff object from %s', $data);
            $this->loadFile($data);
        } elseif ($data instanceof PelDataWindow) {
            Pel::debug('Initializing PelTiff object from PelDataWindow.');
            $this->load($data);
        } else {
            throw new PelInvalidArgumentException('Bad type for $data: %s',
            gettype($data));
        }
    }


    /**
     * Load TIFF data.
     *
     * The data given will be parsed and an internal tree representation
     * will be built.  If the data cannot be parsed correctly, a {@link
     * PelInvalidDataException} is thrown, explaining the problem.
     *
     * @param PelDataWindow the data from which the object will be
     * constructed.  This should be valid TIFF data, coming either
     * directly from a TIFF image or from the Exif data in a JPEG image.
     */
    function load(PelDataWindow $d) {
        Pel::debug('Parsing %d bytes of TIFF data...', $d->getSize());

        /* There must be at least 8 bytes available: 2 bytes for the byte
         * order, 2 bytes for the TIFF header, and 4 bytes for the offset
         * to the first IFD. */
        if ($d->getSize() < 8)
        throw new PelInvalidDataException('Expected at least 8 bytes of TIFF ' .
                                        'data, found just %d bytes.',
        $d->getSize());

        /* Byte order */
        if ($d->strcmp(0, 'II')) {
            Pel::debug('Found Intel byte order');
            $d->setByteOrder(PelConvert::LITTLE_ENDIAN);
        } elseif ($d->strcmp(0, 'MM')) {
            Pel::debug('Found Motorola byte order');
            $d->setByteOrder(PelConvert::BIG_ENDIAN);
        } else {
            throw new PelInvalidDataException('Unknown byte order found in TIFF ' .
                                        'data: 0x%2X%2X',
            $d->getByte(0), $d->getByte(1));
        }

        /* Verify the TIFF header */
        if ($d->getShort(2) != self::TIFF_HEADER)
        throw new PelInvalidDataException('Missing TIFF magic value.');

        /* IFD 0 offset */
        $offset = $d->getLong(4);
        Pel::debug('First IFD at offset %d.', $offset);

        if ($offset > 0) {
            /* Parse the first IFD, this will automatically parse the
             * following IFDs and any sub IFDs. */
            $this->ifd = new PelIfd(PelIfd::IFD0);
            $this->ifd->load($d, $offset);
        }
    }


    /**
     * Load data from a file into a TIFF object.
     *
     * @param string the filename.  This must be a readable file.
     */
    function loadFile($filename) {
        $this->load(new PelDataWindow(file_get_contents($filename)));
    }


    /**
     * Set the first IFD.
     *
     * @param PelIfd the new first IFD, which must be of type {@link
     * PelIfd::IFD0}.
     */
    function setIfd(PelIfd $ifd) {
        if ($ifd->getType() != PelIfd::IFD0)
        throw new PelInvalidDataException('Invalid type of IFD: %d, expected %d.',
        $ifd->getType(), PelIfd::IFD0);

        $this->ifd = $ifd;
    }


    /**
     * Return the first IFD.
     *
     * @return PelIfd the first IFD contained in the TIFF data, if any.
     * If there is no IFD null will be returned.
     */
    function getIfd() {
        return $this->ifd;
    }


    /**
     * Turn this object into bytes.
     *
     * TIFF images can have {@link PelConvert::LITTLE_ENDIAN
     * little-endian} or {@link PelConvert::BIG_ENDIAN big-endian} byte
     * order, and so this method takes an argument specifying that.
     *
     * @param PelByteOrder the desired byte order of the TIFF data.
     * This should be one of {@link PelConvert::LITTLE_ENDIAN} or {@link
     * PelConvert::BIG_ENDIAN}.
     *
     * @return string the bytes representing this object.
     */
    function getBytes($order = PelConvert::LITTLE_ENDIAN) {
        if ($order == PelConvert::LITTLE_ENDIAN)
        $bytes = 'II';
        else
        $bytes = 'MM';

        /* TIFF magic number --- fixed value. */
        $bytes .= PelConvert::shortToBytes(self::TIFF_HEADER, $order);

        if ($this->ifd != null) {
            /* IFD 0 offset.  We will always start IDF 0 at an offset of 8
             * bytes (2 bytes for byte order, another 2 bytes for the TIFF
             * header, and 4 bytes for the IFD 0 offset make 8 bytes
             * together).
             */
            $bytes .= PelConvert::longToBytes(8, $order);

            /* The argument specifies the offset of this IFD.  The IFD will
             * use this to calculate offsets from the entries to their data,
             * all those offsets are absolute offsets counted from the
             * beginning of the data. */
            $bytes .= $this->ifd->getBytes(8, $order);
        } else {
            $bytes .= PelConvert::longToBytes(0, $order);
        }

        return $bytes;
    }


    /**
     * Return a string representation of this object.
     *
     * @return string a string describing this object.  This is mostly useful
     * for debugging.
     */
    function __toString() {
        $str = Pel::fmt("Dumping TIFF data...\n");
        if ($this->ifd != null)
        $str .= $this->ifd->__toString();

        return $str;
    }


    /**
     * Check if data is valid TIFF data.
     *
     * This will read just enough data from the data window to determine
     * if the data could be a valid TIFF data.  This means that the
     * check is more like a heuristic than a rigorous check.
     *
     * @param PelDataWindow the bytes that will be examined.
     *
     * @return boolean true if the data looks like valid TIFF data,
     * false otherwise.
     *
     * @see PelJpeg::isValid()
     */
    static function isValid(PelDataWindow $d) {
        /* First check that we have enough data. */
        if ($d->getSize() < 8)
        return false;

        /* Byte order */
        if ($d->strcmp(0, 'II')) {
            $d->setByteOrder(PelConvert::LITTLE_ENDIAN);
        } elseif ($d->strcmp(0, 'MM')) {
            Pel::debug('Found Motorola byte order');
            $d->setByteOrder(PelConvert::BIG_ENDIAN);
        } else {
            return false;
        }

        /* Verify the TIFF header */
        return $d->getShort(2) == self::TIFF_HEADER;
    }

}