HTML canvas image is fading out after multiple iterations/reusing - html

I have a portal which is used for collecting orders from users in hand written format.
In my portal, I am using HTML canvas for getting inputs from user.
Once the user write order and submits it, I will read the drawings from the canvas and saves it into my DB.
HTML
<canvas height="750" width="768" id="userNotes"></canvas>
Javascript
var canvas = document.getElementById('userNotes');
var notesDataURL = canvas.toDataURL();
saveImageDataToDataBase (notesDataURL);
Next time when the user comes for a new order, I will draw this image back into the canvas, so that he can make modifications on the same and submit it as fresh order.
Javascript
var canvas = document.getElementById('userNotes');
var context = canvas.getContext('2d');
var img = new Image();
img.src = imageData;
context.drawImage(img, 0, 0);
Problem that I am facing is that after multiple iterations, the image starts fading out.
One observation is that fading is more at the bottom part of the image and less on the top side.
Consider the below sample images,
After 10 iterations image became like this,
Below is a JS FIddle created using sample code, in this after about 25 iterations fading will be visible(issue is visible only in tablet mentioned below).
https://jsfiddle.net/hz8r993v/
Observation:
An observation which I made is the issue is happening only in a specific tablet model, Samsung SM-P550, which is unfortunately the one my application is build for.
I am not able to reproduce this issue while using this application in my laptop, PC or another sm-p650 tablet.
Currently Only happening in all tablets of model SM-P550. Even I am confused with this observation.
I also tried disabling ImageSmoothingEnabled properties, but not helping.
Any leads/clues are appreciated.

JPEG compression quirk & rounding.
Looking at the image you provided suggests to me that you are incorrectly offsetting the image each time you render it. As you have not provided enough code for anyone to make an assessment as to why this is happening leaves us only to guess.
Lossless JPEG!
My first instinct is that you have offset the drawing or scaled it. I create the example below and offset a jpeg url of the rendered text by 0.2 pixels.
The result no blur Not a real surprise Jpegs are designed so that they can be copied. The artifacts introduced by the compression actually remove the blur introduced by the offset
Draw jpeg image 0.2 pixels down save and repeat.
var ctx = canvas.getContext("2d");
ctx.font = "64px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("testing",canvas.width / 2,canvas.height / 2);
ctx.font = "18px Arial";
var count = 0;
var imgURL;
function copy(){
imgURL = canvas.toDataURL();
}
function paste(x,y){
var img = new Image;
img.src = imgURL;
img.onload = function(){
ctx.fillStyle = "white";
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.drawImage(img,x,y);
ctx.fillStyle = "red";
ctx.fillText("Copy : " + count,canvas.width / 2,count * 20);
}
}
function iterate(){
count += 1;
copy();
paste(0,0.2);
if(count < 100){
setTimeout(iterate,50);
}
}
iterate();
<canvas id="canvas" width = 300 height = 150></canvas>
Your image clearly shows a vertical blurring so I set about finding when I can blur the image so that it over comes the Jpeg compression. Offsetting by 0.5 or more does not blur the image just scrolls pixel perfect 100 pixels (note I copy past 100 times move image 0.5 pixels down yet resulting image has moved 100 pixels. I have marked row 100)
Jpeg turns 0.5 pixel steps into 1 pixel steps
var ctx = canvas.getContext("2d");
ctx.font = "64px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("testing",canvas.width / 2,canvas.height / 2);
ctx.font = "18px Arial";
var count = 0;
var imgURL;
function copy(){
imgURL = canvas.toDataURL();
}
function paste(x,y){
var img = new Image;
img.src = imgURL;
img.onload = function(){
ctx.fillStyle = "white";
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.drawImage(img,x,y);
ctx.fillStyle = "red";
ctx.fillText("Copy : " + count,canvas.width / 2,count * 20);
if(count === 100){
ctx.fillRect(0,100,canvas.width,1);
}
}
}
function iterate(){
count += 1;
copy();
paste(0,0.5);
if(count < 100){
setTimeout(iterate,50);
}
}
iterate();
<canvas id="canvas" width = 300 height = 150></canvas>
At end redline is pixel row 100.
Seams that Jpeg compression is much better at preserving the original src image pixels than I suspected. But that does not help solve the problem.
Device specific
So is it a quirk of the device. I look up the specs and nothing stands out. I begin to suspect that you may have a canvas display scaling issues (the canvas resolution not matching the display size)
I start to set up the snippet to use differing resolutions and by shear chance I run the code at the same resolution as the samsung device mentioned in the question.
Blurring yay... but i did not change the canvas pixel scaling, all I did was change the canvas resolution.
Example of blurring.
Note that offset is 0.01 pixels (100th of a pixel)
var ctx = canvas.getContext("2d");
ctx.font = "64px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("testing",canvas.width / 2,canvas.height / 2);
ctx.font = "18px Arial";
var count = 0;
var imgURL;
function copy(){
imgURL = canvas.toDataURL();
}
function paste(x,y){
var img = new Image;
img.src = imgURL;
img.onload = function(){
ctx.fillStyle = "white";
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.drawImage(img,x,y);
ctx.fillStyle = "red";
ctx.fillText("Copy : " + count,canvas.width / 2,count * 20);
}
}
function iterate(){
count += 1;
copy();
paste(0,0.01);
if(count < 100){
setTimeout(iterate,50);
}
}
iterate();
<canvas id="canvas" width = 300 height = 1023></canvas>
Possible fix.
As there is not enough code to give a complete answer the above experiments have given me enough information to make an educated guess.
The problem is specific to the resolution 1024 height. (I have tried a few other (very limited) resolutions and could not get blurring)
The blurring does not occur at that resolution if the image is rendered at the pixel boundaries.
The possible fix.
When you render the image convert the render coordinates to integers using Math.floor(coordinate) this will ensure there is no fractional offset, even very tiny offsets that should not affect the image can be amplified by the jpeg compression when the resolution is at 1024 (and maybe some other resolutions as well).
var oldImage = new Image;
oldImage.src = "old image url";
oldImage.onload = function(){
ctx.drawImage(oldImage,Math.floor(x),Math.floor(y));
}
Hope this helps.

