Canvas doesn't save as image properly, just blank transparent image - javascript

I'm looping through an array of image elements (from a local folder) and attaching them to a canvas and at the end I would like to save the canvas as an image but it is not being saved properly. The resulting image matches the dimensions of the canvas but it is just a blank transparent image.
If in test.js I don't save the canvas to an image and I instead use resolve(canvas) I can display it perfectly in index.html so I know that the canvas is being written properly.
There are also no error messages displayed in the Google Chrome console.
Test.js
class Test {
constructor() {}
createPoster(images) {
return new Promise((resolve, reject) => {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let locX = 0;
for (let i = 0, len = images.length; i < len; ++i) {
let image = images[i];
image.onload = () => {
ctx.drawImage(image, locX, 0);
locX += image.width;
};
}
let poster = new Image();
poster.crossOrigin = 'anonymous';
poster.onload = () => {
resolve(poster);
};
poster.src = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream');
});
}
}
Index.html
<script type="module">
const Test = require('./test.js');
let test = new Test();
async function main() {
// let images = Get array of image elements from another library.
let poster = await test.createPoster(images).catch((err) => console.log(err));
document.body.appendChild(poster); // Displays just a transparent image with the same dimensions as the canvas.
}
</script>

You need to check for all image loaded before you create your poster image.
createPoster(images) {
return new Promise((resolve, reject) => {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let locX = 0;
let imageLoaded = 0;
for (let i = 0, len = images.length; i < len; ++i) {
let image = images[i];
image.onload = () => {
ctx.drawImage(image, locX, 0);
locX += image.width;
imageLoaded++;
if (imageLoaded == len) {
let poster = new Image();
poster.crossOrigin = 'anonymous';
poster.onload = () => {
resolve(poster);
};
poster.src = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream');
}
};
// image.src = ..
}
});
}

Related

Async await now working in Nuxt component

I have two functions,
One function converts images from whatever format to canvas, then to WEBP image format.
The other function receives the image and console.log the image.
The problem is that when I log the result of the image in the saveToBackend function, I get a result of undefined, but when I console.log the converted image in the convertImage function, I get the image. Please how can i solve the issue?
Below are the functions in my NUXT app
convertImage(file) {
// convert image
let src = URL.createObjectURL(file)
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d')
let userImage = new Image();
userImage.src = src
userImage.onload = function() {
canvas.width = userImage.width;
canvas.height = userImage.height;
ctx.drawImage(userImage, 0, 0);
let webpImage = canvas.toDataURL("image/webp");
console.log(webpImage); // with this i get the image in the console
return webpImage
}
},
async saveToBackend(file, result) {
// convert images to webp
let webpFile = await this.convertImage(file)
console.log(webpFile); // with this i get undefined in the console
}
You can't return webpImage from inside the onload callback. It simply will not wait for onload to execute. You can instead create a Promise that resolves the value of webpImage when onload completes:
convertImage(file) {
return new Promise((resolve) => {
// convert image
let src = URL.createObjectURL(file);
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
let userImage = new Image();
userImage.src = src;
userImage.onload = function() {
canvas.width = userImage.width;
canvas.height = userImage.height;
ctx.drawImage(userImage, 0, 0);
let webpImage = canvas.toDataURL("image/webp");
console.log(webpImage); // with this i get the image in the console
// resolve promise so that saveToBackend can await it
return resolve(webpImage);
};
});
}
Hopefully that helps!

How to compress image size in JavaScript?

