Canvas Rotate, toDataUrl, and then Crop is ruining image quality - javascript

I have an image that I'm allowing users to rotate 90 degrees in any direction. Every time they rotate, I use canvas to perform the image manipulations and then save the data returned by the canvas.toDataURL("image/png", 1).
The problem is that the image quality decreases every time I rotate the image.
My end goal is to rotate a rectangular image without losing image quality and also saving the new data url.
function rotateAndSave(image: HTMLImageElement, degrees: number): string {
const imageWidth = image.naturalWidth;
const imageHeight = image.naturalHeight;
const startedHorizontalEndedVertical = imageWidth > imageHeight;
const canvasSize = startedHorizontalEndedVertical ? imageWidth : imageHeight;
const canvas = document.createElement("canvas");
canvas.width = canvasSize;
canvas.height = canvasSize;
const ctx = canvas.getContext("2d");
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
// center and rotate canvas
const translateCanvas = canvasSize / 2;
ctx.translate(translateCanvas, translateCanvas);
ctx.rotate(degrees * Math.PI / 180);
// draw from center
const translateImageX = startedHorizontalEndedVertical ? -translateCanvas : (-imageWidth / 2);
const translateImageY = startedHorizontalEndedVertical ? (-imageHeight / 2) : -translateCanvas;
ctx.drawImage(image, translateImageX, translateImageY);
// I got 'cropPlusExport' from another stackoverflow question.
function cropPlusExport(img, cropX, cropY, cropWidth, cropHeight) {
// create a temporary canvas sized to the cropped size
const canvas1 = document.createElement('canvas');
canvas1.width = cropWidth;
canvas1.height = cropHeight;
const ctx1 = canvas1.getContext('2d');
ctx1.setTransform(1, 0, 0, 1, 0, 0);
ctx1.clearRect(0, 0, canvas1.width, canvas1.height);
// use the extended from of drawImage to draw the
// cropped area to the temp canvas
ctx1.drawImage(img, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
return canvas1.toDataURL("image/png", 1);
}
// Start Cropping
let squareImage = new Image();
squareImage.src = canvas.toDataURL("image/png", 1);
squareImage.onload = () => {
const sx = startedHorizontalEndedVertical ? ((canvasSize - imageHeight) / 2) : 0;
const sy = startedHorizontalEndedVertical ? 0 : ((canvasSize - imageWidth) / 2);
const sw = imageHeight;
const sh = imageWidth;
const data = cropPlusExport(squareImage, sx, sy, sw, sh);
// Update DOM via angular binding...
const dataUrl = data.split(",")[1];
this.imageSource = dataUrl;
squareImage = null;
}
example html
<div class="view">
<img [src]="imageSource" />
</div>
Keep in mind that I am cropping to the natural width and height of the image. So, what's weird is that if I don't crop, then the image quality doesn't change but when I do crop, the image quality changes.

Canvas drawing is lossy, and rotating an image induce hard modifications of the pixels. So indeed, if you start always from the last state, you'll end up adding more and more artifacts to your image.
Simply store the original image somewhere and always start from there instead of using the modified version.
// will fire in a loop
img.onload = e => elem.rotateAndSave(1);
const elem = {
// store a copy of the original image
originalimage: img.cloneNode(),
angle: 0,
rotateAndSave(degrees) {
// always use the stored original image
const image = this.originalimage;
// keep track of current transform
this.angle += degrees;
const imageWidth = image.naturalWidth;
const imageHeight = image.naturalHeight;
const startedHorizontalEndedVertical = imageWidth > imageHeight;
const canvasSize = startedHorizontalEndedVertical ? imageWidth : imageHeight;
const canvas = document.createElement("canvas");
canvas.width = canvasSize;
canvas.height = canvasSize;
const ctx = canvas.getContext("2d");
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
// center and rotate canvas
const translateCanvas = canvasSize / 2;
ctx.translate(translateCanvas, translateCanvas);
ctx.rotate(this.angle * Math.PI / 180);
// draw from center
const translateImageX = startedHorizontalEndedVertical ? -translateCanvas : (-imageWidth / 2);
const translateImageY = startedHorizontalEndedVertical ? (-imageHeight / 2) : -translateCanvas;
ctx.drawImage(image, translateImageX, translateImageY);
// I got 'cropPlusExport' from another stackoverflow question.
function cropPlusExport(img, cropX, cropY, cropWidth, cropHeight) {
// create a temporary canvas sized to the cropped size
const canvas1 = document.createElement('canvas');
canvas1.width = cropWidth;
canvas1.height = cropHeight;
const ctx1 = canvas1.getContext('2d');
ctx1.setTransform(1, 0, 0, 1, 0, 0);
ctx1.clearRect(0, 0, canvas1.width, canvas1.height);
// use the extended from of drawImage to draw the
// cropped area to the temp canvas
ctx1.drawImage(img, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
return canvas1.toDataURL("image/png", 1);
}
// Start Cropping
let squareImage = new Image();
squareImage.src = canvas.toDataURL("image/png", 1);
squareImage.onload = () => {
const sx = startedHorizontalEndedVertical ? ((canvasSize - imageHeight) / 2) : 0;
const sy = startedHorizontalEndedVertical ? 0 : ((canvasSize - imageWidth) / 2);
const sw = imageHeight;
const sh = imageWidth;
const data = cropPlusExport(squareImage, sx, sy, sw, sh);
// Update DOM via angular binding...
img.src = data;
}
}
};
<img crossorigin src="https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg" id="img">

Related

Fabric Canvas - Image resolution is very poor

I'm implementing Fabricjs in my application for an editing tool. I need to set a high resolution image into the canvas. If I use setBackgroundImage method, it is only working for small sized (Size very less than canvas) images. So I need to reduce the size of the image (but need to keep the ratio) to have something good looking.
My first idea is down sampling which works fine for some pictures but not for all. Here is the code :
Method 1)
var steps = 2;
var imgAspect = img.width / img.height;
var oc = document.createElement('canvas');
octx = oc.getContext('2d');
for(var i = 0; i < steps; i++){
if(i == 0){
oc.width = img.width * 0.5;
oc.height = img.height * 0.5;
octx.drawImage(img, 0, 0, oc.width, oc.height);
}
else{
octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);
}
}
_w = canvas.width;
_h = canvas.height;
ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5, 0, 0, _w, _h);
var data = ctx.getImageData(0, 0, canvas.width, canvas.height);
var c = document.createElement('canvas');
c.setAttribute('id', '_temp_canvas');
c.width = canvas.width;
c.height = canvas.height;
c.getContext('2d').putImageData(data, 0, 0);
var img = fabric.Image.fromURL(c.toDataURL(), function(img) {
img.left = 00;
img.top = 00;
img.isFixed = true;
img.selectable = false;
canvas.add(img);
c = null;
$('#_temp_canvas').remove();
canvas.renderAll();
});
My second idea, I'm following from here which looks very simple
var canvasCopy = document.createElement("canvas")
var copyContext = canvasCopy.getContext("2d")
var ratio = 1;
if(img.width > canvas.width){
ratio = canvas.width / img.width;
}
else if(img.height > canvas.height){
ratio = canvas.height / img.height;
}
canvasCopy.width = img.width;
canvasCopy.height = img.height;
copyContext.drawImage(img, 0, 0);
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
The issue is none of them producing good resolution. Method 1 works well for some pictures but it is failed for few pictures. If I try the method 2 it is working for the failed pictures from method 1. Can somebody help what is missing regarding the resolution ?
See the fiddle1 and fiddle2 with 2 different images. You can see the difference by calling the method1 and method2 in the _img.onload function to see the difference.
I don't know if this is your case, but for me worked imageSmoothingEnabled=false like
var canvasCopy = document.createElement("canvas")
var copyContext = canvasCopy.getContext("2d")
copyContext.imageSmoothingEnabled = false;
reference http://fabricjs.com/lanczos-webgl