I am probably not experienced enough to tell you what about that tablet version, or what in your HTML/Javascript could be causing this issue. I am, however, good at problem solving, and solving puzzles.
I have two possible guesses:
Is there anything in your code, which could lead to a change in the resolution? The only reason that I think this may be part of the cause, is that because the image is being redrawn on a canvas that is the same size, any change in the image size could cause the image to become slightly pixilated. and repetitive changes in the image size would only exaggerate this change.
That is probably not the sole cause. I think that there is probably an additional cause because the issue is only present on the one specific tablet version. I am not familiar with that tablet version, but is there anything about its OS or interface that could alter the file when it is saved or redisplayed?
As a side note, it would be nice if you could provide a comparison image, just to see the change.
Hope that this at least points you in the right direction.

Related

HTML5 canvas apply color to image where shape overlays

I have this image drawn to a HTML5 canvas:
What I want to do is apply color to just a part of it.
The part where I want to apply color is defined by the following overlay image:
So, basically, I would like to guide my coloring by the overlay. So where the overlay pixels meets the main image pixels I should apply a color on the main image. At least that's how I see it working.
Notice that the overlay matches the whole image except for the lacing.
The catch is that I would like to retain the main image texture while applying the color. You can see that it has a leather texture and a "real" feel which I want to keep.
Can you please show me some methods of achieving this or share some thoughts?
Thank you!
globalCompositeOperation is your friend here.
Basically, you draw your overlay, then you set the gCO to 'source-atop' composite mode, which will make all your future drawings to only stay where there were already opaque pixels drawn, so it is important that your overlay has transparent parts.
So then you just fill a rectangle of your desired command, and finally you draw your original image, either behind, or blended to the new shape we just created.
var ctx = canvas.getContext('2d');
var loaded = 0;
function onload(){
if(++loaded === 2){
canvas.width = this.width;
canvas.height = this.height;
ctx.font = "40px sans-serif";
draw();
}
}
var original = new Image();
var overlay = new Image();
original.onload = overlay.onload = onload;
original.src = 'https://i.stack.imgur.com/vIKpI.png';
overlay.src = 'https://i.stack.imgur.com/10Tre.png';
// list of blending modes.
// Note that destination-over is a composite mode,
// which place the new drawings behind the already-there ones
var currentMode = 0;
var modes = ['destination-over', 'lighter', 'multiply', 'screen', 'overlay', 'darken',
'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light',
'exclusion', 'hue', 'saturation', 'color', 'luminosity' ];
function draw(){
// switch between different Blending modes
var mode = modes[currentMode];
currentMode = (currentMode+1)%(modes.length);
// clear previous
ctx.clearRect(0,0,canvas.width, canvas.height);
// draw our overlay
ctx.drawImage(overlay, 0,0);
// this will keep new drawings only where we already have existing pixels
ctx.globalCompositeOperation = 'source-atop';
ctx.fillStyle = 'red';
ctx.fillRect(0,0,canvas.width, canvas.height);
// now choose between the list of blending modes
ctx.globalCompositeOperation = mode;
// draw our original image
ctx.drawImage(original, 0,0);
// go back to default
ctx.globalCompositeOperation = 'source-over';
// just so we can know which one is shown
ctx.fillStyle = 'black';
ctx.fillText(mode, 40,40)
// do it again
setTimeout(draw, 1000)
}
canvas{
width: 100%;
}
<canvas id="canvas"></canvas>