I am trying to compress image size using JavaScript. but it returns canvas error.
below is my code.
var reader = new FileReader();
reader.readAsDataURL(fileItem._file);
reader.onload = function (event) {
var base64 = event.target.result.substring(event.target.result.indexOf(',') + 1, event.target.result.length);
var cvs = document.createElement('canvas');
var source_img_obj = event.target.result;
cvs.width = source_img_obj.naturalWidth;
cvs.height = source_img_obj.naturalHeight;
var ctx = cvs.getContext("2d").drawImage(source_img_obj, 0, 0);
var newImageData = cvs.toDataURL(type, 70 / 100);
var result_image_obj = new Image();
result_image_obj.src = newImageData;
console.log(result_image_obj);
};
Error:
I think you are looking something like this~
This implementation has two methods to reduce the size of the image. One method resize the image while the other compress it maintaining the same size but it reduces the quality.
This implementation is based on FileReader() to read the contents of files (or raw data buffers for Blob objects) stored on the user's computer.
// Console logging (html)
if (!window.console) console = {}
const consoleOut = document.getElementById('console-out')
console.log = message => {
consoleOut.innerHTML += message + '<br />'
consoleOut.scrollTop = consoleOut.scrollHeight
}
const outputFormat = 'jpg'
const encodeButton = document.getElementById('jpeg-encode-button')
const encodeQuality = document.getElementById('jpeg-encode-quality')
function previewFile() {
const preview = document.getElementById('source-image')
const previewResize = document.getElementById('result-resize-image')
const previewCompress = document.getElementById('result-compress-image')
const file = document.querySelector('input[type=file]').files[0]
const reader = new FileReader()
reader.onload = e => {
preview.onload = () => {
resizeFile(preview, previewResize)
compressFile(preview, previewCompress)
}
preview.src = e.target.result
// preview.src = reader.result
}
if (file) {
reader.readAsDataURL(file)
}
}
function resizeFile(loadedData, preview) {
console.log('Image resizing:')
console.log(`Resolution: ${loadedData.width}x${loadedData.height}`)
const canvas = document.createElement('canvas')
canvas.width = Math.round(loadedData.width / 2)
canvas.height = Math.round(loadedData.height / 2)
preview.appendChild(canvas)
// document.body.appendChild(canvas)
const ctx = canvas.getContext('2d')
ctx.drawImage(loadedData, 0, 0, canvas.width, canvas.height)
console.log(`New resolution: ${canvas.width}x${canvas.height}`)
console.log('---')
}
function compressFile(loadedData, preview) {
console.log('Image compressing:')
console.log(`width: ${loadedData.width} | height: ${loadedData.height}`)
const quality = parseInt(encodeQuality.value)
console.log(`Quality >> ${quality}`)
const timeStart = performance.now()
console.log('process started...')
const mimeType = typeof outputFormat !== 'undefined' && outputFormat === 'png' ? 'image/png' : 'image/jpeg'
const canvas = document.createElement('canvas')
canvas.width = loadedData.width;
canvas.height = loadedData.height;
const ctx = canvas.getContext('2d').drawImage(loadedData, 0, 0)
const newImageData = canvas.toDataURL(mimeType, quality / 100)
const img = new Image()
img.src = newImageData
preview.src = img.src
preview.onload = () => {}
const duration = performance.now() - timeStart;
console.log('process finished...')
console.log(`Processed in: ${duration}ms`)
console.log('---')
}
<input type="file" onchange="previewFile()"><br>
<div>
<h3>Original Image</h3>
<img id="source-image" />
</div>
<div>
<h3>Resized Image</h3>
<div id="result-resize-image">
</div>
</div>
<div>
<h3>Compressed Image</h3>
<img id="result-compress-image" class='img-container' />
</div>
<br><br>
<div>
<fieldset>
<legend>Compressor settings</legend>
<div id='controls-wrapper'>
Compression ratio : <input id="jpeg-encode-quality" size='3' readonly='true' type="text" value="30" /> %
</div>
</fieldset>
</div>
<div>
<fieldset>
<legend>Console output</legend>
<div id='console-out'></div>
</fieldset>
</div>
You might have to resize the canvas size
refer the following example
here
function resizeImg(base, width, height) {
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
var context = canvas.getContext("2d");
var deferred = $.Deferred();
$("<img/>").attr("src", "data:image/gif;base," + base).load(function() {
context.scale(width/this.width, height/this.height);
context.drawImage(this, 0, 0);
deferred.resolve($("<img/>").attr("src", canvas.toDataURL()));
});
return deferred.promise();
}
To compress a File (got from input type file) you can use this function. Just call it with the file, and it will return the same file but compressed:
const compressImage = (imageFile, quality) => {
return new Promise((resolve, reject) => {
const $canvas = document.createElement("canvas");
const image = new Image();
image.onload = () => {
$canvas.width = image.width;
$canvas.height = image.height;
$canvas.getContext("2d").drawImage(image, 0, 0);
$canvas.toBlob(
(blob) => {
if (blob === null) {
return reject(blob);
} else {
resolve(blob);
}
},
"image/jpeg",
quality / 100
);
};
image.src = URL.createObjectURL(imageFile);
});
};
Usage is:
<input type="file" id="my_input">
And JS:
const $inputFile = document.querySelector("#my_input");
$inputFile.addEventListener("change", async () => {
const file = $inputFile.files[0];
const blob = await compressImage(file, 50);
// Upload the blob with FormData or something similar
console.log({ blob });
});

