I'm using createImageBitmap() which creates an ImageBitmap file.
How do I convert this file to a blob or ideally PNG so I can upload?
The only way currently is to draw it on a canvas.
For more efficiency, you can try to use an ImageBitmapRenderingContext, which will not copy the pixels buffer again.
(async () => {
const resp = await fetch('https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png');
// yes.. from a Blob to a Blob...
const blob1 = await resp.blob();
const bmp = await createImageBitmap(blob1);
console.log(bmp); // ImageBitmap
// create a canvas
const canvas = document.createElement('canvas');
// resize it to the size of our ImageBitmap
canvas.width = bmp.width;
canvas.height = bmp.height;
// get a bitmaprenderer context
const ctx = canvas.getContext('bitmaprenderer');
ctx.transferFromImageBitmap(bmp);
// get it back as a Blob
const blob2 = await new Promise((res) => canvas.toBlob(res));
console.log(blob2); // Blob
const img = document.body.appendChild(new Image());
img.src = URL.createObjectURL(blob2);
})().catch(console.error);
Check HTMLCanvasElement#toBlob() for the options you can pass in (file format and quality when applicable).
Related
I'm attempting to put a watermark image on an input image before saving it to a database. I'm using a canvas to do this then convert the canvas back to a blob but when I reference the watermark image in the projects local file directory to draw onto the canvas, I get a "Tainted canvases may not be exported." error in the console.
// Read input file
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
// Create canvas & calculate Stamp size and position
const canvas = document.createElement("canvas");
canvas.width = canvasSize.width;
canvas.height = canvasSize.height;
var calcW = canvasSize.width/2;
var calcH = calcW/2.16;
var calcX = (canvasSize.width - calcW)/2;
var calcY = (canvasSize.height - calcH)/2;
// Load input image into canvas
const img = document.createElement("img");
img.src = reader.result;
img.onload = () => {
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
//Load & draw the Stamp image
const mark = new Image();
mark.src = 'assets/img/compliant.png'; // <<TAINTED ASSET?
mark.onload = () => {
ctx.drawImage(mark, calcX,calcY,calcW,calcH);
// Convert to blob and get URL
canvas.toBlob((canvasBlob) => {
const newImg = document.createElement("img"),
url = URL.createObjectURL(canvasBlob);
newImg.onload = function () {
//Destroy objecturl after image loading
URL.revokeObjectURL(url);
};
// Show stamped image
newImg.src = url;
window.open(url);
});
};
};
};
});
You have to use
img.crossOrigin='anonymous';
On every image you want to manipulate (draw in a canvas, transform to an array buffer… this is to prevent you from manipulating potentially private user data)
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin
I used Ngx-Webcam for capture image from camera. I want to get both high quality and low quality image from camera
This library return to me Base64 image. It have an option to reduce size imageQuality but I can't use because I need high both quality image and low quality image
let data = webcamImage.imageAsBase64;
const raw = window.atob(data);
const rawLength = raw.length;
const unit8array = new Uint8Array(new ArrayBuffer(rawLength));
for (let i = 0; i < rawLength; i++) {
unit8array[i] = raw.charCodeAt(i);
}
I try to apply https://www.npmjs.com/package/image-conversion for our problem,
let data = webcamImage.imageAsBase64;
const raw = window.atob(data);
let contentType = raw.split(';')[0];
const rawLength = raw.length;
const unit8array = new Uint8Array(new ArrayBuffer(rawLength));
for (let i = 0; i < rawLength; i++) {
unit8array[i] = raw.charCodeAt(i);
}
let blob = new Blob([unit8array], {type: contentType});
imageProcess.compress(blob, 0.4);
But it was not work. I want to find another solution for compress image
I found how to compress image with canvas
compress(webcamImage: WebcamImage, quality: number): Observable<string> {
let _canvas = this.canvas;
let width = webcamImage.width;
let height = webcamImage.height;
_canvas.width = width;
_canvas.height = height;
// paint snapshot image to canvas
const img = new Image();
img.src = webcamImage.imageAsDataUrl;
return Observable.create(observe => {
(img.onload = () => {
const context2d = _canvas.getContext('2d');
context2d.drawImage(img, 0, 0, width, height);
// read canvas content as image
const mimeType: string = webcamImage.mineType;
const dataUrl: string = _canvas.toDataURL(mimeType, quality);
observe.next(dataUrl);
});
});
}
The base64 image was most likely already compressed (jpeg) before it was encoded in base64. You can't further compress such data.
If you need both high and low quality of the image, you should request a high quality (preferrably raw pixels) image from the webcam and transform it into jpeg images with different compression parameters.
If the camera only returns jpeg data, you need to decompress before recompressing with different parameters, which is possible but will consume more time and result in worse quality.
Ngx-webcam is probably a little limiting for this requirement, it might be necessary to look at its code and extend it somewhat to return captured images at different quality levels. There's a captureImageData flag that can be used to retrieve raw image data, but the documentation is a bit thin on how to work with that.
I am developing angular application and trying to decode image with QR code on client side only and facing with next errors.
I have next flow:
User uploads image.
I decode qr code from image.
<input type="file" name="file" id="file" accept="image/*"(change)="qrCodeUploaded($event.target.files)"/>
I have tried next libraries:
https://github.com/zxing-js/library
qrCodeUploaded(files: FileList): void {
const fileReader = new FileReader();
const codeReader = new BrowserQRCodeReader();
fileReader.readAsDataURL(files[0]);
fileReader.onload = (e: any) => {
var image = document.createElement("img");
image.src = e.target.result;
setTimeout(() => codeReader.decodeFromImage(image, e.target.result).then(res => console.log(res)), 100);
};
}
Works for some of qr codes, but issues on mobile. If you will take a photo of QR code with your phone, it will be not decoded. So for mobile you will need to implement video.
https://github.com/cozmo/jsQR
qrCodeUploaded(files: FileList): void {
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(files[0]);
fileReader.onload = (e: any) => {
console.log(new Uint8ClampedArray(e.target.result));
console.log(jsQR(new Uint8ClampedArray(e.target.result), 256, 256));
};
}
I get next error for any QR images I upload:
core.js:15714 ERROR Error: Malformed data passed to binarizer.
at Object.binarize (jsQR.js:408)
at jsQR (jsQR.js:368)
gist link:
https://gist.github.com/er-ant/b5c75c822eb085e70035cf333bb0fb55
Please, tell me what I am doing wrong and propose some solution for QR codes decoding. Open for any thoughts :)
For second library I missed that it expects ImageData and I pass Binary Data.
Thus, we have 3 solutions how to convert Binary Data to ImageData:
Use createImageBitmap Kaiido solution with some updates, as proposed in comments doesn't work.
qrCodeUploadedHandler(files: FileList): void {
const file: File = files[0];
createImageBitmap(files[0])
.then(bmp => {
const canvas = document.createElement('canvas');
const width: number = bmp.width;
const height: number = bmp.height;
canvas.width = bmp.width;
canvas.height = bmp.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(bmp, 0, 0);
const qrCodeImageFormat = ctx.getImageData(0,0,bmp.width,bmp.height);
const qrDecoded = jsQR(qrCodeImageFormat.data, qrCodeImageFormat.width, qrCodeImageFormat.height);
});
}
Get ImageData from canvas.
qrCodeUploadedHandler(files: FileList): void {
const file: File = files[0];
const fileReader: FileReader = new FileReader();
fileReader.readAsDataURL(files[0]);
fileReader.onload = (event: ProgressEvent) => {
const img: HTMLImageElement = new Image();
img.onload = () => {
const canvas: HTMLCanvasElement = document.createElement('canvas');
const width: number = img.width;
const height: number = img.height;
canvas.width = width;
canvas.height = height;
const canvasRenderingContext: CanvasRenderingContext2D = canvas.getContext('2d');
console.log(canvasRenderingContext);
canvasRenderingContext.drawImage(img, 0, 0);
const qrCodeImageFormat: ImageData = canvasRenderingContext.getImageData(0, 0, width, height);
const qrDecoded = jsQR(qrCodeImageFormat.data, qrCodeImageFormat.width, qrCodeImageFormat.height);
canvas.remove();
};
img.onerror = () => console.error('Upload file of image format please.');
img.src = (<any>event.target).result;
}
You can parse images with png.js and jpeg-js libraries for ImageData.
step:
Create a new image object from image file
Create a canvas
Draw the image to the canvas
Get ImageData through the context of canvas
Scan QR code with jsQR.
install:
npm install jsqr --save
code:
// utils/qrcode.js
import jsQR from "jsqr"
export const scanQrCode = (file, callback) => {
const image = new Image()
image.src = file.content
image.addEventListener("load", (e) => {
console.log(
"image on load, image.naturalWidth, image.naturalHeight",
image.naturalWidth,
image.naturalHeight
)
const canvas = document.createElement("canvas") // Creates a canvas object
canvas.width = image.naturalWidth // Assigns image's width to canvas
canvas.height = image.naturalHeight // Assigns image's height to canvas
const context = canvas.getContext("2d") // Creates a contect object
context.imageSmoothingEnabled = false
context.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight) // Draws the image on canvas
const imageData = context.getImageData(0, 0, image.naturalWidth, image.naturalHeight) // Assigns image base64 string in jpeg format to a variable
const code = jsQR(imageData.data, image.naturalWidth, image.naturalHeight)
if (code) {
console.log("Found QR code", code)
callback(code)
}
})
}
use:
scanQrCode(file, (code) => {
console.log(code.data);
});
I'm trying to get a base64 encoded png value from a canvas that was created by converting a pdf with the PDF.JS library by Mozilla.
So I had a base64 encoded PDF that I converted into a HTML canvas. I now need to convert that HTML canvas into a base64 encoded png value that I can use with a HTML img tag that displays properly.
I tried the HTMLCanvasElement.toDataURL(), however it doesn't display anything. I tried using the same method with some green boxes drawn in canvas and it worked fine, meaning that the base64 encoded pdf converted to canvas just doesn't want to work with that method.
Any other solutions or a workaround for the solution I tried?
**I need to do this inside my JS code with my HTML.
function convertDataURIToBinary() {
var raw = window.atob(BASE64 OF PDF);
var rawLength = raw.length;
var array = new Uint8Array(new ArrayBuffer(rawLength));
for(var i = 0; i < rawLength; i++) {
array[i] = raw.charCodeAt(i);
}
return array;
}
var pdfAsArray = convertDataURIToBinary();
PDFJS.workerSrc = "http://mozilla.github.io/pdf.js/build/pdf.worker.js";
var canvas = _el.querySelector(".insertHere");
PDFJS.getDocument(pdfAsArray).then(function getPdfHelloWorld(pdf) {
pdf.getPage(1).then(function getPageHelloWorld(page) {
var scale = 1.5;
var viewport = page.getViewport(scale);
var context = canvas.getContext("2d");
canvas.height = viewport.height;
canvas.width = viewport.width;
var renderContext = {
canvasContext: context,
viewport: viewport
};
page.render(renderContext);
canvas = _el.querySelector(".insertHere");
var imgSrc = canvas.toDataURL("image/png");
var img = new Image();
img.src = imgSrc;
_el.querySelector(".insertImageLabel").appendChild(img);
});
});
You are not waiting on the promise returned by render() to finish async operation. See https://github.com/mozilla/pdf.js/blob/master/examples/learning/prevnext.html#L76 how to wait on completion:
...
var renderTask = page.render(renderContext);
// Wait for rendering to finish
renderTask.promise.then(function () {
// Page rendered, now take snapshot.
});
...
I have two canvases, and I want to pass the content of canvas1, serialize it to an ArrayBuffer, and then load it in canvas2. In the future I will send the canvas1 content to the server, process it, and return it to canvas2, but right now I just want to serialize and deserialize it.
I found this way of getting the canvas info in bytes:
var img1 = context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img1.data.length);
for (var i = 0; i < img1.data.length; i++) {
binary[i] = img1.data[i];
}
And also found this way of set the information to a Image object:
var blob = new Blob( [binary], { type: "image/png" } );
var urlCreator = window.URL || window.webkitURL;
var imageUrl = urlCreator.createObjectURL( blob );
var img = new Image();
img.src = imageUrl;
But unfortunately it doesn't seem to work.
Which would be the right way of doing this?
The ImageData you get from getImageData() is already using an ArrayBuffer (used by the Uint8ClampedArray view). Just grab it and send it:
var imageData = context.getImageData(x, y, w, h);
var buffer = imageData.data.buffer; // ArrayBuffer
To set it again:
var imageData = context.createImageData(w, h);
imageData.data.set(incomingBuffer);
You probably want to consider some form of byte encoding though (such as f.ex base-64) as any byte value above 127 (ASCII) is subject to character encoding used on a system. Or make sure all steps on the trip uses the same (f.ex. UTF8).
Create an ArrayBuffer and send it into to the Uint8Array constructor, then send the buffer using websockets:
var img1 = context.getImageData(0, 0, 400, 320);
var data=img1.data;
var buffer = new ArrayBuffer(data.length);
var binary = new Uint8Array(buffer);
for (var i=0; i<binary.length; i++) {
binary[i] = data[i];
}
websocket.send(buffer);
[ Previous answer using canvas.toDataURL removed ]
Consider using canvas.toBlob() instead of context.getImageData() if you want compact data rather than a raw ImageData object.
Example:
const imageIn = document.querySelector('#image-in');
const imageOut = document.querySelector('#image-out');
const canvas = document.querySelector('#canvas');
const imageDataByteLen = document.querySelector('#imagedata-byte-length');
const bufferByteLen = document.querySelector('#arraybuffer-byte-length');
const mimeType = 'image/png';
imageIn.addEventListener('load', () => {
// Draw image to canvas.
canvas.width = imageIn.width;
canvas.height = imageIn.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(imageIn, 0, 0);
// Convert canvas to ImageData.
const imageData = ctx.getImageData(0, 0, imageIn.width, imageIn.height);
imageDataByteLen.textContent = imageData.data.byteLength + ' bytes.';
// Convert canvas to Blob, then Blob to ArrayBuffer.
canvas.toBlob((blob) => {
const reader = new FileReader();
reader.addEventListener('loadend', () => {
const arrayBuffer = reader.result;
bufferByteLen.textContent = arrayBuffer.byteLength + ' bytes.';
// Dispay Blob content in an Image.
const blob = new Blob([arrayBuffer], {type: mimeType});
imageOut.src = URL.createObjectURL(blob);
});
reader.readAsArrayBuffer(blob);
}, mimeType);
});
<h1>Canvas ↔ ArrayBuffer</h1>
<h2>1. Source <code><img></code></h2>
<img id="image-in" src="https://ucarecdn.com/a0338bfa-9f88-4ce7-b53f-e6b61000df89/" crossorigin="">
<h2>2. Canvas</h2>
<canvas id="canvas"></canvas>
<h2>3. ImageData</h2>
<p id="imagedata-byte-length"></p>
<h2>4. ArrayBuffer</h2>
<p id="arraybuffer-byte-length"></p>
<h2>5. Final <code><img></code></h2>
<img id="image-out">
JSFiddle: https://jsfiddle.net/donmccurdy/jugzk15b/
Also note that images must be hosted on a service that provides CORS headers, or you'll see errors like "The canvas has been tainted by cross-origin data."