I just shot a ton of pictures on vacation and was amazed to see just how much extra data our mobile phones store in images.
This exif data in JPG and TIFF files can be a privacy issue, which I pointed out in my TEDx talk some time ago and even created a tool to remove the extra information in-browser without the need for any server.
However, it can also be a great thing to use in your products.
You can access EXIF information in many different ways.
What data can you get from an image?
Take the following photo:
If you use any of the solutions above, you can get the following information from the image. For example, in PHP you can do a:
$file = './Corfu-2024-126.jpg';
$exif = exif_read_data($file);
print_r($exif); |
$file = './Corfu-2024-126.jpg';
$exif = exif_read_data($file);
print_r($exif);
This results in:
Array
(
[FileName] => Corfu-2024-126.jpg
[FileDateTime] => 1730189825
[FileSize] => 3497266
[FileType] => 2
[MimeType] => image/jpeg
[SectionsFound] => ANY_TAG, IFD0, THUMBNAIL, EXIF, GPS, INTEROP
[COMPUTED] => Array
(
[html] => width="4032" height="2268"
[Height] => 2268
[Width] => 4032
[IsColor] => 1
[ByteOrderMotorola] => 0
[ApertureFNumber] => f/1.9
[FocusDistance] => 11.92m
[Thumbnail.FileType] => 2
[Thumbnail.MimeType] => image/jpeg
)
[Make] => Google
[Model] => Pixel 7 Pro
[Orientation] => 1
[XResolution] => 72/1
[YResolution] => 72/1
[ResolutionUnit] => 2
[Software] => HDR+ 1.0.641377693zd
[DateTime] => 2024:10:09 16:37:40
[YCbCrPositioning] => 1
[Exif_IFD_Pointer] => 222
[GPS_IFD_Pointer] => 960
[THUMBNAIL] => Array
(
[Compression] => 6
[Orientation] => 1
[XResolution] => 72/1
[YResolution] => 72/1
[ResolutionUnit] => 2
[JPEGInterchangeFormat] => 1303
[JPEGInterchangeFormatLength] => 24473
)
[ExposureTime] => 989/1000000
[FNumber] => 185/100
[ExposureProgram] => 2
[ISOSpeedRatings] => 45
[ExifVersion] => 0232
[DateTimeOriginal] => 2024:10:09 16:37:40
[DateTimeDigitized] => 2024:10:09 16:37:40
[UndefinedTag:0x9010] => +03:00
[UndefinedTag:0x9011] => +03:00
[UndefinedTag:0x9012] => +03:00
[ComponentsConfiguration] =>
[ShutterSpeedValue] => 998/100
[ApertureValue] => 178/100
[BrightnessValue] => 791/100
[ExposureBiasValue] => 0/6
[MaxApertureValue] => 178/100
[SubjectDistance] => 11921/1000
[MeteringMode] => 2
[Flash] => 16
[FocalLength] => 6810/1000
[SubSecTime] => 251
[SubSecTimeOriginal] => 251
[SubSecTimeDigitized] => 251
[FlashPixVersion] => 0100
[ColorSpace] => 1
[ExifImageWidth] => 4032
[ExifImageLength] => 2268
[InteroperabilityOffset] => 930
[SensingMethod] => 2
[SceneType] =>
[CustomRendered] => 1
[ExposureMode] => 0
[WhiteBalance] => 0
[DigitalZoomRatio] => 0/1
[FocalLengthIn35mmFilm] => 24
[SceneCaptureType] => 0
[Contrast] => 0
[Saturation] => 0
[Sharpness] => 0
[SubjectDistanceRange] => 3
[UndefinedTag:0xA433] => Google
[UndefinedTag:0xA434] => Pixel 7 Pro back camera 6.81mm f/1.85
[UndefinedTag:0xA460] => 3
[GPSVersion] =>
[GPSLatitudeRef] => N
[GPSLatitude] => Array
(
[0] => 39/1
[1] => 47/1
[2] => 4862/100
)
[GPSLongitudeRef] => E
[GPSLongitude] => Array
(
[0] => 19/1
[1] => 41/1
[2] => 5362/100
)
[GPSAltitudeRef] =>
[GPSAltitude] => 4200/100
[GPSTimeStamp] => Array
(
[0] => 13/1
[1] => 37/1
[2] => 38/1
)
[GPSImgDirectionRef] => M
[GPSImgDirection] => 76/1
[GPSDateStamp] => 2024:10:09
[InterOperabilityIndex] => R98
[InterOperabilityVersion] => 0100
) |
Array
(
[FileName] => Corfu-2024-126.jpg
[FileDateTime] => 1730189825
[FileSize] => 3497266
[FileType] => 2
[MimeType] => image/jpeg
[SectionsFound] => ANY_TAG, IFD0, THUMBNAIL, EXIF, GPS, INTEROP
[COMPUTED] => Array
(
[html] => width="4032" height="2268"
[Height] => 2268
[Width] => 4032
[IsColor] => 1
[ByteOrderMotorola] => 0
[ApertureFNumber] => f/1.9
[FocusDistance] => 11.92m
[Thumbnail.FileType] => 2
[Thumbnail.MimeType] => image/jpeg
)
[Make] => Google
[Model] => Pixel 7 Pro
[Orientation] => 1
[XResolution] => 72/1
[YResolution] => 72/1
[ResolutionUnit] => 2
[Software] => HDR+ 1.0.641377693zd
[DateTime] => 2024:10:09 16:37:40
[YCbCrPositioning] => 1
[Exif_IFD_Pointer] => 222
[GPS_IFD_Pointer] => 960
[THUMBNAIL] => Array
(
[Compression] => 6
[Orientation] => 1
[XResolution] => 72/1
[YResolution] => 72/1
[ResolutionUnit] => 2
[JPEGInterchangeFormat] => 1303
[JPEGInterchangeFormatLength] => 24473
)
[ExposureTime] => 989/1000000
[FNumber] => 185/100
[ExposureProgram] => 2
[ISOSpeedRatings] => 45
[ExifVersion] => 0232
[DateTimeOriginal] => 2024:10:09 16:37:40
[DateTimeDigitized] => 2024:10:09 16:37:40
[UndefinedTag:0x9010] => +03:00
[UndefinedTag:0x9011] => +03:00
[UndefinedTag:0x9012] => +03:00
[ComponentsConfiguration] =>
[ShutterSpeedValue] => 998/100
[ApertureValue] => 178/100
[BrightnessValue] => 791/100
[ExposureBiasValue] => 0/6
[MaxApertureValue] => 178/100
[SubjectDistance] => 11921/1000
[MeteringMode] => 2
[Flash] => 16
[FocalLength] => 6810/1000
[SubSecTime] => 251
[SubSecTimeOriginal] => 251
[SubSecTimeDigitized] => 251
[FlashPixVersion] => 0100
[ColorSpace] => 1
[ExifImageWidth] => 4032
[ExifImageLength] => 2268
[InteroperabilityOffset] => 930
[SensingMethod] => 2
[SceneType] =>
[CustomRendered] => 1
[ExposureMode] => 0
[WhiteBalance] => 0
[DigitalZoomRatio] => 0/1
[FocalLengthIn35mmFilm] => 24
[SceneCaptureType] => 0
[Contrast] => 0
[Saturation] => 0
[Sharpness] => 0
[SubjectDistanceRange] => 3
[UndefinedTag:0xA433] => Google
[UndefinedTag:0xA434] => Pixel 7 Pro back camera 6.81mm f/1.85
[UndefinedTag:0xA460] => 3
[GPSVersion] =>
[GPSLatitudeRef] => N
[GPSLatitude] => Array
(
[0] => 39/1
[1] => 47/1
[2] => 4862/100
)
[GPSLongitudeRef] => E
[GPSLongitude] => Array
(
[0] => 19/1
[1] => 41/1
[2] => 5362/100
)
[GPSAltitudeRef] =>
[GPSAltitude] => 4200/100
[GPSTimeStamp] => Array
(
[0] => 13/1
[1] => 37/1
[2] => 38/1
)
[GPSImgDirectionRef] => M
[GPSImgDirection] => 76/1
[GPSDateStamp] => 2024:10:09
[InterOperabilityIndex] => R98
[InterOperabilityVersion] => 0100
)
This is a ton of information to play with. The classic is using the GPS data to show images on a map . If you click on the different thumbnails, you can see the map moving to where the photo was taken.
To get this information, all you need to do is read the data and then convert it.
$exif = exif_read_data($file);
$lat = $exif['GPSLatitude'];
$lon = $exif['GPSLongitude'];
$latref = $exif['GPSLatitudeRef'];
$lonref = $exif['GPSLongitudeRef']; |
$exif = exif_read_data($file);
$lat = $exif['GPSLatitude'];
$lon = $exif['GPSLongitude'];
$latref = $exif['GPSLatitudeRef'];
$lonref = $exif['GPSLongitudeRef'];
One thing I had not done before though was reading the altitude information. This allows you, for example, to show the images on the height they were taken.
As the data is returned in a format like “4200/100” you need to do some parsing. This one creates an array of all images with the image name and its height in meters.
$max = 0;
$all = array();
foreach ($jpgs as $jpg) {
$gps = exif_read_data($jpg);
if (isset($gps['GPSAltitude'])) {
$parts = explode('/', $gps['GPSAltitude']);
$height = round($parts[0] / $parts[1]);
$jpgs[$jpg]["height"] = $height;
if ($height > $max) {
$max = $height;
}
}
array_push($all, array($jpg, $height));
} |
$max = 0;
$all = array();
foreach ($jpgs as $jpg) {
$gps = exif_read_data($jpg);
if (isset($gps['GPSAltitude'])) {
$parts = explode('/', $gps['GPSAltitude']);
$height = round($parts[0] / $parts[1]);
$jpgs[$jpg]["height"] = $height;
if ($height > $max) {
$max = $height;
}
}
array_push($all, array($jpg, $height));
}
Using the embedded thumbnail information
One interesting use case is to use the embedded thumbnail information to avoid having to create thumbnails. If you check the gallery or the height example and you keep developer tools open, you can see that whilst the images are all a few megabytes, the page only reads a few hundred kilobytes.
This is because we don’t load the images, or created thumbnails from them, but I use the exif thumbnail information in the JPG file instead:
[COMPUTED] => Array
(
[html] => width="4032" height="2268"
[Height] => 2268
[Width] => 4032
[IsColor] => 1
[ByteOrderMotorola] => 0
[ApertureFNumber] => f/1.9
[FocusDistance] => 11.92m
[Thumbnail.FileType] => 2
[Thumbnail.MimeType] => image/jpeg
)
…
[THUMBNAIL] => Array
(
[Compression] => 6
[Orientation] => 1
[XResolution] => 72/1
[YResolution] => 72/1
[ResolutionUnit] => 2
[JPEGInterchangeFormat] => 1303
[JPEGInterchangeFormatLength] => 24473
) |
[COMPUTED] => Array
(
[html] => width="4032" height="2268"
[Height] => 2268
[Width] => 4032
[IsColor] => 1
[ByteOrderMotorola] => 0
[ApertureFNumber] => f/1.9
[FocusDistance] => 11.92m
[Thumbnail.FileType] => 2
[Thumbnail.MimeType] => image/jpeg
)
…
[THUMBNAIL] => Array
(
[Compression] => 6
[Orientation] => 1
[XResolution] => 72/1
[YResolution] => 72/1
[ResolutionUnit] => 2
[JPEGInterchangeFormat] => 1303
[JPEGInterchangeFormatLength] => 24473
)
Originally, this thumbnail was meant to be displayed on screens built into digital cameras to have a faster preview. We can, however, also use this to preview the image without having to load it in full. In PHP this is the exif_thumbnail() method.
$image = exif_thumbnail('Corfu-2024-991.jpg', $width, $height, $type);
if ($image !== false) {
header('Content-type: ' .image_type_to_mime_type($type));
echo $image;
exit;
} else {
echo 'No thumbnail available';
} |
$image = exif_thumbnail('Corfu-2024-991.jpg', $width, $height, $type);
if ($image !== false) {
header('Content-type: ' .image_type_to_mime_type($type));
echo $image;
exit;
} else {
echo 'No thumbnail available';
}
This will not load the whole JPG file, but only as much as it needs to get the thumbnail. This is not news. Flickr, for example, used this in the early 2000s to show you a thumbnail of the image immediately when you dragged it into the browser and then started to upload the rest. However, I have not seen it used lately. Maybe because it is too easy to create images on the cloud.
Playing with Exif is fun. And why not – after all, our phones and cameras add this information anyway.