Image resize with HTML5 Canvas - Distortion

I'm working on a Webworks app where I need to resize an image for upload to a server. I'm using JQuery Mobile, the app needs to run on OS6 and up. The user can either use the camera or select an image off the device. The relevant code is as follows:
function handleOpenedFile(fullPath, blobData) {
var image = new Image();
image.src = fullPath; //path to image
image.onload = function () {
var resized = resizeMe(image); // send it to canvas
//Do stuff with the DataURL returned from resizeMe()
};
}
function resizeMe(img) {
var canvas = document.createElement('canvas');
var width = Math.round(img.width / 2);
var height = Math.round(img.height / 2);
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
return canvas.toDataURL("image/jpeg", 0.8);
}
I then use the Base64 in the DataURL for uploading to the server. The images get scaled but they come out garbled. Parts of the image are shifted around and colours come out strange. It's not the scaling per se that messes up the image as it comes out garbled if you draw it on the canvas without any scaling.
I've searched both SO and the BB Dev forums extensively with no luck.
Does anyone have any idea how to fix this or have an alternative suggestion for resizing an image for upload?
I've managed to solve the problem although why it works eludes me. Simply replace
return canvas.toDataURL("image/jpeg", 0.8);
with
return canvas.toDataURL();
This returns a base64 encoded string with the properly resized image without any strange visual artifacts.
Cannot help with JQuery mobile, apps running on OS6 or using Base64 in the DataURL for uploading to a server but your code for the function resizeMe does not place the canvas in the HTML document anywhere.
Required line added below.
function resizeMe(img) {
var canvas = document.createElement('canvas');
var width = Math.round(img.width / 2);
var height = Math.round(img.height / 2);
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas); //append canvas child to body <<<<-----------
ctx.drawImage(img, 0, 0, width, height);
return canvas.toDataURL("image/jpeg", 0.8);
}
If this extra line is missed out in the jsfiddle below you get no image at all, with it you get a scaled image properly formed. The fiddle works on my andriod smart phone.
http://jsfiddle.net/FeALQ/

Canvas gets larger every time I use it to create a chart

I'm currently using Chart.JS on Phonegap and every time I make a new chart on the same Canvas, it gets twice as big. It's very strange and I don't know if I'm describing it properly. I'll clarify upon suggestions/questions, thanks guys.
Here is the javascript used to make the chart:
var ctx = $("#myChart").get(0).getContext("2d");
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
new Chart(ctx).Pie(data);
canvas.width = canvas.width
EDIT:
I don't know why this bug happens, but I fixed it by hard-setting the width and height each time:
var ctx = $("#myChart").get(0).getContext("2d");
// set canvas dimensions
ctx.canvas.width = chartWidth;
ctx.canvas.height = chartHeight;
// draw chart
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
drawChart(avgMin[1],avgMin[0], ctx);
// maintain canvas dimensions
ctx.canvas.width = chartWidth;
ctx.canvas.height = chartHeight;

html5 getImageData then putImageData results in fading image

