I am storing base64 encoded images, and at the moment I can only create one code (i'm attempting to create two, but it appears the second is being overwritten). I don't get the over-arching concept of canvas drawing, so I believe that is the root of my issue when trying to solve this problem.
current behavior: It stores the same DataUrl in local storage twice. It does log the correct info. the favicon-green is getting stored, just not red
How do I encode multiple base64 images with canvas?
html:
<head>
...
<link id="favicon" rel="icon" src="/assets/favicon-red-16x16.ico.png">
</head>
<body>
...
<!-- hidden images to store -->
<img id="favicon-green" rel="icon" src="/assets/favicon-green-16x16.ico.png" width="16" height="16" />
<img id="favicon-red" rel="icon" src="/assets/favicon-red-16x16.ico.png" width="16" height="16" />
...
</body>
js:
// cache images
function storeImages() {
// my sorry attempt to create two canvas elements for two image encodings
var canvasGreen = document.createElement('canvas');
var canvasRed = document.createElement('canvas');
// painting both images
var ctxGreen = canvasGreen.getContext('2d');
var ctxRed = canvasRed.getContext('2d');
// getting both images from DOM
var favGreen = document.getElementById('favicon-green');
var favRed = document.getElementById('favicon-red');
// checking if images are already stored
var base64Green = localStorage.getItem('greenFavicon');
var base64Red = localStorage.getItem('redFavicon');
console.log('storing...')
if (base64Green == null && window.navigator.onLine || base64Red == null && window.navigator.onLine) {
ctxGreen.drawImage(favGreen, 0, 0);
ctxRed.drawImage(favRed, 0, 0);
// getting images (the DataUrl is currently the same for both)
base64Green = canvasGreen.toDataURL();
base64Red = canvasRed.toDataURL();
localStorage.setItem('greenFavicon', base64Green);
localStorage.setItem('redFavicon', base64Red);
console.log("are they equal : ", base64Green == base64Red); // returns true
}
}
storeImages();
I don't see anything particularly wrong with your code. If the code isn't a direct copy and paste, I would look through it with a fine-tooth come to make sure you don't switch any red and green around.
There shouldn't be any surprising mechanisms when it comes to converting canvases to data URLs.
Here is a quick example of two:
const a = document.createElement('canvas');
const b = document.createElement('canvas');
const aCtx = a.getContext('2d');
const bCtx = b.getContext('2d');
aCtx.fillStyle = '#000';
aCtx.fillRect(0, 0, a.width, a.height);
const aUrl = a.toDataURL();
const bUrl = b.toDataURL();
console.log(aUrl == bUrl, aUrl, bUrl);
console.log('First difference index:', Array.prototype.findIndex.call(aUrl, (aChar, index) => aChar !== bUrl[index]));
Notice that they are different. However, notice that they also start out very similar, and you have to go quite a ways over to start seeing differences (in my example, character 70). I would double-check that they are actually the same (by comparing them like I did). It could be it just looks the same.
Another thing you might do, which is more of a code style thing, but could also help with accidentally green and red mixups, is make a function to save just one, then call it twice.
const saveImage = (imageId, key) => {
if (localStorage.getItem(key)) {
return; // already saved
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const image = document.getElementById(imageId);
ctx.drawImage(image, 0, 0);
if (window.navigator.onLine) {
localStorage.setItem(key, canvas.toDataURL());
}
}
saveImage('favicon-green', 'greenFavicon');
saveImage('favicon-red', 'redFavicon');
Not only does that clean up your code and keep it DRY, but it also helps avoid accidental mix-ups between red and green in your function.
After some comments back and forth, I realized another possibility is you are trying to draw the images to the canvas before the images are loaded. This will cause it to draw blank images, but otherwise act like it is working fine.
You can quickly test this by console logging this:
console.log(image.width === 0);
after setting the image variable. If the value is true, then the image isn't loaded yet (before loading, images will have a width and height of 0). You need to make sure to wait until the image is loaded before trying to save it.
The best way to do this is with an addEventListener():
document.getElementById('favicon-green').addEventListener('load', () => {
saveImage('favicon-green', 'greenFavicon');
});
There is one more catch with this, in that if the image is somehow already loaded by the time that code runs, it'll never trigger. You need to look at the width of the image as well. Here is a function that does this all for you, and returns a Promise so you know it's done:
const saveImage = (imageId, key) => new Promise(resolve => {
if (localStorage.getItem(key)) {
return resolve(); // already saved
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const image = document.getElementById(imageId);
const onImageLoaded = () => {
ctx.drawImage(image, 0, 0);
if (window.navigator.onLine) {
localStorage.setItem(key, canvas.toDataURL());
}
resolve();
}
if (image.width > 0) {
onImageLoaded();
} else {
image.addEventListener('load', onImageLoaded);
}
});
saveImage('favicon-green', 'greenFavicon').then(() => console.log('saved'));
Related
My ultimate goal is that I'm trying to convert an animated SMIL SVG into an APNG file. I have found no easy way to do this, and so I'm doing something a roundabout: I've written a node.js + express.js app that hosts a simple backend to get svg images on my local filesystem, and I've written a vue.js app that will go and pull those images and render them on a google chrome browser. I then play the SVG and try to capture rendered "frames", and save those frames as static png files (about 30 static PNG files for each second of SVG animation). I then plan to take those static png files & convert them over to a single animated png / apng file using another program. The part that I'm stuck on: actually trying to capture a rasterized "frame" of the svg.
Here's a snippet of code from my vue.js app which requests an SVG file, and renders it to a div, and then it tries to call a function takeSnap().
const file = await RequestsService.getFile(i);
const div = document.getElementById("svgContainer");
div.innerHTML = file.svg;
const { width, height } = div.children[0].getBBox();
console.debug(`width: ${width}, height: ${height}`);
const svg = div.children[0];
await svg.pauseAnimations();
let time = 0.0;
const interval = 1.0 / numFrames; // interval in seconds.
let count = 0;
while (time < file.duration) {
console.log(`time=${time}`);
await svg.setCurrentTime(time);
await this.takeSnap(svg, width, height);
time += interval;
console.debug(`file: ${file.fileName}_${count}`);
}
await svg.setCurrentTime(file.duration);
await this.takeSnap(svg, width, height);
I haven't been able to make a proper implementation of takeSnap(). I know that there are a slew of tools such as Canvg or HTML2png that go and directly render a webpage from the DOM. I've tried many different libraries, but none of them seem to be able to correctly render the frame of the SVG that chrome is correctly rendering. I don't blame the libraries: going from animated SVG XML file to actually rasterized pixels is a very difficult problem I think. But Chrome can do it, and what I'm wondering is... can I capture the browser engine output of chrome somehow?
Is there a way that I can get the rasterized pixel data produced by the blink browser engine in chrome & then save that rasterized pixel data into a png file? I know that I'll lose the transparency data of the SVG, but that's okay, I'll work around that later.
OK, this got a bit complicated. The script can now take SMIL animations with both <animate> and <animateTransform>. Essentially I take a snap shot of the SVG using Window.getComputedStyle() (for <animate> elements) and the matrix value using SVGAnimatedString.animVal (for <animateTransform> elements). A copy of the SVG is turned into a data URL and inserted into a <canvas>. From here it is exported as a PNG image.
In this example I use a data URL in the fetch function, but this can be replaced by a URL. The script has been tested with the SVG that OP provided.
var svgcontainer, svg, canvas, ctx, output, interval;
var num = 101;
const nsResolver = prefix => {
var ns = {
'svg': 'http://www.w3.org/2000/svg',
'xlink': 'http://www.w3.org/1999/xlink'
};
return ns[prefix] || null;
};
const takeSnap = function() {
// get all animateTransform elements
let animateXPath = document.evaluate('//svg:*[svg:animateTransform]', svg, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
// store all animateTransform animVal.matrix in a dataset attribute
Object.keys([...Array(animateXPath.snapshotLength)]).forEach(i => {
let node = animateXPath.snapshotItem(i);
let mStr = [...node.transform.animVal].map(animVal => {
let m = animVal.matrix;
return `matrix(${m.a} ${m.b} ${m.c} ${m.d} ${m.e} ${m.f})`;
}).join(' ');
node.dataset.transform = mStr;
});
// get all animate elements
animateXPath = document.evaluate('//svg:animate', svg, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
// store all animate properties in a dataset attribute on the target for the animation
Object.keys([...Array(animateXPath.snapshotLength)]).forEach(i => {
let node = animateXPath.snapshotItem(i);
let propName = node.getAttribute('attributeName');
let target = node.targetElement;
let computedVal = getComputedStyle(target)[propName];
target.dataset[propName] = computedVal;
});
// create a copy of the SVG DOM
let parser = new DOMParser();
let svgcopy = parser.parseFromString(svg.outerHTML, "application/xml");
// find all elements with a dataset attribute
animateXPath = svgcopy.evaluate('//svg:*[#*[starts-with(name(), "data")]]', svgcopy, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
// copy the animated property to a style or attribute on the same element
Object.keys([...Array(animateXPath.snapshotLength)]).forEach(i => {
let node = animateXPath.snapshotItem(i);
// for each data-
for (key in node.dataset) {
if (key == 'transform') {
node.setAttribute(key, node.dataset[key]);
} else {
node.style[key] = node.dataset[key];
}
}
});
// find all animate and animateTransform elements from the copy document
animateXPath = svgcopy.evaluate('//svg:*[starts-with(name(), "animate")]', svgcopy, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
// remove all animate and animateTransform elements from the copy document
Object.keys([...Array(animateXPath.snapshotLength)]).forEach(i => {
let node = animateXPath.snapshotItem(i);
node.remove();
});
// create a File object
let file = new File([svgcopy.rootElement.outerHTML], 'svg.svg', {
type: "image/svg+xml"
});
// and a reader
let reader = new FileReader();
reader.addEventListener('load', e => {
/* create a new image assign the result of the filereader
to the image src */
let img = new Image();
// wait got load
img.addEventListener('load', e => {
// update canvas with new image
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(e.target, 0, 0);
// create PNG image based on canvas
let img = new Image();
img.src = canvas.toDataURL("image/png");
output.append(img);
//let a = document.createElement('A');
//a.textContent = `Image-${num}`;
//a.href = canvas.toDataURL("image/png");
//a.download = `Image-${num}`;
//num++;
//output.append(a);
});
img.src = e.target.result;
});
// read the file as a data URL
reader.readAsDataURL(file);
};
document.addEventListener('DOMContentLoaded', e => {
svgcontainer = document.getElementById('svgcontainer');
canvas = document.getElementById('canvas');
output = document.getElementById('output');
ctx = canvas.getContext('2d');
fetch('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMCAxMCI+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0ibmF2eSI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJmaWxsIiBkdXI9IjNzIiBrZXlUaW1lcz0iMDsuMTsuMjsxIiB2YWx1ZXM9Im5hdnk7bGlnaHRibHVlO2JsdWU7bmF2eSIvPgogICAgPGFuaW1hdGVUcmFuc2Zvcm0gYWRkaXRpdmU9InN1bSIgYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiBkdXI9IjNzIiBrZXlUaW1lcz0iMDsuNTsxIiB0eXBlPSJ0cmFuc2xhdGUiIHZhbHVlcz0iMCwwOzIsMjs0LDYiLz4KICAgIDxhbmltYXRlVHJhbnNmb3JtIGFkZGl0aXZlPSJzdW0iIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgZHVyPSIzcyIga2V5VGltZXM9IjA7LjU7MSIgdHlwZT0icm90YXRlIiB2YWx1ZXM9IjAsMiwyOzMwLDIsMjstMTAsMiwyOyIvPgogIDwvcmVjdD4KPC9zdmc+CgoK').then(res => res.text()).then(text => {
let parser = new DOMParser();
let svgdoc = parser.parseFromString(text, "application/xml");
canvas.width = svgdoc.rootElement.getAttribute('width');
canvas.height = svgdoc.rootElement.getAttribute('height');
svgcontainer.innerHTML = svgdoc.rootElement.outerHTML;
svg = svgcontainer.querySelector('svg');
// set interval
interval = setInterval(takeSnap, 50);
// get all
let animateXPath = document.evaluate('//svg:*[starts-with(name(), "animate")]', svg, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
let animationArr = Object.keys([...Array(animateXPath.snapshotLength)]).map(i => {
let node = animateXPath.snapshotItem(i);
return new Promise((resolve, reject) => {
node.addEventListener('endEvent', e => {
resolve();
});
});
});
Promise.all(animationArr).then(value => {
clearInterval(interval);
});
});
});
<div style="display:flex">
<div id="svgcontainer"></div>
<canvas id="canvas" width="200" height="200"></canvas>
</div>
<p>Exported PNGs:</p>
<div id="output"></div>
I'm working on an nft-art-generator app, the app generate multiple and differnte html canvas (as image).
Each canvas is a combination of 3 images, in other words, 3 images should be drawn in each canvas and after drawing on canvas i download it.
The Problem is that when i check the downloaded canvas, i get the same canvas. To be more precise I get the last generated canvas has been downloaded multiple time, it's like all canvas generated before are gone.
I think that happened because something in my code is running asynchronously.
Here is my React.js Code
const generateCollection = async()=>{
//In this exemple i want to generate 2 canvas only!!
for(var k = 1; k <= 2; k++){
drawLayer(data, k)
}
}
const drawLayer = (data, index)=>{
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d')
let sources = {};
//Get some random images url
const objURLs = getImagesURLs(data, index);
objURLs.forEach((url,i)=>{
const key = `image${i+1}`;
Object.assign(sources, {[key]: url})
})
loadImages(sources, function(images) {
objURLs.forEach((url,i)=>{
if (Boolean(url)) {
context.drawImage(images[`image${i+1}`], 0, 0, 230, 230)
}
})
});
setTimeout(()=> download(canvas.current, index), 1000);
}
const 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) {
console.log("!! ", images )
callback(images);
}
};
images[src].src = sources[src];
}
}
const download = (canvas, index)=>{
console.log("Download: ", canvas)
var url = canvas.toDataURL("image/png");
var link = document.createElement('a');
link.download = `img_${index}.png`;
link.href = url;
link.click();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<canvas
id="myCanvas"
ref={canvas}
width={230}
height={230}
/>
<Button onClick={()=>generateCollection()} ><Button>
Here is a live demo code based on
https://jsfiddle.net/vprdhomf/1/
I will appreciate any help, or suggestion!!
To lift from question comment:
Loading images is asynchronous, so callback is called asynchronously.
If all canvas combinations (of different sets of multiple images) have been completed in less than a second, the setTimeout call to download only sees the canvas in its final state. To resolve this you can either
Wait for each download to be initiated before creating the next download, or
Use multiple canvas elements, or perhaps
Store data URLs in an array for each canvas combination for downloading later.
If the images are loaded across domains, and the server doesn't allow cross domain requests, canvas.toDataURL throws a security exception. Fixing the fiddle using the URLs provided in it became impossible because of this.
I am using scalable SVG icons in my Chrome extension.
chrome.browserAction.setIcon({
tabId: tabId,
path: '../icons/' + icon + '/scalable.svg'
});
I want to switch icons based on some parameters, so I have visual feedback.
What I have realized is that when I am switching icons very quickly, Chrome is messing up and often I end up seeing the wrong icon. I added console.log prints to code to ensure I am switching icons properly and I see that my code has no errors.
It looks like Chrome executes such change of icon requests asynchronously and conversion of SVG to pixels takes sometimes longer than usual. That leads to an execution in the wrong order.
So for example if I switch icons from A to B, then to C; then to D, ... at the end I may see C, although the last request for change was to switch it to D.
Any ideas on how to fix this annoying problem?
Chain the calls to the API using Promise
If you call setIcon often, create a cache of imageData yourself and use it instead of path because the API re-reads the source icon each time and re-creates imageData.
Here's a generic example, not tested:
const queue = {};
const cache = {};
// auto-clean the queue so it doesn't grow infinitely
chrome.tabs.onRemoved.addListener(tabId => delete queue[tabId]);
async function setIcon(tabId, icon) {
const url = '../icons/' + icon + '/scalable.svg';
const imageData = await (cache[url] || (cache[url] = loadImageData(url)));
queue[tabId] = (queue[tabId] || Promise.resolve()).then(() =>
new Promise(resolve =>
chrome.browserAction.setIcon({tabId, imageData}, resolve)));
}
function loadImageData(url) {
return new Promise((resolve, reject) => {
const data = {};
const img = new Image();
img.src = url;
img.onload = () => {
for (const size of [16, 32]) {
const canvas = document.createElement('canvas');
document.documentElement.appendChild(canvas);
canvas.width = canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
data[size] = ctx.getImageData(0, 0, size, size);
canvas.remove();
}
resolve(data);
};
img.onerror = reject;
});
}
This question already has answers here:
Tainted Canvas, due to CORS and SVG?
(2 answers)
Closed 4 years ago.
I have a directive in angular JS that allows exporting SVGs to a PNG. This works fine in other browsers, but in IE I get a security error.
I have tried all sorts of things, but it just doesn't seem to work.
I read somewhere that if I base64 encode it, then that would work, but it doesn't.
The code I have for drawing the SVG on the canvas is this:
// Private function for drawing our images
var drawImage = function (canvas, ctx, svgContainer) {
// Defer our promise
var deferred = $q.defer();
// Remove hidden layers
removeHidden(angular.element(clone));
// Create our data
var clone = angular.element(svgContainer[0].cloneNode(true)),
child = clone.children()[0];
// Remove hidden layers
removeHidden(angular.element(clone));
var s = new XMLSerializer(),
t = s.serializeToString(child),
base64 = 'data:image/svg+xml;base64,' + window.btoa(t);
console.log(base64);
var img = new Image();
// When the image has loaded
img.onload = function () {
// Create our dimensions
var viewBox = child.getAttribute('viewBox').split(' ');
console.log(viewBox);
var dimensions = {
width: viewBox[2],
height: viewBox[3]
};
console.log(img.width);
// Get our location
getNextLocation(canvas.width, canvas.height, dimensions);
// Draw our image using the context
ctx.drawImage(img, 0, 0, dimensions.width / 2, dimensions.height, location.x, location.y, location.width, location.height);
// Resolve our promise
deferred.resolve(location);
};
// Set the URL of the image
img.src = base64;
// Return our promise
return deferred.promise;
};
Before this I was creating a blob but that also caused the security error.
My main bit of code, is this bit here:
// Public function to generate the image
self.generateImage = function (onSuccess) {
// Reset our location
location = null;
counter = 0;
// Get our SVG
var target = document.getElementById(self.options.targets.containerId),
svgContainers = angular.element(target.getElementsByClassName(self.options.targets.svgContainerClassName)),
itemCount = svgContainers.length;
// Get our context
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
// Set our canvas height and width
setCanvasDimensions(canvas, itemCount);
// Create our array of promises
var promises = [];
// Draw our header and footer
drawHeader(canvas, ctx);
//// For each container
//for (var i = 0; i < itemCount; i++) {
// // Get our elements
// var svgContainer = svgContainers[i];
// // Draw our image and push our promise to the array
// promises.push(draw(canvas, ctx, svgContainer));
//}
promises.push(draw(canvas, ctx, svgContainers[0]));
// Finally add our footer to our promises array
promises.push(drawFooter(canvas, ctx));
// When all promises have resolve
$q.all(promises).then(function () {
console.log('all promises completed');
// Get our URL as a base64 string
var dataURL = canvas.toDataURL("image/png");
console.log('we have the image');
// Create our model
var model = {
teamName: self.options.team.name,
sport: self.options.team.sport,
data: dataURL
};
// Create our preview
self.create(model).then(function (response) {
console.log('saved to the database');
// Invoke our success callback
onSuccess(response);
});
})
};
As you can see I loop through each svg container and draw the SVG for each one. I have commented that out in this bit of code and just draw the first image.
The console.log directive after canvas.toDateURL never get's invoked and I get a security error.
The SVG is inline. From what I have read the issue might be due to the xlns declaration, but removing it still gives me the issue:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 259.5 131" enable-background="new 0 0 259.5 131" xml:space="preserve">
Does anyone know how I can solve this issue?
You need to set
img.crossOrigin= 'anonymous';
before
img.src=...
However, to make this work, you will also need to have
access-control-allow-origin: *
set in the response header of your image.
to try the solution, you can use 'http://fabricjs.com/assets/printio.png'(which has the proper header set) for example
I'm using a good library on here to handle some large images coming in through the iphone camera in order to avoid the whole subsampling drama here.
My draw code:
function imageLoaded(img, frontCamera) {
element = document.getElementById("canvas1");
var mpImg= new MegaPixImage(img);
// read the width and height of the canvas- scaled down
width = element.width; //188 94x2
height = element.height; //125
//used for side by side comparison of images
w2 = width / 2;
// stamp the image on the left of the canvas
if (frontCamera) {
mpImg.render(element, {maxWidth:94, maxHeight:125});} else{
mpImg.render(element, {maxWidth:94, maxHeight:125});}
//at this point, i want to grab the imageData drawn to the canvas using
//MegaPixImage and continue to do some more image processing, which normally
//would happen by declaring ctx=element.getContext("2d");
//more stuff here
}
The image is drawing fine,...but I cannot seem to find a way of then doing image processing on that image subsequently. How would I get a new context after having drawn that image on the canvas?
Maybe I would either have to run further image processing from within that library so I have context access or strip the context drawing out of the library.
Thanks for the help!
I had a similar issue, and actually found a helpful function to detect subsampling and only use MegaPixImage when subsampling was found.
In my case, for local file reading (iPhone camera, in your case), I called a handleFileSelect function when a <input type="file"> value is changed (i.e. when a file is selected to populate this input). Inside this function, I called a general populateImage JS function that draws the image data to the canvas.
Here's the handleFileSelect function and input binding:
$("#my_file_input").bind('change', function (event) {
handleFileSelect(event);
});
function handleFileSelect(event) {
var reader,
tmp,
file = event.target.files[0];
try {
reader = new FileReader();
reader.onload = function (e) {
tmp = e.target.result.toString();
// In my case, some image data (from Androids, mostly) didn't contain necessary image data, so I added it in
if (tmp.search("image/jpeg") === -1 && tmp.search("data:base64") !== -1) {
tmp = tmp.replace("data:", "data:image/jpeg;");
}
populateImage(tmp);
};
reader.onerror = function (err) {
// Handle error as you need
};
reader.readAsDataURL(file);
} catch (error) {
// Handle error as you need
}
}
Then, my populateImage function (called in the reader.onload function above):
function populateImage(imageURL) {
var tmpImage = new Image();
$(tmpImage).load(function () {
var mpImg, mpImgData;
// If subsampling found, render using MegaPixImage fix, grab image data, and re-populate so we can use non-subsampled image.
// Note: imageCanvas is my canvas element.
if (detectSubsampling(this)) {
mpImg = new MegaPixImage(this);
mpImg.render(imageCanvas, {maxWidth: 94, maxHeight: 125});
mpImgData = imageCanvas.toDataURL("image/jpg");
populateImage(mpImgData);
return;
}
// Insert regular code to draw image to the canvas
// Note: ctx is my canvas element's context
ctx.drawImage(tmpImage, 0, 0, 94, 125); // Or whatever x/y/width/height values you need
});
$(tmpImage).error(function (event) {
// Handle error as you need
});
tmpImage.src = imageURL;
}
And last but not least, the detectSubsampling function. Note that this method was found from another source and isn't my own.
function detectSubsampling(img) {
var iw = img.naturalWidth,
ih = img.naturalHeight,
ssCanvas,
ssCTX;
if (iw * ih > 1024 * 1024) { // Subsampling may happen over megapixel image
ssCanvas = document.createElement('canvas');
ssCanvas.width = ssCanvas.height = 1;
ssCTX = ssCanvas.getContext('2d');
ssCTX.drawImage(img, -iw + 1, 0);
// Subsampled image becomes half smaller in rendering size.
// Check alpha channel value to confirm image is covering edge pixel or not.
// If alpha value is 0 image is not covering, hence subsampled.
return ssCTX.getImageData(0, 0, 1, 1).data[3] === 0;
}
return false;
}
This may be more than you bargained for, but like I said, I ran into a similar issue and this solution proved to work across all browsers/devices that were canvas supported.
Hope it helps!