Converting canvas to PNG returns blank image

I am experiencing an issue with converting a canvas to PNG.
Although the canvas looks exactly as I want it and the conversion from canvas to data URL PNG seems right, the image is blank.
I also tried converting a div to PNG but it did not work for me because I wanted a greyscale filter to be applied. Anyone have any ideas?
JavaScript
var imgis = new Image();
var bubble = new Image();
var canvasWidth;
var canvasHeight;
var ctx = canvas.getContext('2d');
bubble.onload = function() {
var imgis = new Image();
var bubble = new Image();
var ctx = canvas.getContext('2d');
bubble.onload = function() {
// set the canvas' size
canvas.width = this.width;
canvas.height = this.height;
// first fill a rect
ctx.fillStyle = 'rgba(255, 255, 255, 0)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// set the gCO
ctx.globalCompositeOperation = 'luminosity';
// if the browser doesn't support Blend Modes
console.log(ctx.globalCompositeOperation)
if (ctx.globalCompositeOperation !== 'luminosity')
fallback(this);
else {
// draw the image
ctx.drawImage(this, 0, 0);
ctx.drawImage(imgis, 30, 60);
// reset the gCO
ctx.globalCompositeOperation = 'source-over';
}
}
imgis.crossOrigin = "anonymous";
bubble.crossOrigin = "anonymous";
imgis.src = "image1 src";
bubble.src = "image2 src";
function fallback(img) {
// first remove our black rectangle
ctx.clearRect(0, 0, canvas.width, canvas.height);
//draw the image
ctx.drawImage(img, 0, 0);
ctx.drawImage(imgis, 30, 60);
// get the image data
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var d = imgData.data;
// loop through all pixels
// each pixel is decomposed in its 4 rgba values
for (var i = 0; i < d.length; i += 4) {
// get the medium of the 3 first values
var med = (d[i] + d[i + 1] + d[i + 2]) / 3;
// set it to each value
d[i] = d[i + 1] = d[i + 2] = med;
}
// redraw the new computed image
ctx.putImageData(imgData, 0, 0);
}
canvas = document.getElementById('canvas');
var image = Canvas2Image.convertToPNG(canvas);
console.log(image.src);
// document.getElementById('theDemo').src = image.src;
var image_data = $(image).attr('src');
console.log(image_data);
$("#theDemo").attr('src', image_data);
HTML
<canvas id='canvas' > </canvas>
<img src="" id="theDemo" />
I assume you're using canvas2image. You should replace var image = Canvas2Image.convertToPNG(canvas); with Canvas2Image.convertToPNG(canvas, width, height). Hopefully that helps!
EDIT Since the issue is with the actual canvas to base64 conversion, you can try to use the .toDataURL() method instead of using that library. My comment explains how to test this in your specific code.

Pixelated resize using canvas with transparent PNG

I want to accomplish a pixelated effect using the canvas option imageSmoothingEnabled=false; so the image "unblurs" on scroll.
Everything works fine until using transparent images namely PNGs. The scaled image is projected, which stays in the background.
Also the image does not get loaded until the user has scrolled a few pixels.
I've found out that the canvas.drawImage() function owns parameters to set the offset. However I haven't found a solution to this.
Demo https://jsfiddle.net/aLjfemru/
var ctx = canvas.getContext('2d'),
img = new Image(),
play = false;
/// turn off image smoothing - this will give the pixelated effect
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
/// wait until image is actually available
img.onload = function(){
image1.src="nf.png";
context.drawImage(image1, 50, 50, 10, 10);
};
img.src = 'https://upload.wikimedia.org/wikipedia/commons/b/bb/Gorgosaurus_BW_transparent.png';
/// MAIN function
function pixelate(v) {
document.getElementById("v").innerHTML = "(v): " + v;
/// if in play mode use that value, else use slider value
var size = v * 0.01;
var w = canvas.width * size;
var h = canvas.height * size;
/// draw original image to the scaled size
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, w, h);
ctx.drawImage(canvas, 0, 0, w, h, 0, 0, canvas.width, canvas.height);
}
function onScroll() {
$(window).on('scroll', function() {
var y = window.pageYOffset;
if (y > 10) {
y = Math.pow(y, 0.8);
if (y >= 60) {
y = 100;
}
pixelate(y);
}
});
}
onScroll();
Some quick changes to get it happening
Use a second canvas to do the pixelation
Wait for the images to load before doing the rendering.
The onscroll will not fire until you scroll, so when image has loaded call the rendering function to display the image.
canvas.width = innerWidth-20;
ctx = canvas.getContext("2d");
var ctxImage;
const img = new Image;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/b/bb/Gorgosaurus_BW_transparent.png';
/// wait until image is actually available
img.onload = function(){
// I dont knwo what this is for so removed the following two lines
//image1.src="nf.png";
//context.drawImage(image1, 50, 50, 10, 10);
// Create a canvas to match the image
var c = document.createElement("canvas");
canvas.width = Math.min(canvas.width,(c.width = this.naturalWidth));
canvas.height = c.height = this.naturalHeight;
ctxImage = c.getContext("2d");
// changing canvas size resets the state so need to set this again.
ctx.imageSmoothingEnabled = false;
onScroll();
pixelate(100); // call first time
};
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.fillText("Loading please wait.",ctx.canvas.width /2, ctx.canvas.height / 4);
/// MAIN function
function pixelate(v) {
document.getElementById("v").innerHTML = "(v): " + v;
/// if in play mode use that value, else use slider value
var size = Number(v) * 0.01;
var w = img.width * size;
var h = img.height * size;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctxImage.clearRect(0, 0, ctxImage.canvas.width, ctxImage.canvas.height);
ctxImage.drawImage(img, 0, 0, w, h);
ctx.drawImage(ctxImage.canvas, 0, 0, w, h, 0, 0, canvas.width, canvas.height);
}
function onScroll() {
addEventListener("scroll", function() {
var y = window.pageYOffset;
if (y > 10) {
y = Math.pow(y, 0.65);
if (y >= 100) {
y = 100;
}
pixelate(y);
}
});
}
#fix {
position: fixed;
}
html {
height: 2000px;
}
<div id="fix">
<p id="v" value="Animate">1</p><br />
<canvas id="canvas"></canvas>
</div>
This has since been made into an extremely minimalist library, and my PR for PNG support can be found here.
Once it has been merged I will come back and update this answer.
The full code, generalized and simplified from #Blindman67's answer:
/**
* 8bit
*
* A module that converts an image into a pixelated version (just like
* 8bit artwork).
*
* #author rogeriopvl <https://github.com/rogeriopvl>
* #license MIT
*/
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define([], factory);
} else if (typeof exports === "object") {
module.exports = factory();
} else {
root.eightBit = factory();
}
} (this, function () {
// Necessary to hide the original image with PNG transparency
const invisibleCanvas = document.createElement("canvas");
const invisibleCtx = invisibleCanvas.getContext("2d");
/**
* Draws a pixelated version of an image in a given canvas.
* #param {object} canvas - a canvas object
* #param {object} image - an image HTMLElement object
* #param {number} quality - the new quality: between 0 and 100
*/
const eightBit = function (canvas, image, quality) {
quality /= 100;
canvas.width = invisibleCanvas.width = image.width;
canvas.height = invisibleCanvas.height = image.height;
const scaledW = canvas.width * quality;
const scaledH = canvas.height * quality;
const ctx = canvas.getContext("2d");
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
// Draws image scaled to desired quality on the invisible canvas, then
// draws that scaled image on the visible canvas.
ctx.clearRect(0, 0, canvas.width, canvas.height);
invisibleCtx.clearRect(0, 0, invisibleCtx.canvas.width, invisibleCtx.canvas.height);
invisibleCtx.drawImage(image, 0, 0, scaledW, scaledH);
ctx.drawImage(invisibleCtx.canvas, 0, 0, scaledW, scaledH, 0, 0, canvas.width, canvas.height);
};
return eightBit;
}));