I'm very new to Html5 and I was wondering if someone could shed some light on this:
<script type="text/javascript">
window.onload = function () {
var canvas = document.getElementById('canvas'); //682 x 111 pixel canvas
var context = canvas.getContext('2d');
var image = new Image();
image.src = "/Content/ImageTestOne/logo-for-dissolve.png"; //682 x 111 pixel image
image.onload = function () { context.drawImage(image, 0, 0); drawFrame(); };
function drawFrame() {
window.requestAnimationFrame(drawFrame, canvas);
imageData = context.getImageData(0, 0, canvas.width, canvas.height);
//Do something to some pixels here that persists over time
context.clearRect(0, 0, canvas.width, canvas.height);
context.putImageData(imageData, 0, 0);
};
};
</script>
According to my limited knowledge of Html5 this code should do nothing except continually display the "image". But instead the image quite rapidly burns out to almost white which suggests that the imageData is changed slightly each time it is either read from or written to the canvas...
Basically I wanted to fade the image where the mouse was located so that a background image shows through as the mouse is moved around. Is there a way around this or am I going to have to become a little more creative with the process? Is there anyway I can manipulate the "image" ImageData rather than getting it from the canvas each time?
Thanks in advance, I've tried using jpg and png and loading into DOM rather than via image.src but they all have the same issue.
Using the latest Chrome btw.
Here is the setup for the requestionAnimationFrame to handle a range of browsers with a fail over to SetTimeout:
(!window.requestAnimationFrame)
{
window.requestAnimationFrame = (window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
return window.setTimeout(callback, 1000/60);
});
}
Here is the code for the canvas
<canvas id="canvas" width="682" height="111"></canvas>
That's all the code for this.
putImageData() and getImageData() can be lossy. There's a note in the spec about this:
Due to the lossy nature of converting to and from premultiplied alpha
color values, pixels that have just been set using putImageData()
might be returned to an equivalent getImageData() as different values.
See also this related question:
Why does HTML Canvas getImageData() not return the exact same values that were just set?
I have tried also to apply this to my game where in im going to manipulate the selected pixels to have effect but It doesn't give me the expected result
here is some sample code that i used to manipulate the pixel to change
get image information and store
var img = context.getImageData(0,0, width, height)
var imgdata = img.data
var len = imgdata.length
loop to all data and manipulate pixel information
var i = 0;
for(i; i<leng; i++) {
var red = imgdata[i]
var green = imgadata[i+1]
var blue = imgdata[i+2]
var alpha = imgdata[i+3]
imgdata[i] = new value
imgdata[i+1] = new value
imgdata[i+2] = new value
imgdata[i+3] = new value
}
context.putImageData(img, 0,0)
then create animation frame to see effect
requestAnimationFrame is an experimental feature (june 2012) that uses time based frame access. The reason for this is avoid latency in animations.
I suggest you take a look at this Moz article.

How to change the opacity (alpha, transparency) of an element in a canvas element?

