exif
Read an image's EXIF orientation and strip its EXIF metadata without re-encoding the pixels.
import "std:exif"Photos carry EXIF metadata: where they were taken, with what camera, and which way up the sensor was held. std:exif reads the orientation and removes the metadata, working on the bytes of the file so the picture itself is never re-encoded.
Use it when you accept uploads: fix the orientation so portraits aren't sideways, and strip the metadata before you publish a photo, since it can hold a location.
A phone photo already carries EXIF, but the examples need one that does so predictably. withOrientation in the photo.osl tab of each example adds an orientation tag to a JPEG that img encoded.
Reading what a photo says#
exif.has(data) says whether a JPEG, PNG, WebP or TIFF carries EXIF. It never fails: bad data gives false. exif.orientation(data) returns a result with a number from 1 to 8. 1 means upright, and 6 means "rotate 90 degrees clockwise to display". An image with no EXIF is 1, as is any format that cannot carry it, such as GIF or BMP. Data that is not an image is a ParseError:
import "std:exif"
import "std:img"
import "./photo.osl"
byte[] plain = img.new(4, 2, {fill: "red"}).encode("jpeg").unwrap()
byte[] tagged = withOrientation(plain, 6)
log exif.has(plain) exif.has(tagged)
log exif.orientation(plain).unwrap() exif.orientation(tagged).unwrap()
log exif.orientation([1, 2, 3]).unwrapErr().kindTurning the picture upright#
An image with orientation 6 is stored sideways. img.decode turns it for you with autoOrient, or you can read the number and call orient. img.info(data) reports the same number in its orientation field:
import "std:exif"
import "std:img"
import "./photo.osl"
byte[] tagged = withOrientation(img.new(4, 2, {fill: "red"}).encode("jpeg").unwrap(), 6)
*img.Image raw = img.decode(tagged).unwrap()
log raw.width() raw.height()
*img.Image upright = img.decode(tagged, {autoOrient: true}).unwrap()
log upright.width() upright.height()
*img.Image same = raw.orient(exif.orientation(tagged).unwrap())
log same.width() same.height()Stripping metadata#
exif.strip(data) returns a copy of a JPEG, PNG or WebP with the EXIF block removed. Every other byte is unchanged, so there is no quality loss. An image without EXIF comes back identical. TIFF, HEIF, AVIF and JPEG XL are an UnsupportedError:
import "std:exif"
import "std:img"
import "./photo.osl"
byte[] tagged = withOrientation(img.new(4, 2, {fill: "red"}).encode("jpeg").unwrap(), 6)
byte[] clean = exif.strip(tagged).unwrap()
log exif.has(tagged) exif.has(clean)
log clean.len < tagged.len
log exif.orientation(clean).unwrap()Files#
exif.orientationFile(path) and exif.stripFile(path) do the same from a file. stripFile returns the cleaned bytes and leaves the file as it is, so write the result where you want it with fs.writeFileBytes.
A photo has orientation 6. What does that mean?
Is it upright?
Finish isUpright so it returns true only for an image whose orientation is 1. Data that can't be read is not upright.
true false
API reference#
5 members, generated from the std:exif source of OSL 9.3.0.
Functions
exif.has(byte[] data) booleanhas reports whether data carries EXIF metadata: a JPEG, PNG or WebP image with an EXIF block, a TIFF image, or a raw EXIF block. It never fails; corrupt or unrecognised data gives false.
exif.orientation(byte[] data) result<int, error>orientation reads the EXIF orientation of an image: 1 to 8, where 1 is upright and 6 means "rotate 90 degrees clockwise to display" (see pic.orient). It reads JPEG, PNG, WebP and TIFF images and raw EXIF blocks, and is 1 when the image has no EXIF or no orientation tag, including for GIF, BMP, ICO and QOI images, which can't carry EXIF.
Fails with ParseError for corrupt image or EXIF data or data that isn't an image, and UnsupportedError for HEIF, AVIF and JPEG XL images.
exif.orientationFile(string path) result<int, error>orientationFile reads the EXIF orientation of the image file at path, as exif.orientation does. Fails with NotFoundError, PermissionError or IOError when the file can't be read, and otherwise as exif.orientation does.
exif.strip(byte[] data) result<byte[], error>strip returns a copy of a JPEG, PNG or WebP image with its EXIF metadata removed and every other byte unchanged; the pixels aren't re-encoded. An image without EXIF comes back as an identical copy, as do GIF, BMP, ICO and QOI images, which can't carry EXIF.
Fails with ParseError for corrupt image data or data that isn't an image, and UnsupportedError for TIFF, HEIF, AVIF and JPEG XL images and raw EXIF blocks.
exif.stripFile(string path) result<byte[], error>stripFile reads the image file at path and returns it with its EXIF metadata removed, as exif.strip does; the file itself isn't changed. Fails with NotFoundError, PermissionError or IOError when the file can't be read, and otherwise as exif.strip does.