Enable CORS on multiple images at once

I know we can use this code to enable CORS on a single image
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var img = new Image();
img.crossOrigin = '';
img.src = 'http://crossdomain.com/image.jpg';
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, img.width, img.height);
Is there any way to do it for multiple image URLs at once?
URL array
To load several images enabling CORS request, you can use an array which is practical for this purpose.
One thing to be aware of is that requesting CORS can be denied by server. The browser may fail loading the image in those cases so you will need to know in advance if CORS need to be requested or not.
Example loader
var urls = [url1, url2, url3, ...]; // etc. replace with actual URLs
var images = []; // store the loaded images
var i = 0, len = urls.length;
var count = len; // for load and error handlers
for(; i < len; i++) {
var img = new Image();
img.onload = loadHandler;
img.onerror = img.onabort = errorHandler;
img.crossOrigin = ""; // enable CORS request
img.src = urls[i]; // set src last
images.push(img); // store in array
}
function loadHandler() {
if (!--count) callback(); // loading done
}
function errorHandler() {
// handle errors here
loadHandler(); // make sure to update counter/callback
}
function callback() {
// ... ready, continue from here
}
Demo
var urls = [
"http://i.imgur.com/0LINzxs.jpg", // random urls from imgur..
"http://i.imgur.com/6ksiMgS.jpg",
"http://i.imgur.com/aGQSLi9.jpg"
];
var images = []; // store the loaded images
var i = 0, len = urls.length;
var count = len; // for load and error handlers
for(; i < len; i++) {
var img = new Image();
img.onload = loadHandler;
img.onerror = img.onabort = errorHandler;
img.crossOrigin = ""; // enable CORS request
img.src = urls[i]; // set src last
images.push(img); // store in array
}
function loadHandler() {
if (!--count) callback(); // loading done
}
function errorHandler() {
// handle errors here
loadHandler(); // make sure to update
}
function callback() {
// ... ready, continue from here
console.log(images);
var ctx = document.querySelector("canvas").getContext("2d");
ctx.drawImage(images[0], 0, 0);
ctx.drawImage(images[1], 0, 0);
ctx.drawImage(images[2], 0, 0);
console.log(ctx.canvas.toDataURL()); // OK if CORS is OK!
}
<canvas></canvas>
Put the srcs in an array and iterate over them
working fiddle
https://jsfiddle.net/ps50po4z/
This allows you to use multiple images from different sources and display them.
use this function as a template to iterate through all the cors src images
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="578" height="200"></canvas>
<script>
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var sources = {
darthVader: 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg',
yoda: 'http://www.html5canvastutorials.com/demos/assets/yoda.jpg'
};
loadImages(sources, function(images) {
context.drawImage(images.darthVader, 100, 30, 200, 137);
context.drawImage(images.yoda, 350, 55, 93, 104);
});
</script>
</body>
</html>

WinJS Low Memory issue with ZXing

