Commit | Line | Data |
---|---|---|
dc70b4e5 | 1 | <?php |
a9229942 | 2 | |
dc70b4e5 MS |
3 | namespace wcf\util; |
4 | ||
5 | /** | |
6 | * Provides exif-related functions. | |
a9229942 TD |
7 | * |
8 | * @author Matthias Schmidt, Marcel Werk | |
9 | * @copyright 2001-2019 WoltLab GmbH | |
10 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> | |
11 | * @package WoltLabSuite\Core\Util | |
dc70b4e5 | 12 | */ |
a9229942 TD |
13 | final class ExifUtil |
14 | { | |
15 | /** | |
16 | * orientation value for the original orientation | |
17 | * @see http://jpegclub.org/exif_orientation.html | |
18 | * @var int | |
19 | */ | |
20 | const ORIENTATION_ORIGINAL = 1; | |
21 | ||
22 | /** | |
23 | * orientation value of a horizontal flip | |
24 | * @see http://jpegclub.org/exif_orientation.html | |
25 | * @var int | |
26 | */ | |
27 | const ORIENTATION_HORIZONTAL_FLIP = 2; | |
28 | ||
29 | /** | |
30 | * orientation value of a 180 degree rotation | |
31 | * @see http://jpegclub.org/exif_orientation.html | |
32 | * @var int | |
33 | */ | |
34 | const ORIENTATION_180_ROTATE = 3; | |
35 | ||
36 | /** | |
37 | * orientation value of a vertical flip | |
38 | * @see http://jpegclub.org/exif_orientation.html | |
39 | * @var int | |
40 | */ | |
41 | const ORIENTATION_VERTICAL_FLIP = 4; | |
42 | ||
43 | /** | |
44 | * orientation value of a vertical flip and a 270 degree rotation | |
45 | * @see http://jpegclub.org/exif_orientation.html | |
46 | * @var int | |
47 | */ | |
48 | const ORIENTATION_VERTICAL_FLIP_270_ROTATE = 5; | |
49 | ||
50 | /** | |
51 | * orientation value of a 90 degree rotation | |
52 | * @see http://jpegclub.org/exif_orientation.html | |
53 | * @var int | |
54 | */ | |
55 | const ORIENTATION_90_ROTATE = 6; | |
56 | ||
57 | /** | |
58 | * orientation value of a horizontal flip and a 270 degree rotation | |
59 | * @see http://jpegclub.org/exif_orientation.html | |
60 | * @var int | |
61 | */ | |
62 | const ORIENTATION_HORIZONTAL_FLIP_270_ROTATE = 7; | |
63 | ||
64 | /** | |
65 | * orientation value of a 270 degree rotation | |
66 | * @see http://jpegclub.org/exif_orientation.html | |
67 | * @var int | |
68 | */ | |
69 | const ORIENTATION_270_ROTATE = 8; | |
70 | ||
71 | /** | |
72 | * Forbid creation of ExifUtil objects. | |
73 | */ | |
74 | private function __construct() | |
75 | { | |
76 | // does nothing | |
77 | } | |
78 | ||
79 | /** | |
80 | * Returns the exif data of the image at the given location or an empty | |
81 | * array if the exif data can't be read. | |
82 | * | |
83 | * @param string $filename | |
84 | * @return array | |
85 | */ | |
86 | public static function getExifData($filename) | |
87 | { | |
88 | if (\function_exists('exif_read_data')) { | |
89 | $exifData = @exif_read_data($filename, '', true); | |
90 | if ($exifData !== false) { | |
91 | return $exifData; | |
92 | } | |
93 | } | |
94 | ||
95 | return []; | |
96 | } | |
97 | ||
98 | /** | |
99 | * Returns the name of the used camera based on the given exif data. | |
100 | * | |
101 | * @param array $exifData | |
102 | * @return string | |
103 | */ | |
104 | public static function getCamera(array $exifData) | |
105 | { | |
106 | $camera = ''; | |
107 | if (isset($exifData['IFD0'])) { | |
108 | $maker = ''; | |
109 | if (!empty($exifData['IFD0']['Make'])) { | |
110 | $maker = $exifData['IFD0']['Make']; | |
111 | } | |
112 | ||
113 | if (!empty($exifData['IFD0']['Model'])) { | |
114 | $camera = $exifData['IFD0']['Model']; | |
115 | if ($maker != '' && \strpos($camera, $maker) === false) { | |
116 | $camera = $maker . ' ' . $camera; | |
117 | } | |
118 | } | |
119 | } | |
120 | ||
121 | return $camera; | |
122 | } | |
123 | ||
124 | /** | |
125 | * Returns the creation timestamp based on the given exif data. | |
126 | * | |
127 | * @param array $exifData | |
128 | * @return string | |
129 | */ | |
130 | public static function getCreationTime(array $exifData) | |
131 | { | |
132 | $creationTime = 0; | |
133 | if (isset($exifData['EXIF'])) { | |
134 | if (isset($exifData['EXIF']['DateTimeOriginal'])) { | |
135 | $creationTime = @\intval(\strtotime($exifData['EXIF']['DateTimeOriginal'])); | |
136 | } elseif (isset($exifData['EXIF']['DateTimeDigitized'])) { | |
137 | $creationTime = @\intval(\strtotime($exifData['EXIF']['DateTimeDigitized'])); | |
138 | } elseif (!empty($exifData['EXIF']['DateTime'])) { | |
139 | $creationTime = @\intval(\strtotime($exifData['EXIF']['DateTime'])); | |
140 | } | |
141 | } | |
142 | if ($creationTime < 0 || $creationTime > 2147483647) { | |
143 | $creationTime = 0; | |
144 | } | |
145 | ||
146 | return $creationTime; | |
147 | } | |
148 | ||
149 | /** | |
150 | * Returns the longitude of the place the image with the given exif data | |
151 | * was taken. | |
152 | * | |
153 | * @param array $exifData | |
154 | * @return float | |
155 | */ | |
156 | public static function getLongitude(array $exifData) | |
157 | { | |
158 | $longitude = 0; | |
159 | if (isset($exifData['GPS']) && isset($exifData['GPS']['GPSLongitudeRef']) && isset($exifData['GPS']['GPSLongitude'])) { | |
160 | $degrees = (isset($exifData['GPS']['GPSLongitude'][0]) ? self::convertCoordinateToDecimal($exifData['GPS']['GPSLongitude'][0]) : 0.0); | |
161 | $minutes = (isset($exifData['GPS']['GPSLongitude'][1]) ? self::convertCoordinateToDecimal($exifData['GPS']['GPSLongitude'][1]) : 0.0); | |
162 | $seconds = (isset($exifData['GPS']['GPSLongitude'][2]) ? self::convertCoordinateToDecimal($exifData['GPS']['GPSLongitude'][2]) : 0.0); | |
163 | $longitude = ($degrees * 60.0 + (($minutes * 60.0 + $seconds) / 60.0)) / 60.0; | |
164 | if ($exifData['GPS']['GPSLongitudeRef'] == 'W') { | |
165 | $longitude *= -1; | |
166 | } | |
167 | } | |
168 | ||
169 | if ($longitude < -180.0 || $longitude > 180.0) { | |
170 | $longitude = 0; | |
171 | } | |
172 | ||
173 | return $longitude; | |
174 | } | |
175 | ||
176 | /** | |
177 | * Returns the latitude of the place the image with the given exif data | |
178 | * was taken. | |
179 | * | |
180 | * @param array $exifData | |
181 | * @return float | |
182 | */ | |
183 | public static function getLatitude(array $exifData) | |
184 | { | |
185 | $latitude = 0; | |
186 | if (isset($exifData['GPS']) && isset($exifData['GPS']['GPSLatitudeRef']) && isset($exifData['GPS']['GPSLatitude'])) { | |
187 | $degrees = isset($exifData['GPS']['GPSLatitude'][0]) ? self::convertCoordinateToDecimal($exifData['GPS']['GPSLatitude'][0]) : 0.0; | |
188 | $minutes = isset($exifData['GPS']['GPSLatitude'][1]) ? self::convertCoordinateToDecimal($exifData['GPS']['GPSLatitude'][1]) : 0.0; | |
189 | $seconds = isset($exifData['GPS']['GPSLatitude'][2]) ? self::convertCoordinateToDecimal($exifData['GPS']['GPSLatitude'][2]) : 0.0; | |
190 | $latitude = ($degrees * 60.0 + (($minutes * 60.0 + $seconds) / 60.0)) / 60.0; | |
191 | if ($exifData['GPS']['GPSLatitudeRef'] == 'S') { | |
192 | $latitude *= -1; | |
193 | } | |
194 | } | |
195 | ||
196 | if ($latitude < -90.0 || $latitude > 90.0) { | |
197 | $latitude = 0; | |
198 | } | |
199 | ||
200 | return $latitude; | |
201 | } | |
202 | ||
203 | /** | |
204 | * Returns the formats exif data. | |
205 | * | |
206 | * @param array $rawExifData | |
207 | * @return array | |
208 | */ | |
209 | public static function getFormattedExifData(array $rawExifData) | |
210 | { | |
211 | $exifData = []; | |
212 | ||
213 | // unit is second (unsigned rational) | |
214 | if (isset($rawExifData['ExposureTime']) && \is_string($rawExifData['ExposureTime'])) { | |
215 | $exifData['ExposureTime'] = $rawExifData['ExposureTime']; | |
216 | } | |
217 | // actual F-number(F-stop) of lens when the image was taken (unsigned rational) | |
218 | if (isset($rawExifData['FNumber']) && \is_string($rawExifData['FNumber'])) { | |
219 | $exifData['FNumber'] = self::convertExifRational($rawExifData['FNumber']); | |
220 | } | |
221 | // unit is millimeter (unsigned rational) | |
222 | if (isset($rawExifData['FocalLength']) && \is_string($rawExifData['FocalLength'])) { | |
223 | $exifData['FocalLength'] = self::convertExifRational($rawExifData['FocalLength']); | |
224 | } | |
225 | /*if (isset($rawExifData['ShutterSpeedValue']) && is_string($rawExifData['ShutterSpeedValue'])) { | |
226 | // To convert this value to ordinary 'Shutter Speed'; calculate this value's power of 2, then reciprocal. | |
227 | // For example, if value is '4', shutter speed is 1/(2^4)=1/16 second. (signed rational) | |
228 | $exifData['ShutterSpeedValue'] = '1/' . round(pow(2, self::convertExifRational($rawExifData['ShutterSpeedValue'])), 0); | |
229 | }*/ | |
230 | if (isset($rawExifData['ISOSpeedRatings'])) { | |
231 | // CCD sensitivity equivalent to Ag-Hr film speedrate. (unsigned short) | |
232 | $exifData['ISOSpeedRatings'] = \intval($rawExifData['ISOSpeedRatings']); | |
233 | } | |
234 | if (isset($rawExifData['Flash'])) { | |
235 | // Indicates the status of flash when the image was shot. (unsigned short) | |
236 | $exifData['Flash'] = \intval($rawExifData['Flash']); | |
237 | } | |
238 | ||
239 | return $exifData; | |
240 | } | |
241 | ||
242 | /** | |
243 | * Returns the orientation of the image based on the given exif data. | |
244 | * | |
245 | * @param array $exifData | |
246 | * @return int | |
247 | */ | |
248 | public static function getOrientation(array $exifData) | |
249 | { | |
250 | $orientation = self::ORIENTATION_ORIGINAL; | |
251 | if (isset($exifData['IFD0']['Orientation'])) { | |
252 | $orientation = \intval($exifData['IFD0']['Orientation']); | |
253 | if ($orientation < self::ORIENTATION_ORIGINAL || $orientation > self::ORIENTATION_270_ROTATE) { | |
254 | $orientation = self::ORIENTATION_ORIGINAL; | |
255 | } | |
256 | } | |
257 | ||
258 | return $orientation; | |
259 | } | |
260 | ||
261 | /** | |
262 | * Converts the format of exif geo tagging coordinates. | |
263 | * | |
264 | * @param string $coordinate | |
265 | * @return double | |
266 | */ | |
267 | private static function convertCoordinateToDecimal($coordinate) | |
268 | { | |
269 | $result = 0.0; | |
270 | $coordinateData = \explode('/', $coordinate); | |
271 | for ($i = 0, $j = \count($coordinateData); $i < $j; $i++) { | |
272 | if ($i == 0) { | |
273 | $result = (float)$coordinateData[0]; | |
274 | } elseif ($coordinateData[$i]) { | |
275 | $result /= (float)$coordinateData[$i]; | |
276 | } | |
277 | } | |
278 | ||
279 | return $result; | |
280 | } | |
281 | ||
282 | /** | |
283 | * Converts a exif rational value to a float. | |
284 | * | |
285 | * @param string $rational | |
286 | * @return float | |
287 | */ | |
288 | private static function convertExifRational($rational) | |
289 | { | |
290 | $data = \explode('/', $rational); | |
291 | if (\count($data) == 1) { | |
292 | return \floatval($rational); | |
293 | } | |
294 | ||
295 | // prevent division by zero if 2nd value is invalid | |
296 | $data[1] = \floatval($data[1]); | |
297 | if (!$data[1]) { | |
298 | return 0.0; | |
299 | } | |
300 | ||
301 | return \floatval($data[0]) / $data[1]; | |
302 | } | |
dc70b4e5 | 303 | } |