Using the HTML5 <canvas> element, I would like to load an image file (PNG, JPEG, etc.), draw it to the canvas completely transparently, and then fade it in. I have figured out how to load the image and draw it to the canvas, but I don't know how to change its opacity.
Here's the code I have so far:
var canvas = document.getElementById('myCanvas');
if (canvas.getContext)
{
var c = canvas.getContext('2d');
c.globalAlpha = 0;
var img = new Image();
img.onload = function() {
c.drawImage(img, 0, 0);
}
img.src = 'image.jpg';
}
Will somebody please point me in the right direction like a property to set or a function to call that will change the opacity?
I am also looking for an answer to this question, (to clarify, I want to be able to draw an image with user defined opacity such as how you can draw shapes with opacity) if you draw with primitive shapes you can set fill and stroke color with alpha to define the transparency. As far as I have concluded right now, this does not seem to affect image drawing.
//works with shapes but not with images
ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
I have concluded that setting the globalCompositeOperation works with images.
//works with images
ctx.globalCompositeOperation = "lighter";
I wonder if there is some kind third way of setting color so that we can tint images and make them transparent easily.
EDIT:
After further digging I have concluded that you can set the transparency of an image by setting the globalAlpha parameter BEFORE you draw the image:
//works with images
ctx.globalAlpha = 0.5
If you want to achieve a fading effect over time you need some kind of loop that changes the alpha value, this is fairly easy, one way to achieve it is the setTimeout function, look that up to create a loop from which you alter the alpha over time.
Some simpler example code for using globalAlpha:
ctx.save();
ctx.globalAlpha = 0.4;
ctx.drawImage(img, x, y);
ctx.restore();
If you need img to be loaded:
var img = new Image();
img.onload = function() {
ctx.save();
ctx.globalAlpha = 0.4;
ctx.drawImage(img, x, y);
ctx.restore()
};
img.src = "http://...";
Notes:
Set the 'src' last, to guarantee that your onload handler is called on all platforms, even if the image is already in the cache.
Wrap changes to stuff like globalAlpha between a save and restore (in fact use them lots), to make sure you don't clobber settings from elsewhere, particularly when bits of drawing code are going to be called from events.
Edit: The answer marked as "correct" is not correct.
It's easy to do. Try this code, swapping out "ie.jpg" with whatever picture you have handy:
<!DOCTYPE HTML>
<html>
<head>
<script>
var canvas;
var context;
var ga = 0.0;
var timerId = 0;
function init()
{
canvas = document.getElementById("myCanvas");
context = canvas.getContext("2d");
timerId = setInterval("fadeIn()", 100);
}
function fadeIn()
{
context.clearRect(0,0, canvas.width,canvas.height);
context.globalAlpha = ga;
var ie = new Image();
ie.onload = function()
{
context.drawImage(ie, 0, 0, 100, 100);
};
ie.src = "ie.jpg";
ga = ga + 0.1;
if (ga > 1.0)
{
goingUp = false;
clearInterval(timerId);
}
}
</script>
</head>
<body onload="init()">
<canvas height="200" width="300" id="myCanvas"></canvas>
</body>
</html>
The key is the globalAlpha property.
Tested with IE 9, FF 5, Safari 5, and Chrome 12 on Win7.
This suggestion is based on pixel manipulation in canvas 2d context.
From MDN:
You can directly manipulate pixel data in canvases at the byte level
To manipulate pixels we'll use two functions here - getImageData and putImageData.
getImageData usage:
var myImageData = context.getImageData(left, top, width, height);
The putImageData syntax:
context.putImageData(myImageData, x, y);
Where context is your canvas 2d context, and x and y are the position on the canvas.
So to get red green blue and alpha values, we'll do the following:
var r = imageData.data[((x*(imageData.width*4)) + (y*4))];
var g = imageData.data[((x*(imageData.width*4)) + (y*4)) + 1];
var b = imageData.data[((x*(imageData.width*4)) + (y*4)) + 2];
var a = imageData.data[((x*(imageData.width*4)) + (y*4)) + 3];
Where x is the horizontal offset, y is the vertical offset.
The code making image half-transparent:
var canvas = document.getElementById('myCanvas');
var c = canvas.getContext('2d');
var img = new Image();
img.onload = function() {
c.drawImage(img, 0, 0);
var ImageData = c.getImageData(0,0,img.width,img.height);
for(var i=0;i<img.height;i++)
for(var j=0;j<img.width;j++)
ImageData.data[((i*(img.width*4)) + (j*4) + 3)] = 127;//opacity = 0.5 [0-255]
c.putImageData(ImageData,0,0);//put image data back
}
img.src = 'image.jpg';
You can make you own "shaders" - see full MDN article here
You can. Transparent canvas can be quickly faded by using destination-out global composite operation. It's not 100% perfect, sometimes it leaves some traces but it could be tweaked, depending what's needed (i.e. use 'source-over' and fill it with white color with alpha at 0.13, then fade to prepare the canvas).
// Fill canvas using 'destination-out' and alpha at 0.05
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = "rgba(255, 255, 255, 0.05)";
ctx.beginPath();
ctx.fillRect(0, 0, width, height);
ctx.fill();
// Set the default mode.
ctx.globalCompositeOperation = 'source-over';
I think this answers the question best, it actually changes the alpha value of something that has been drawn already. Maybe this wasn't part of the api when this question was asked.
Given 2d context c.
function reduceAlpha(x, y, w, h, dA) {
let screenData = c.getImageData(x, y, w, h);
for(let i = 3; i < screenData.data.length; i+=4){
screenData.data[i] -= dA; //delta-Alpha
}
c.putImageData(screenData, x, y );
}
Set global Alpha draw the object that has opacity then set back to normal.
//////////////////////// circle ///////////////////////
ctx.globalAlpha = 0.75;
ctx.beginPath();
ctx.arc(x1, y1, r1, 0, Math.PI*2);
ctx.fillStyle = colour;
ctx.fill();
ctx.closePath();
ctx.globalAlpha = 1;
How i made it..on canvas i first draw rect in a selfrun function 0,0,canvas.width,canvas.height as a background of canvas and i set globalAlpha to 1 .then i draw other shapes in ather own functions and set their globalAlpha to 0.whatever number they dont affect each other even images.
Like Ian said, use c.globalAlpha = 0.5 to set the opacity, type up the rest of the settings for the square, then follow up with c.save();. This will save the settings for the square then you can c.rect and c.fillStyle the square how you want it. I chose not to wrap it with c.restore afterwards and it worked well
If you use jCanvas library you can use opacity property when drawing. If you need fade effect on top of that, simply redraw with different values.
You can't. It's immediate mode graphics. But you can sort of simulate it by drawing a rectangle over it in the background color with an opacity.
If the image is over something other than a constant color, then it gets quite a bit trickier. You should be able to use the pixel manipulation methods in this case. Just save the area before drawing the image, and then blend that back on top with an opacity afterwards.