I am creating a Barcode scanner module for Windows 8 Metro App.
I some how success with my logic but suddenly I saw my application crash due to low memory issue.
<script>
var canvas = null;
var ctx = null;
var livePreview = null;
var count = 0,rescount=0;
function takepicture() {
var Capture = Windows.Media.Capture;
livePreview = document.getElementById("live-preview");
var mediaCapture = new Capture.MediaCapture();
canvas = document.getElementById("Vcanvas");
ctx=canvas.getContext('2d');
livePreview.addEventListener('play', function () { var i = window.setInterval(function () { ctx.drawImage(livePreview, 0, 0, canvas.width, canvas.height); scanCanvasEasy(); }, 20); }, false);
livePreview.addEventListener('pause', function () { window.clearInterval(i); }, false);
livePreview.addEventListener('ended', function () { clearInterval(i); }, false);
/*
var openPicker = new Windows.Storage.Pickers.FileOpenPicker();
openPicker.viewMode = Windows.Storage.Pickers.PickerViewMode.thumbnail;
openPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.videosLibrary;
openPicker.fileTypeFilter.replaceAll([".mp4", ".avi", ".ogg"]);
openPicker.pickSingleFileAsync()
.then(function (file) {
if (file) {
// draw the image
var img = new Image;
//img.onload = function () {
// canvas.width = img.width;
// canvas.height = img.height;
// ctx.drawImage(img, 0, 0, img.width, img.height);
// scanCanvasEasy();
//}
//img.src = URL.createObjectURL(file);
// open a stream from the image
livePreview.src = URL.createObjectURL(file);
livePreview.play();
}
})*/
mediaCapture.initializeAsync().then(function () {
livePreview.src = URL.createObjectURL(mediaCapture);
livePreview.play();
});
}
function scanCanvasEasy() {
var imgd = ctx.getImageData(0, 0,canvas.width,canvas.height);
var pix = imgd.data;
var reader = new ZXing.BarcodeReader();
reader.onresultpointfound = function (resultPoint) {
// do something with the resultpoint location
console.log(resultPoint.toString());
}
// try to decode the raw pixel data
var result = reader.decode(pix, canvas.width, canvas.height, ZXing.BitmapFormat.rgba32);
/*
The above line cause that memory issue, without that line there is no change in memory level.
*/
// show the result
if (result) {
document.getElementById("result").innerText ="Result(+"+rescount++ +")==>"+ result.text;
}
else {
document.getElementById("error").innerText = "no barcode found" + count++;
}
}
</script>
I posted the whole code i used here I Just called the takepicture() method from button click event.
var result = reader.decode(pix, canvas.width, canvas.height, ZXing.BitmapFormat.rgba32);
This line cause memory issue.
Thanks in advance.
var reader = new ZXing.BarcodeReader();
Multiple instance of reader cause this issue. Just created one instance of reader and use it for all subsequent scan will fixed that issue.

canvas and img working in chrome but not firefox

I have searched a few results regarding this issue but none of them works for me, so I am posting here to get some helps. Basically my issue is when I clicked on the generate button, I want the image from canvas to be displayed in a img element. However, the image will show up in chrome but not firefox! Below is my coding...
<body onload="onLoad();">
<canvas id="aimage">Your browser does not support the canvas tag.</canvas>
<input type="button" value="Generate" onclick="genImage();" />
<img id="cImg" name="cImg" src="${param.src}" />
...
</body>
And the javascript...
var tcanvas;
var scanvas;
var tcontext;
var imageURL;
function onLoad() {
tcanvas = document.getElementById("aimage");
tcontext = tcanvas.getContext("2d");
scanvas = document.getElementById("cImg");
imageURL = "${param.src}";
update();
}
function update() {
var image = new Image();
image.onload = function () {
if (image.width != tcanvas.width)
tcanvas.width = image.width;
if (image.height != tcanvas.height)
tcanvas.height = image.height;
tcontext.clearRect(0, 0, tcanvas.width, tcanvas.height);
tcontext.drawImage(image, 0, 0, tcanvas.width, tcanvas.height);
}
image.crossOrigin = 'anon';
image.src = imageURL;
}
function genImage() {
var url = tcanvas.toDataURL("image/jpeg");
scanvas.crossOrigin = 'anon';
scanvas.src = url;
if(scanvas.width > 1000){
scanvas.width = 1000;
}
if(scanvas.height > 1000){
scanvas.height = 1000;
}
}
try this:
var scanvas, tcontext, tcanvas;
function genImage() {
var url = tcanvas.toDataURL("image/jpeg");
scanvas.src = url;
if (scanvas.width > 1000) {
scanvas.width = 1000;
}
if (scanvas.height > 1000) {
scanvas.height = 1000;
}
}
window.onload = function () {
tcanvas = document.getElementById("aimage");
tcontext = tcanvas.getContext("2d");
scanvas = document.getElementById('cImg');
var img = new Image();
img.onload = function () {
tcontext.drawImage(img, 0, 0, img.width, img.height)
};
img.src = "yourImage.jpg";
}

Categories

Resources