You can do this in two ways:
- Load the image source using
XMLHttpRequest[]
orfetch[]
instead of an image element - Convert image element via a canvas element. This will recompress the image causing some quality loss. There is also the "risk" of color/gamma changes depending of the image contains ICC/gamma information and/or the browser support this information. Ie. the image won't be exact the same as the original - if you just want the original image to be represented as a blob, use method 1.
For method one and since you're already using promises, you can do:
function loadXHR[url] {
return new Promise[function[resolve, reject] {
try {
var xhr = new XMLHttpRequest[];
xhr.open["GET", url];
xhr.responseType = "blob";
xhr.onerror = function[] {reject["Network error."]};
xhr.onload = function[] {
if [xhr.status === 200] {resolve[xhr.response]}
else {reject["Loading error:" + xhr.statusText]}
};
xhr.send[];
}
catch[err] {reject[err.message]}
}];
}
Then get the image as Blob using it like this:
loadXHR["url-to-image"].then[function[blob] {
// here the image is a blob
}];
or use fetch[]
in browsers which support this:
fetch["url-to-image"]
.then[function[response] {
return response.blob[]
}]
.then[function[blob] {
// here the image is a blob
}];
The other method will require a canvas:
var img = new Image;
var c = document.createElement["canvas"];
var ctx = c.getContext["2d"];
img.onload = function[] {
c.width = this.naturalWidth; // update canvas size to match image
c.height = this.naturalHeight;
ctx.drawImage[this, 0, 0]; // draw in image
c.toBlob[function[blob] { // get content as JPEG blob
// here the image is a blob
}, "image/jpeg", 0.75];
};
img.crossOrigin = ""; // if from different origin
img.src = "url-to-image";
ArrayBuffer
and views are a part of ECMA standard, a part of JavaScript.
In the browser, there are additional higher-level objects, described in File API, in particular Blob
.
Blob
consists of an optional string type
[a MIME-type usually], plus blobParts
– a sequence of other Blob
objects, strings and BufferSource
.
The constructor syntax is:
new Blob[blobParts, options];
blobParts
is an array ofBlob
/BufferSource
/String
values.options
optional object:type
–Blob
type, usually MIME-type, e.g.image/png
,endings
– whether to transform end-of-line to make theBlob
correspond to current OS newlines [\r\n
or\n
]. By default"transparent"
[do nothing], but also can be"native"
[transform].
For example:
// create Blob from a string
let blob = new Blob[["…"], {type: 'text/html'}];
// please note: the first argument must be an array [...]
// create Blob from a typed array and strings
let hello = new Uint8Array[[72, 101, 108, 108, 111]]; // "Hello" in binary form
let blob = new Blob[[hello, ' ', 'world'], {type: 'text/plain'}];
We can extract Blob
slices with:
blob.slice[[byteStart], [byteEnd], [contentType]];
byteStart
– the starting byte, by default 0.byteEnd
– the last byte [exclusive, by default till the end].contentType
– thetype
of the new blob, by default the same as the source.
The arguments are similar to array.slice
, negative numbers are allowed too.
Blob
objects are immutable
We can’t change data directly in a Blob
, but we can slice parts of a Blob
, create new Blob
objects from them, mix them into a new Blob
and so on.
This behavior is similar to JavaScript strings: we can’t change a character in a string, but we can make a new corrected string.
Blob as URL
A Blob can be easily used as a URL for ,
The browser will decode the string and show the image:
To transform a Blob
into base64, we’ll use the built-in FileReader
object. It can read data from Blobs in multiple formats. In the next chapter we’ll cover it more in-depth.
Here’s the demo of downloading a blob, now via base-64:
let link = document.createElement['a'];
link.download = 'hello.txt';
let blob = new Blob[['Hello, world!'], {type: 'text/plain'}];
let reader = new FileReader[];
reader.readAsDataURL[blob]; // converts the blob to base64 and calls onload
reader.onload = function[] {
link.href = reader.result; // data url
link.click[];
};
Both ways of making a URL of a Blob
are usable. But usually URL.createObjectURL[blob]
is simpler and faster.
URL.createObjectURL[blob]
- We need to revoke them if care about memory.
- Direct access to blob, no “encoding/decoding”
Blob to data url
- No need to revoke anything.
- Performance and memory losses on big
Blob
objects for encoding.
Image to blob
We can create a Blob
of an image, an image part, or even make a page screenshot. That’s handy to upload it somewhere.
Image operations are done via element:
- Draw an image [or its part] on canvas using canvas.drawImage.
- Call canvas
method .toBlob[callback, format, quality] that creates a
Blob
and runscallback
with it when done.
In the example below, an image is just copied, but we could cut from it, or transform it on canvas prior to making a blob:
// take any image
let img = document.querySelector['img'];
// make of the same size
let canvas = document.createElement['canvas'];
canvas.width = img.clientWidth;
canvas.height = img.clientHeight;
let context = canvas.getContext['2d'];
// copy image to it [this method allows to cut image]
context.drawImage[img, 0, 0];
// we can context.rotate[], and do many other things on canvas
// toBlob is async operation, callback is called when done
canvas.toBlob[function[blob] {
// blob ready, download it
let link = document.createElement['a'];
link.download = 'example.png';
link.href = URL.createObjectURL[blob];
link.click[];
// delete the internal blob reference, to let the browser clear memory from it
URL.revokeObjectURL[link.href];
}, 'image/png'];
If we prefer async/await
instead of callbacks:
let blob = await new Promise[resolve => canvasElem.toBlob[resolve, 'image/png']];
For screenshotting a page, we can use a library such as //github.com/niklasvh/html2canvas. What it does is just walks the page and draws it on . Then we can get a
Blob
of it the same way as above.
From Blob to ArrayBuffer
The Blob
constructor
allows to create a blob from almost anything, including any BufferSource
.
But if we need to perform low-level processing, we can get the lowest-level ArrayBuffer
from blob.arrayBuffer[]
:
// get arrayBuffer from blob
const bufferPromise = await blob.arrayBuffer[];
// or
blob.arrayBuffer[].then[buffer => /* process the ArrayBuffer */];
From Blob to stream
When we read and write to a blob of more than 2 GB
, the use of arrayBuffer
becomes more memory intensive for us. At
this point, we can directly convert the blob to a stream.
A stream is a special object that allows to read from it [or write into it] portion by portion. It’s outside of our scope here, but here’s an example, and you can read more at //developer.mozilla.org/en-US/docs/Web/API/Streams_API. Streams are convenient for data that is suitable for processing piece-by-piece.
The Blob
interface’s stream[]
method returns a ReadableStream
which upon reading returns the data contained within the Blob
.
Then we can read from it, like this:
// get readableStream from blob
const readableStream = blob.stream[];
const stream = readableStream.getReader[];
while [true] {
// for each iteration: value is the next blob fragment
let { done, value } = await stream.read[];
if [done] {
// no more data in the stream
console.log['all blob processed.'];
break;
}
// do something with the data portion we've just read from the blob
console.log[value];
}
Summary
While ArrayBuffer
, Uint8Array
and other BufferSource
are “binary data”, a Blob represents “binary data with
type”.
That makes Blobs convenient for upload/download operations, that are so common in the browser.
Methods that perform web-requests, such as XMLHttpRequest, fetch and so on, can work with Blob
natively, as well as with other binary types.
We can easily convert between Blob
and low-level binary data types:
- We can make a
Blob
from a typed array usingnew Blob[...]
constructor. - We can get back
ArrayBuffer
from a Blob usingblob.arrayBuffer[]
, and then create a view over it for low-level binary processing.
Conversion streams are very useful when we need to handle large blob. You can easily create a ReadableStream
from a blob. The Blob
interface’s stream[]
method returns a ReadableStream
which upon reading returns the data contained within the blob.