Recently, I found that photos taken with a phone were rotated 90 degrees counterclockwise when drawing PDFs using the pdf-lib.js library. After investigation, it turns out that the image is recorded with a rotated data grid that records each pixel. We can see it correctly in most image viewers because there is an Orientation metadata information that these image viewers use to determine whether and how to rotate the image for display, but some programs do not do this.
In my case, the image’s Orientation
metadata value is 6, and it is displayed normally in a regular image viewer, but it is rotated 90 degrees counterclockwise when drawing PDF using the pdf-lib.js
JavaScript library.
Exif Orientation
is a metadata field used to indicate the orientation of an image. It was initially introduced into the Exif specification by the Japan Electronics and Information Technology Industries Association (JEIDA) in 1998. It is embedded in image formats such as JPEG, TIFF, RAW, etc., and is used to store information related to image shooting, such as camera model, shooting date and time, exposure time, etc. Among them, Exif Orientation is used to indicate the orientation of the image so that it can be displayed correctly in image browsers and editors.
Orientation has eight values, as shown in the figure below, representing eight directions.
Smartphones usually use built-in sensors such as gyroscopes and accelerometers to detect their orientation. These sensors can detect the direction of gravity, tilt angle, and direction of the device, and pass this data to the device’s operating system. Based on this data, the operating system can determine the device’s orientation and set the Exif Orientation tag of the image to the corresponding value when needed. For example, when you take a horizontal photo, the device detects that its orientation is horizontal and sets the Exif Orientation tag to 6. This way, when you view the photo in the gallery, the device will automatically rotate it to the correct orientation.
When taking photos, regardless of whether you hold the device horizontally or vertically, the image sensor always captures data according to a fixed orientation, and the final photo data is usually saved in a fixed orientation, rather than transforming and saving the data according to the Orientation value. This is to save computing resources and processing time.
Therefore, many devices use the Exif Orientation tag to mark the orientation information of the image, and the image itself is not rotated or transformed. This can avoid unnecessary processing and reduce storage costs. In addition, by saving the Exif Orientation tag, the original orientation information of the image can be preserved, which is very useful for subsequent processing and editing of the image. Therefore, using the Exif Orientation tag is a more common and efficient way to save image orientation information.
Knowing the problem, we need to extract the Orientation metadata information for adaptation.
Get EXIF Orientation Value
0x0112(274) is the tag for orientation
function getOrientation(buffer) {
return new Promise((resolve, reject) => {
const dataView = new DataView(buffer);
let offset = 0;
let marker = dataView.getUint16(offset, false);
if (marker === 0xffd8) {
offset += 2;
while (offset < dataView.byteLength) {
marker = dataView.getUint16(offset, false);
offset += 2;
if (marker === 0xffe1) {
offset += 2;
if (dataView.getUint32(offset, false) !== 0x45786966) { // Exif
reject(new Error('Invalid Exif header'));
return;
}
const littleEndian = dataView.getUint16(offset += 6, false) === 0x4949;
offset += dataView.getUint32(offset + 4, littleEndian);
const tags = dataView.getUint16(offset, littleEndian);
offset += 2;
for (let i = 0; i < tags; i++) {
if (dataView.getUint16(offset, littleEndian) === 0x0112) {
// 0x0112(274) is the tag for orientation
resolve(dataView.getUint16(offset + 8, littleEndian));
return;
}
offset += 12;
}
reject(new Error('Orientation tag not found'));
return;
} else if ((marker & 0xff00) !== 0xff00) {
break;
} else {
offset += dataView.getUint16(offset, false);
}
}
reject(new Error('Invalid JPEG format'));
return;
}
reject(new Error('Not a valid JPEG file'));
});
}