Author: Cheiron
background
When you take a photo from the Web, many camera programs will automatically rotate to adjust the display based on the direction in which you took the photo, but upload the photo in the original direction. This often results in the photos being upside-down on the web.
The solution is to actively rotate the photo by reading the Orientation field in the photo’s EXIF information. This article explains how to use javascript to read EXIF information in detail.
ArrayBuffer TypedArray and DataView
ArrayBuffer, TypedArray, and DataView together provide a convenient way for javascript to manipulate binary data.
An ArrayBuffer is a piece of memory, or a piece of content that represents binary data. It cannot be read or written directly, only TypedArray or DataView. An ArrayBuffer is a constructor that takes an integer as an argument to indicate how many bytes of memory are allocated. Const ab = new ArrayBuffer(32) allocates a contiguous memory area of 16 bytes. The default value for each byte is 0. Also, some javascript apis return an ArrayBuffer as a result, such as the FileReader API discussed in this article, whose readAsArrayBuffer method returns an ArrayBuffer object.
TypedArray is the generic name for a class of constructors, Uint8Array Int8Array, Uint8ClampedArray Int16Array There are nine Float64Array types. A typed array generated with these nine constructors behaves like an array. If both have the length attribute, you can access the element through [], or you can use most of the array methods.
For example, the ab object created above. Const i8View = new Int8Array(ab) creates a view of an 8-bit signed integer. Since ab has 32 bytes and int8 has one byte, each entry in i8View is equal to one byte of ab, so i8View. length = 32, and each entry is 0.
We can also create a view of 32-bit unsigned integers with const ui32View = new Uint32Array(ab). Because AB has 32 bytes and the uint32 has four bytes, each item of UI32View equals four bytes of AB. Therefore, UI32View. length = 8. So, everything in ui32view is still going to be 0.
As you can see, ab itself does not change during this process. The process of creating different views is to treat ab data as INT8, Uint32, or other data format.
The difference between a Typed array and an array is that all members of a Typed array are of the same type (that is, the meaning of ‘Typed’) and are completely continuous with no empty Spaces. If the array length is passed in to initialize, then all elements default to 0. TypedArray is just a view and does not store data itself; the data is stored in an ArrayBuffer. TypedArray is suitable for handling binary data of simple types, whereas DataView is required for complex types.
DataView can define a composite view. For example, Uint8Array defines the view, so elements are unsigned 8-bit integers, while DataView defines the view, the first byte can be Uint8, the second byte can be Int16, etc., and the byte order can be customized. Refer to MDN for details, and the following examples.
JEPG and EXIF formats
JPEG files are generally divided into two parts: markup and compressed data.
The token consists of two bytes, the first of which is the fixed value 0xFF, followed by the corresponding value of different meanings. For example, 0xFFD8 represents SOI (Start of Image), 0xFFD9 represents EOI, that is, End of Image. The EXIF information we care about relates to the tag in the 0xFFE0 0xFFEF range. These areas are called application tenure N(ApplicationN), where 0xFFE0 is App0. The EXIF we need is marked by App1, that is, the data between 0xFFE1 and the next 0xFFE1 to the next 0xFF tag.
The format of the EXIF
You can see that the last two bits next to the FFE1 identifier are the data size of APP1, and after the TIFF header is IFD0, which stands for Image File Directory. It contains image information data. The following table describes the data format of IFD.
The format of the IFD
The 2bytes of TTTT represent Tag, and the 2bytes of FFFF represent the data type. NNNNNNNN The 4bytes are the number of constituent elements. DDDDDDDD the 4bytes are the data itself or the offset of the data.
In this case, the Tag Number of image Orientation is 0x0112; The data type is unsigned short, the corresponding FFFF is 0x0003, and there is only one component, so NNNNNNNN is 00000001. DDDDDDDD is a bit of a problem, there are two cases. If the data type * number of constituent elements is less than 4bytes, DDDDDDDD is the value of the change label; otherwise, it is the offset of the data store address. One component of an Unsigned short is 2bytes, and there is only one, so 2bytes * 1 < 4bytes, so DDDDDDDD is the value of the tag for the Orientation tag. (Refer to 1 in The Reference Documentation for details)
The value and meaning of Orientation.
When the phone goes around, it gets 1, 6, 3, 8.
The image processing
Use the FileReader API to read images from the input tag into an ArrayBuffer
const reader = new FileReader()
reader.onload = async function () {
const buffer = reader.result
const orientation = getOrientation(buffer)
const image = await rotateImage(buffer, orientation)
}
reader.readAsArrayBuffer(file)
Copy the code
Now look at the implementation of the getOrientation function.
function getOrientation(buffer) {
// Create a DataView
const dv = new DataView(buffer)
// Set a position pointer
let idx = 0
// Set a default result
let value = 1
// Check whether it is a JPEG
if (buffer.length < 2|| dv.getUint16(idx) ! = =0xFFD8 {
return false
}
idx += 2
let maxBytes = dv.byteLength
// Walk through the contents of the file to find APP1, which is the identifier of EXIF
while (idx < maxBytes - 2) {
const uint16 = dv.getUint16(idx)
idx += 2
switch (uint16) {
case 0xFFE1:
// After finding EXIF, traverse the EXIF data to find the Orientation identifier
const exifLength = dv.getUint16(idx)
maxBytes = exifLength - 2
idx += 2
break
case 0x0112:
// After finding the Orientation identifier, read the DDDDDDDD section and set maxBytes to 0 to end the loop.
value = dv.getUint16(idx + 6.false)
maxBytes = 0
break}}return value
}
Copy the code
Let’s look at the implementation of rotateImage:
function rotateImage (buffer, orientation) {
// Use canvas to rotate
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
// Use the image object to draw the image onto the canvas
const image = new Image()
// Generate the base64 URL of the image based on the arrayBuffer
const url = arrayBufferToBase64Url(buffer)
return new Promise((resolve, reject) = > {
image.onload = function () {
const w = image.naturalWidth
const h = image.naturalHeight
switch (orientation) {
case 8:
canvas.width = h
canvas.height = w
ctx.translate(h / 2, w / 2)
ctx.rotate(270 * Math.PI / 180)
ctx.drawImage(image, -w / 2, -h / 2)
break
case 3:
canvas.width = w
canvas.height = h
ctx.translate(w / 2, h / 2)
ctx.rotate(180 * Math.PI / 180)
ctx.drawImage(image, -w / 2, -h / 2)
break
case 6:
canvas.width = h
canvas.height = w
ctx.translate(h / 2, w / 2)
ctx.rotate(90 * Math.PI / 180)
ctx.drawImage(image, -w / 2, -h / 2)
break
default:
canvas.width = w
canvas.height = h
ctx.drawImage(image, 0.0)
break
}
// Canvas can also be exported using other apis
const data = canvas.toDataURL('image/jpeg'.1)
resolve(data)
}
image.src = url
})
}
Copy the code
arrayBufferToBase64Url
function arrayBufferToBase64 (buffer) {
let binary = ' '
// TypedArray is used here
const bytes = new Uint8Array(buffer)
const len = bytes.byteLength
for (let i = 0; i < len; i++) {
The fromCharCode method creates a string from the specified sequence of Unicode values
binary += String.fromCharCode(bytes[ i ])
}
// Use the bTOA method to create base-64 encoded ASCII strings from String objects
return window.btoa(binary)
}
Copy the code
Reference:
- Description of Exif file format
- ArrayBuffer
The original link: tech.meicai.cn/detail/59, you can also search the small program “Mei CAI product technical team” on wechat, full of dry goods and updated every week, want to learn technology you do not miss oh.