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
Related
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 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 am trying to save image in encoded64 and than get the value of image from local storage .But I am getting null value why ?
here is my fiddle:
http://jsfiddle.net/sAH8w/7/
function getBase64Image(img) {
// Create an empty canvas element
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
// Copy the image contents to the canvas
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// Get the data-URL formatted image
// Firefox supports PNG and JPEG. You could check img.src to
// guess the original format, but be aware the using "image/jpg"
// will re-encode the image.
var dataURL = canvas.toDataURL("image/png");
try {
localStorage.setItem("elephant", dataURL);
}
catch (e) {
alert('error')
console.log("Storage failed: " + e);
}
//return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
}
This minor change fix the issue:
$('#save').click(function(){
var image = new Image();
image.src = "https://dl.dropboxusercontent.com/s/t2ywui846zp58ye/plus_minus_icons.png?m=";
getBase64Image(image);
})
Working fiddle: http://jsfiddle.net/robertrozas/sAH8w/15/
Update(get button): http://jsfiddle.net/robertrozas/sAH8w/17/
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."