Apply PSR-12 code style (#3886)
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / util / ExifUtil.class.php
CommitLineData
dc70b4e5 1<?php
a9229942 2
dc70b4e5
MS
3namespace 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
13final 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}