Inset-shadow on HTML5 canvas image

I've seen this question before but the answers given are for canvas images that have been drawn on via path however, i'm drawing an image.
Is it possible to create an inset-shadow?
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.shadowBlur = 10;
context.shadowColor = 'rgba(30,30,30, 0.4)';
var imgOne = new Image();
imgOne.onload = function() {
context.drawImage(imgOne, 0, 0);
};
imgOne.src = "./public/circle.png";
So I draw the circle picture on. I've now at the moment got a slight shadow on the outside of the circle, how can I get this inset instead of offset?
Composition chain
Use a series of composite + draw operation to obtain inset shadow.
Note: the solution require exclusive access to the canvas element when created so either do this on an off-screen canvas and draw back to main, or if possible, plan secondary graphics to be drawn after this has been generated.
The needed steps:
Draw in original image
Invert alpha channel filling the canvas with a solid using xor composition
Define shadow and draw itself back in
Deactivate shadow and draw in original image (destination-atop)
var ctx = c.getContext("2d"), img = new Image;
img.onload = function() {
// draw in image to main canvas
ctx.drawImage(this, 0, 0);
// invert alpha channel
ctx.globalCompositeOperation = "xor";
ctx.fillRect(0, 0, c.width, c.height);
// draw itself again using drop-shadow filter
ctx.shadowBlur = 7*2; // use double of what is in CSS filter (Chrome x4)
ctx.shadowOffsetX = ctx.shadowOffsetY = 5;
ctx.shadowColor = "#000";
ctx.drawImage(c, 0, 0);
// draw original image with background mixed on top
ctx.globalCompositeOperation = "destination-atop";
ctx.shadowColor = "transparent"; // remove shadow !
ctx.drawImage(this, 0, 0);
}
img.src = "http://i.imgur.com/Qrfga2b.png";
<canvas id=c height=300></canvas>
Canvas will shadow where an image changes from opaque to transparent so, as K3N shows in his correct answer, you can turn the image inside out (opaque becomes transparent & visa-versa) so the shadows are drawn inside the circle.
If you know your circle's centerpoint and radius, you can use a stroked-path to create an inset circle shadow. Here's an example:
var canvas=document.getElementById("canvas");
var context=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
context.beginPath();
context.arc(cw/2,ch/2,75,0,Math.PI*2);
context.fillStyle='lightcyan';
context.fill();
context.globalCompositeOperation='source-atop';
context.shadowOffsetX = 500;
context.shadowOffsetY = 0;
context.shadowBlur = 15;
context.shadowColor = 'rgba(30,30,30,1)';
context.beginPath();
context.arc(cw/2-500,ch/2,75,0,Math.PI*2);
context.stroke();
context.stroke();
context.stroke();
context.globalCompositeOperation='source-over';
<canvas id="canvas" width=300 height=300></canvas>
If your path is irregular or hard to define mathematically, you can also use edge-path detection algorithms. One common edge-path algorithm is Marching Squares. Stackoverflow's K3N has coded a nice Marching Squares algorithm.
Inspired by markE's answer , I made my own version based on a png instead of vector-graphics.
Additionnaly, I made possible to choose the true alpha of the shadow (because the default shadow strength is a way too soft in my opinion)
var img = document.getElementById("myImage");
img.onload = function(){
createInnerShadow(this,5,1);
}
function createInnerShadow(img,distance,alpha){
//the size of the shadow depends on the size of the target,
//then I will create extra "walls" around the picture to be sure
//tbat the shadow will be correctly filled (with the same intensity everywhere)
//(it's not obvious with this image, but it is when there is no space at all between the image and its border)
var offset = 50 + distance;
var hole = document.createElement("canvas");
var holeContext = hole.getContext("2d");
hole.width = img.width + offset*2;
hole.height = img.height + offset*2;
//first, I draw a big black rect
holeContext.fillStyle = "#000000";
holeContext.fillRect(0,0,hole.width,hole.height);
//then I use the image to make an hole in it
holeContext.globalCompositeOperation = "destination-out";
holeContext.drawImage(img,offset,offset);
//I create a new canvas that will contains the shadow of the hole only
var shadow = document.createElement("canvas");
var shadowContext = shadow.getContext("2d");
shadow.width = img.width;
shadow.height = img.height;
shadowContext.filter = "drop-shadow(0px 0px "+distance+"px #000000 ) ";
shadowContext.drawImage(hole,-offset,-offset);
shadowContext.globalCompositeOperation = "destination-out";
shadowContext.drawImage(hole,-offset,-offset);
//now, because the default-shadow filter is really to soft, I normalize the shadow
//then I will be sure that the alpha-gradient of the shadow will start at "alpha" and end at 0
normalizeAlphaShadow(shadow,alpha);
//Finally, I create another canvas that will contain the image and the shadow over it
var result = document.createElement("canvas");
result.width = img.width;
result.height = img.height;
var context = result.getContext("2d");
context.drawImage(img,0,0)
context.drawImage(shadow,0,0);
//and that's it !
document.body.appendChild(result);
}
function normalizeAlphaShadow(canvas,alpha){
var imageData = canvas.getContext("2d").getImageData(0,0,canvas.width,canvas.height);
var pixelData = imageData.data;
var i,len = pixelData.length;
var max = 0;
for(i=3;i<len;i+=4) if(pixelData[i]>max) max = pixelData[i];
max = (255/max) * alpha;
for(i=3;i<len;i+=4) pixelData[i] *= max;
canvas.getContext("2d").putImageData(imageData,0,0)
}
<html>
<body>
<img id="myImage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAACWCAYAAAB92c4YAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkFENzg2NTc4MDg5MTFFOEI1OTdBNEZCMEY2OTg3OTAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkFENzg2NTg4MDg5MTFFOEI1OTdBNEZCMEY2OTg3OTAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyQUQ3ODY1NTgwODkxMUU4QjU5N0E0RkIwRjY5ODc5MCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyQUQ3ODY1NjgwODkxMUU4QjU5N0E0RkIwRjY5ODc5MCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pk/K8voAADFpSURBVHja5H0HmFXV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" />
</body>
</html>
the jsfiddle is here : https://jsfiddle.net/jrekw5og/141/
Inspired by K3N's answer, I've created Inset.js for this exact situation!
Inset.js
Only requires setting ctx.shadowInset = true;
For example: http://codepen.io/patlillis/pen/ryoWey
var ctx = canvas.getContext('2d');
var img = new Image;
img.onload = function() {
ctx.shadowInset = true;
ctx.shadowBlur = 25;
ctx.shadowColor = "#000";
ctx.drawImage(this, 0, 0);
}
img.src = "http://i.imgur.com/Qrfga2b.png";
const width = 100 * devicePixelRatio;
const height = 100 * devicePixelRatio;
// original canvas
const c = document.getElementById('canvas');
c.width = 300 * devicePixelRatio;
c.height = 300 * devicePixelRatio;
c.style.width = '300px';
c.style.height = '300px';
const cctx = c.getContext('2d');
cctx.fillStyle = 'rgb(20,205,75)';
cctx.arc(150 * devicePixelRatio, 150 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
cctx.fill();
// temporary canvas
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.style.width = `${width / devicePixelRatio}px`;
canvas.style.height = `${height / devicePixelRatio}px`;
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
// original object on temporary canvas
ctx.arc(50 * devicePixelRatio, 50 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
ctx.fill();
// shadow cutting
ctx.globalCompositeOperation = 'xor';
ctx.arc(50 * devicePixelRatio, 50 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
ctx.fill();
// shadow props
ctx.shadowBlur = 50;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = -25;
ctx.shadowColor = '#000';
ctx.arc(50 * devicePixelRatio, 50 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
ctx.fill();
// shadow color
ctx.globalCompositeOperation = 'source-in';
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// object cutting
ctx.globalCompositeOperation = 'destination-in';
ctx.arc(50 * devicePixelRatio, 50 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
ctx.fill();
// shadow opacity
cctx.globalAlpha = .4
// inserting shadow into original canvas
cctx.drawImage(canvas, 200, 200);
Colored shadow /w opacity

Why are pixels retrieved from canvas all black?

As an exercise, I would like to fill the background of the browser window with pixels of random color: https://jsfiddle.net/j8fay7bs/3/
My question is how to retrieve the current filling of the background image and randomly change the pixels? Currently the image seems to be reverted to black.
// redraw canvas pixels on resize --------------------------
var render = function resize() {
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.canvas.width = window.innerWidth;
context.canvas.height = window.innerHeight;
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
var t0 = performance.now();
for (var i = 0; i < canvas.width*canvas.height; i++) {
if (Math.random() < 0.5) {
imageData.data[i*4] = Math.round(Math.random()*255);
imageData.data[i*4+1] = Math.round(Math.random()*255);
imageData.data[i*4+2] = Math.round(Math.random()*255);
imageData.data[i*4+3] = 255;
}
}
context.putImageData(imageData, 0, 0);
$('#fps').text(1000/(performance.now() - t0) + " fps");
}
window.onresize = render;
// rendering loop ---------------------------------------
(function loop() {
setInterval(render, 10);
})();
You may retrieve the last image data before u touch the canvas width and height,
once u touched them, the canvas will be reset:
var render = function resize() {
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var oldImageData = context.getImageData(0, 0, canvas.width, canvas.height); // <----
context.canvas.width = window.innerWidth;
context.canvas.height = window.innerHeight;
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
updated fiddle

Categories

Resources