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."
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 am trying to convert an image file captured from an input type=file element without success. Below is my javascript code
var img = document.getElementById('myimage');
var file = document.querySelector('input[type=file]').files[0];
var reader = new FileReader();
reader.addEventListener("load", function () {
var theBlob = reader.result;
theBlob.lastModifiedDate = new Date();
theBlob.name = file;
img.src = theBlob;
var newfile = new File([theBlob], "c:files/myfile.jpg");
}, false);
if (file) {
reader.readAsDataURL(file);
}
The image is displayed properly in the img element but the File command does not create the myfile.jpg image file. I am trying to capture the image and then resizing it using javascript before I upload it to the server. Anyone know why the image file is not being created? Better yet, anyone have code on how to resize an image file on the client and then upload it to a server?
This is how you can get a resized jpeg file from your "myimage" element using Canvas.
I commented every line of code so you could understand what I was doing.
// Set the Width and Height you want your resized image to be
var width = 1920;
var height = 1080;
var canvas = document.createElement('canvas'); // Dynamically Create a Canvas Element
canvas.width = width; // Set the width of the Canvas
canvas.height = height; // Set the height of the Canvas
var ctx = canvas.getContext("2d"); // Get the "context" of the canvas
var img = document.getElementById("myimage"); // The id of your image container
ctx.drawImage(img,0,0,width,height); // Draw your image to the canvas
var jpegFile = canvas.toDataURL("image/jpeg"); // This will save your image as a
//jpeg file in the base64 format.
The javascript variable "jpegFile" now contains your image encoded into a URL://Base64 format. Which looks something like this:
// data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ...9oADAMBAAIRAxEAPwD/AD/6AP/Z"
You can put that variable as an image source in HTML code and it will display your image in the browser, or you can upload the Base64 encoded string to the server.
Edit: How to convert the file to a blob (binary file) and upload it to the server
// This function is used to convert base64 encoding to mime type (blob)
function base64ToBlob(base64, mime)
{
mime = mime || '';
var sliceSize = 1024;
var byteChars = window.atob(base64);
var byteArrays = [];
for (var offset = 0, len = byteChars.length; offset < len; offset += sliceSize) {
var slice = byteChars.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return new Blob(byteArrays, {type: mime});
}
Now clean the base64 up and then pass it into the function above:
var jpegFile64 = jpegFile.replace(/^data:image\/(png|jpeg);base64,/, "");
var jpegBlob = base64ToBlob(jpegFile64, 'image/jpeg');
Now send the "jpegBlob" with ajax
var oReq = new XMLHttpRequest();
oReq.open("POST", url, true);
oReq.onload = function (oEvent) {
// Uploaded.
};
oReq.send(jpegBlob);
You can't manipulate hard drive directly from the browser. What you can do though is create a link that can be downloaded.
To do this change your var newfile = new File([theBlob], "c:files/myfile.jpg"); into:
const a = document.createElement('a');
a.setAttribute('download', "some image name");
a.setAttribute('href', theBlob);
a.click();
I'm getting an image file from an input element, which I then catch in a vanilla javascript snippet via e.target.files[0]. Next, I'm attempting to standardize the dimensions of this image (in case it exceeds the benchmarks) via passing e.target.files[0] to source_img in the function below:
function process_img(source_img,new_width, new_height, mime_type, quality){
var canvas = document.createElement('canvas');
canvas.width = new_width;
canvas.height = new_height;
var ctx = canvas.getContext("2d");
ctx.drawImage(source_img, 0, 0, new_width, new_height);
return dataURItoBlob(canvas.toDataURL(mime_type,quality/100),mime_type);
}
function dataURItoBlob(dataURI,mime_type) {
var byteString = atob(dataURI.split(',')[1]);
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); }
return new Blob([ab], { type: mime_type });
}
This is currently giving me the following error:
TypeError: Argument 1 of CanvasRenderingContext2D.drawImage could not
be converted to any of: HTMLImageElement, HTMLCanvasElement,
HTMLVideoElement, ImageBitmap.
My question is two-fold:
1) Why am I getting the error and what's the most efficient way to solve it?
2) What's the most efficient way to accomplish this image resizing/processing? E.g. ways to circumvent the base64 string (while ensuring cross-browser compatibility)? Essentially I want to know how the industry pros do it for scalable projects.
I'd prefer answers with illustrative examples (along with explanations).
Note: Professional server-side dev here who's a JS newbie. Let's stick to pure JS for the scope of this question. JQuery's on my radar, but I won't touch it till I learn JS fundamentals.
question 1:change your process_img function to this:
function process_img(source_img, new_width, new_height, mime_type, quality) {
var canvas = document.createElement('canvas');
canvas.width = new_width;
canvas.height = new_height;
var ctx = canvas.getContext("2d");
var reader = new FileReader();
var img = document.createElement('img');
reader.onload = function() {
img.src = this.result;
document.body.appendChild(img); //maybe
img.style.display = 'none'; //maybe
img.onload = function() {//better
ctx.drawImage(img, 0, 0, new_width, new_height);
return dataURItoBlob(canvas.toDataURL(mime_type, quality / 100), mime_type);
};
};
reader.readAsDataURL(source_img);
}
the principle is transform your imageFile to HTMLimgElement with src by FileReader;