There are several questions about uploading pictures in the question of thinking or not, all of which involve the concept of ArrayBuffer. I want to sort out the knowledge in this area and hope that more people can get something.
Ladies and gentlemen, let's start together.
1. How to upload files
Generally, FormData is used to create request data for upload in the front end. The example is as follows:
var formData = new FormData(); formData.append("username", "Groucho"); // HTML file type input, user selected formData.append("userfile", fileInputElement.files[0]); // JavaScript file like object var content = '<a id="a"><b id="b">hey!</b></a>'; // Body of new file var blob = new Blob([content], { type: "text/xml"}); formData.append("webmasterfile", blob); var request = new XMLHttpRequest(); request.open("POST", "http://foo.com/submitform.php"); request.send(formData);
The field type of a FormData object can be Blob, File, or string. If its field type is neither Blob nor File, it will be converted to string.
We use < input type = "input" / > to select the image, put the obtained file into FormData, and then submit it to the server.
If multiple files are uploaded, they are appended to the same field.
fileInputElement.files.forEach(file => { formData.append('userfile', file); })
The examples of file like and new Blob show that we can construct a new file to upload directly.
Scene 1: clip image upload
We can get the data url or canvas by tailoring the library.
Take crowperjs as an example, use getcropped canvas to get canvas, then use its own toBlob to get file data, and then upload it through FormData.
The core code of transformation can refer to the following:
canvas = cropper.getCroppedCanvas({ width: 160, height: 160, }); initialAvatarURL = avatar.src; avatar.src = canvas.toDataURL(); // Get blob data from canvs canvas.toBlob(function (blob) { var formData = new FormData(); formData.append('avatar', blob, 'avatar.jpg'); // Now you can make a request makeRequest(formData) })
Scenario 2: base64 image upload
After getting the base64 image, we can convert it to blob form through the following functions:
function btof(base64Data, fileName) { const dataArr = base64Data.split(","); const byteString = atob(dataArr[1]); const options = { type: "image/jpeg", endings: "native" }; const u8Arr = new Uint8Array(byteString.length); for (let i = 0; i < byteString.length; i++) { u8Arr[i] = byteString.charCodeAt(i); } return new File([u8Arr], fileName + ".jpg", options); }
So we get the file, and then we can upload it.
Scenario 3: URL image upload
If you want to upload directly with the image URL, we can do it in two parts:
- Get base64
- Then file
The key code is how to create a canvas from the URL. Here, by creating an Image object, after the Image is mounted, it is filled into the canvas.
var img = "https://Ss0.bdstatic.com/70cfuhsh q1ynxgkpowk1hf6hhy / it / u = 508387608248974022 & FM = 26 & GP = 0. JPG "; / / imgurl is your picture path var image = new Image(); image.src = img; image.setAttribute("crossOrigin", "Anonymous"); image.onload = function() { // Step 1: get a base64 image var base64 = getBase64Image(image); var formData = new FormData(); // Step 2: convert base64 to file var file = btof(base64, "test"); formData.append("imageName", file); }; function getBase64Image(img) { var canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, img.width, img.height); var ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase(); var dataURL = canvas.toDataURL("image/" + ext); return dataURL; }
Class = "code" data height = "355" data theme id = "0" data default tab = "JS, result" data user = "ineo6" data slug hash = "mwgpgqz" data preview = "true" style = "height: 355px; box sizing: border box; display: Flex; align items: Center; justify content: Center; border: 2px solid; margin: 1eem 0; padding: 1eem;" data pen-pen-data-pen-pen-content; 1eem; "1eem;" 1eem; "1eem;" 1eem; "data pen-pen-pen-pen-data-pen-pen-pen-1;" 1eem; "1eem;" 1eem; "1eem;" 1eem; "data-pen-pen-pen-data-pen-pen-pen-pen-data-pen-pen-pen-pen-data-pen-pen-pen-data-pen-pen-pen-qz" title = "URL image to Base64" >
<span>See the Pen
url image to base64 by neo (@ineo6)
on CodePen.</span>
</p>
<script async src="https://static.codepen.io/ass...;></script>
2. thinking
Although we have solved the scenario mentioned above, it contains these keywords, which makes people think:
- Blob
- File
- Uint8Array
- ArrayBuffer
- TypedArray
- Base64
- atob,btoa
These keywords all point to "file", "binary" and "encoding", which we usually don't pay much attention to.
Before using File and Blob, I had doubts.
What's the point of all this? Next let's take a look at the knowledge I've sorted out.
3. concept
3.1 Blob
Blob object represents an immutable, raw data class file object.
The File interface is also based on the Blob object, and is extended to support the File format of the user system.
3.1.1 create Blob object
To construct a blob from other non blob objects and data, use the Blob() constructor:
var debug = {hello: "world"}; var blob = new Blob([JSON.stringify(debug, null, 2)], {type : 'application/json'});
3.1.1 read Blob object
Use FileReader to read the contents of Blob objects.
var reader = new FileReader(); reader.addEventListener("loadend", function() { //reader.result is the content console.log(reader.result) }); reader.readAsArrayBuffer(blob);
3.1.1 Object URLs
Object URLs refer to addresses beginning with blob:, which can be used to display picture and text information.
This is similar to the display of base64 image, so we can also use it to preview the image.
The following code snippet is to turn the selected image into Object URLs.
function handleFiles(files) { if (!files.length) { fileList.innerHTML = "<p>No file!</p>"; } else { fileList.innerHTML = ""; var list = document.createElement("ul"); fileList.appendChild(list); for (var i = 0; i < files.length; i++) { var li = document.createElement("li"); list.appendChild(li); var img = document.createElement("img"); // Create object url from file img.src = window.URL.createObjectURL(files[i]); img.height = 60; img.onload = function() { // Remember to release the object url after loading window.URL.revokeObjectURL(this.src); } li.appendChild(img); var info = document.createElement("span"); info.innerHTML = files[i].name + ": " + files[i].size + " bytes"; li.appendChild(info); } } }
3.2 Typed Arrays - typed arrays
A typed array is an array like object that provides access to raw binary data. But typed array and normal array are not the same class. The call to Array.isArray() will return false.
Typed Arrays has two parts:
- Arraybuffer
- Views (TypedArray and DataView)
3.2.1 ArrayBuffer
The ArrayBuffer object is used to represent a generic, fixed length raw binary data buffer.
ArrayBuffer cannot be operated directly, but through TypedArray or DataView objects. They represent the data in the buffer in specific formats, and read and write the contents of the buffer through these formats.
ArrayBuffer is mainly used to access binary data efficiently and quickly, such as the data used by WebGL, Canvas 2D or Web Audio.
Let's take a look at TypedArray.
3.2.2 TypedArray
TypedArray can build views based on different data types on the ArrayBuffer object.
// Create an 8-byte ArrayBuffer const b = new ArrayBuffer(8); // Create an Int32 view pointing to b, starting at byte 0 and ending at the end of the buffer const v1 = new Int32Array(b); // Create a Uint8 view pointing to b, starting at byte 2 and ending at the end of the buffer const v2 = new Uint8Array(b, 2); // Create an Int16 view pointing to b, starting at byte 2, with a length of 2 const v3 = new Int16Array(b, 2, 2);
Int32array and uint8array refer to TypedArray. TypedArray objects describe an array like view of the underlying binary data cache.
It has many members:
Int8Array(); Uint8Array(); Uint8ClampedArray(); Int16Array(); Uint16Array(); Int32Array(); Uint32Array(); Float32Array(); Float64Array();
And a little Chestnut:
var buffer = new ArrayBuffer(2) var bytes = new Uint8Array(buffer) bytes[0] = 65 // ASCII for 'A' bytes[1] = 66 // ASCII for 'B' // View buffer content var blob = new Blob([buffer], {type: 'text/plain'}) var dataUri = window.URL.createObjectURL(blob) window.open(dataUri) // AB
Byte order
In the above example, we write "A" first, then "B". Of course, we can also write two bytes through Uint16Array.
var buffer = new ArrayBuffer(2) // Two byte buffer var word = new Uint16Array(buffer) // Access buffer with 16 bit integer // Add 'A' to high and 'B' to low var value = (65 << 8) + 66 word[0] = value var blob = new Blob([buffer], {type: 'text/plain'}) var dataUri = window.URL.createObjectURL(blob) window.open(dataUri) // BA
When you execute this code, why do you see "BA" instead of "AB"?
This is because of the existence of "byte order", namely, small byte order and large byte order.
For example, if a hexadecimal number 0x12345678 occupies four bytes, the most important byte determining its size is "12", and the least important is "78". Small endian byte order puts the least important byte first, and the storage order is 78563412; large endian byte order puts the most important byte first, and the storage order is 12345678.
Because browsers use small endian byte order, we see "BA". In order to solve the problem of byte order inconsistency, we can use DataView to set byte order.
TypedArray.prototype.buffer
The buffer property of the TypedArray instance, which returns the ArrayBuffer object corresponding to the whole memory area. The property is read-only.
const a = new Float32Array(64); const b = new Uint8Array(a.buffer);
The a view object and b view object of the above code correspond to the same ArrayBuffer object, that is, the same memory segment.
TypedArray.prototype.byteLength,TypedArray.prototype.byteOffset
The byteLength property returns the memory length occupied by the TypedArray array in bytes. The byteOffset property returns which byte of the underlying ArrayBuffer object the TypedArray array starts from. Both properties are read-only.
const b = new ArrayBuffer(8); const v1 = new Int32Array(b); const v2 = new Uint8Array(b, 2); const v3 = new Int16Array(b, 2, 2); v1.byteLength // 8 v2.byteLength // 6 v3.byteLength // 4 v1.byteOffset // 0 v2.byteOffset // 2 v3.byteOffset // 2
TypedArray.prototype.length
The length property indicates how many members the TypedArray array contains. Note the distinction between the length attribute, which is the member length and the byteLength attribute, which is the byte length.
const a = new Int16Array(8); a.length // 8 a.byteLength // 16
TypedArray.prototype.set()
The set method of TypedArray array is used to copy an array (ordinary array or TypedArray array), that is, copy one piece of content completely to another piece of memory.
const a = new Uint8Array(8); const b = new Uint8Array(8); b.set(a);
The set method can also accept a second parameter that indicates which member of the b object to copy the a object from.
TypedArray.prototype.subarray()
The subarray method is to create a new view for a part of the TypedArray array.
const a = new Uint16Array(8); const b = a.subarray(2,3); a.byteLength // 16 b.byteLength // 2
TypedArray.prototype.slice()
slice method of TypeArray instance, which can return a new TypedArray instance at the specified location.
let ui8 = Uint8Array.of(0, 1, 2); ui8.slice(-1) // Uint8Array [ 2 ]
TypedArray.of()
All constructors of TypedArray arrays have a static method of, which is used to convert parameters to a TypedArray instance.
Float32Array.of(0.151, -8, 3.7) // Float32Array [ 0.151, -8, 3.7 ]
The following three methods will generate the same TypedArray array.
// Method 1 let tarr = new Uint8Array([1,2,3]); // Method two let tarr = Uint8Array.of(1,2,3); // Method three let tarr = new Uint8Array(3); tarr[0] = 1; tarr[1] = 2; tarr[2] = 3;
TypedArray.from()
The static method from takes a traversable data structure (such as an array) as a parameter and returns an instance of TypedArray based on this structure.
Uint16Array.from([0, 1, 2]) // Uint16Array [ 0, 1, 2 ]
This method can also convert one instance of TypedArray to another.
const ui16 = Uint16Array.from(Uint8Array.of(0, 1, 2)); ui16 instanceof Uint16Array // true
The from method can also accept a function as the second parameter to traverse each element, similar to the map method.
Int8Array.of(127, 126, 125).map(x => 2 * x) // Int8Array [ -2, -4, -6 ] Int16Array.from(Int8Array.of(127, 126, 125), x => 2 * x) // Int16Array [ 254, 252, 250 ]
In the above example, the from method does not overflow, which indicates that the traversal is not aimed at the original 8-bit integer array. In other words, from will copy the TypedArray array specified by the first parameter to another section of memory, and then convert the result to the specified array format after processing.
Composite view
Because the view constructor can specify the starting position and length, different types of data can be stored in the same memory in turn, which is called "composite view".
const buffer = new ArrayBuffer(24); const idView = new Uint32Array(buffer, 0, 1); const usernameView = new Uint8Array(buffer, 4, 16); const amountDueView = new Float32Array(buffer, 20, 1);
The above code divides a 24 byte ArrayBuffer object into three parts:
- Byte 0 to byte 3: 1 32-bit unsigned integer
- Byte 4 to byte 19: 16 8-bit integers
- Byte 20 to byte 23: 1 32-bit floating-point number
3.2.3 DataView - View
If a piece of data contains multiple types, we can also use the DataView view view to operate.
The DataView view provides eight methods to write to memory.
dataview.setXXX(byteOffset, value [, littleEndian])
- byteOffset offset, in bytes
- Value set value
- littleEndian passed in false or undefined to use large byte order
setInt8: write an 8-bit integer of 1 byte. setUint8: write an 8-bit unsigned integer of 1 byte. setInt16: write a 2-byte 16 bit integer. setUint16: write a 2-byte 16 bit unsigned integer. setInt32: writes a 4-byte 32-bit integer. setUint32: writes a 4-byte, 32-bit, unsigned integer. setFloat32: writes a 4-byte 32-bit floating-point number. setFloat64: writes a 64 bit floating-point number of 8 bytes.
There are also 8 ways to read memory:
getInt8: reads 1 byte and returns an 8-bit integer. getUint8: reads 1 byte and returns an unsigned 8-bit integer. getInt16: reads 2 bytes and returns a 16 bit integer. getUint16: reads 2 bytes and returns an unsigned 16 bit integer. getInt32: reads 4 bytes and returns a 32-bit integer. getUint32: reads 4 bytes and returns an unsigned 32-bit integer. getFloat32: reads 4 bytes and returns a 32-bit floating-point number. getFloat64: reads 8 bytes and returns a 64 bit floating-point number.
Here is the header information of BMP file in the table:
Byte | describe |
---|---|
2 | "BM" mark |
4 | file size |
2 | Retain |
2 | Retain |
4 | Offset between file header and bitmap data |
We can use DataView to implement this simply:
var buffer = new ArrayBuffer(14) var view = new DataView(buffer) view.setUint8(0, 66) // Write 1 byte: 'B' view.setUint8(1, 67) // Write 1 byte: 'M' view.setUint32(2, 1234) // Write 4 bytes: 1234 view.setUint16(6, 0) // Write 2-byte reserved bit view.setUint16(8, 0) // Write 2-byte reserved bit view.setUint32(10, 0) // Write 4-byte offset
The corresponding structure should be as follows:
Byte | 0 | 1 | 2 | 3 | 4 | 5 | ... | Type | I8 | I8 | I32 | ... | Data | B | M |00000000|00000000|00000100|11010010| ... |
Back to the previous "BA" problem, we use DataView to re execute the following:
var buffer = new ArrayBuffer(2) var view = new DataView(buffer) var value = (65 << 8) + 66 view.setUint16(0, value) var blob = new Blob([buffer], {type: 'text/plain'}) var dataUri = window.URL.createObjectURL(blob) window.open(dataUri) // AB
We get the correct result "AB", which also shows that DataView uses large byte order by default.
Reference articles
- Blob - MDN
- ArrayBuffer - MDN
- Getting started with ECMAScript 6 - ArrayBuffer
- BMP file reading and writing notes
- TypedArray or DataView: Understanding byte order
- TypedArray or DataView: understanding byte order
This article was published in the author's blog: Image upload pose and Typed Arrays you don't know