What is the fastest way to create copy this image without green color or making green color transparent or removing green color from 100X100 px top left
In this case do I have to check every pixel value?
This process is too slow, eg: for 100X100px it takes 40000 loops for checking all rgba values
In browsers that do support it, you could make use of svg filters to do it:
Here is an other Q/A that shows an interesting way of doing this for a fixed color.
Here I made a simple helper function that will set up for us the required tableValues with a bit of tolerance, and I removed the <feFill> so the selected color become transparent (<feFill> would taint the canvas in Chrome).
If you wish to replace the color, you can still achieve it with the canvas' compositing options (commented code in below snippet).
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = e => {
canvas.width = img.width;
canvas.height = img.height;
// update our filter
updateChroma([76, 237, 0], 8);
// if you wish to replace the color, uncomment followings
// ctx.fillStyle = "your_replaceColor";
// ctx.fillRect(0,0,img.width,img.height);
ctx.filter = 'url(#chroma)';
ctx.drawImage(img, 0, 0);
ctx.filter = 'none';
// ctx.globalCompositeOperation = 'destination-in';
// ctx.drawImage(img, 0,0);
};
img.src = "https://i.stack.imgur.com/hZm8o.png";
function updateChroma(rgb, tolerance) {
const sels = ['R', 'G', 'B'];
rgb.forEach((value, ind) => {
const fe = document.querySelector('#chroma feFunc' + sels[ind]);
let vals = '';
if (!value) {
vals = '0'
} else {
for (let i = 0; i < 256; i++) {
vals += (Math.abs(value - i) <= tolerance) ? '1 ' : '0 ';
}
}
fe.setAttribute('tableValues', vals);
});
}
canvas {
background: ivory
}
<svg width="0" height="0" style="position:absolute;visibility:hidden">
<filter id="chroma" color-interpolation-filters="sRGB"x="0" y="0" height="100%" width="100%">
<feComponentTransfer>
<feFuncR type="discrete"/>
<feFuncG type="discrete"/>
<feFuncB type="discrete"/>
</feComponentTransfer>
<feColorMatrix type="matrix" values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
1 1 1 1 -1" result="selected"/>
<feComposite in="SourceGraphic" in2="selected" operator="out"/>
</filter>
</svg>
<canvas id="canvas"></canvas>
I didn't do extensive tests on many devices, but where Hardware Acceleration is enabled, this might perform better than any pixel loop, since it should be all done on GPU.
But browser support is still not that great...
So you may need to fallback to pixel manips anyway.
Here, depending on what it is you are doing the chroma on, you may want to sacrifice a bit of quality for speed.
For instance, on video, you can perform the chroma on a downsized canvas, then draw it back with compositing on the main one, winning a few iterations per frames. See this previous Q/A for an example.
If you check every pixel value and remove the green you'll be left with a ugly image with holes. An easier way to do it and with better results is planning in advance. This is produced with canvas. When you draw the image you may save every circle in an array, and then redraw everything after excluding the green circles.
In the next example click the color to choose it or click the D to remove the green circles.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw = canvas.width = 300,
cx = cw / 2;
var ch = canvas.height = 180,
cy = ch / 2;
var color = "blue";
var drawing = false;
var points= [];
class Point{
constructor(color,x,y){
this.color = color;
this.x = x;
this.y = y
}
draw(){
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x,this.y,5,0,2*Math.PI);
ctx.fill()
}
}
canvas.addEventListener('mousedown', function(evt) {
drawing = true;
}, false);
canvas.addEventListener('mouseup', function(evt) {
drawing = false;
}, false);
canvas.addEventListener("mouseout", function(evt) {
drawing = false;
}, false);
canvas.addEventListener("mousemove", function(evt) {
if (drawing) {
ctx.clearRect(0, 0, cw, ch);
m = oMousePos(canvas, evt);
var point = new Point(color,m.x,m.y);
//point.draw();
points.push(point);
points.forEach((p) =>{
p.draw()
})
}
}, false);
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return {
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
colors.addEventListener("click", (e)=>{
if(e.target.tagName == "SPAN"){color = e.target.id;
}else if(e.target.id == "deleteGreen"){
ctx.clearRect(0,0,cw,ch);
points.forEach( p => {
if(p.color !== "green"){p.draw()}
})
}
})
body {
background-color: #eee;
}
#app {
display: block;
margin: auto;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 300px;
height: 300px;
}
canvas {
background: #fff;
border-radius: 3px;
box-shadow: 0px 0px 15px 3px #ccc;
cursor: pointer;
}
#colors {
display: flex;
margin-top: 1em;
justify-content: space-between;
}
#colors span, #deleteGreen {
display: block;
width: 50px;
height: 50px;
border: 1px solid #d9d9d9;
}
#green {
background-color: green;
}
#gold {
background-color: gold;
}
#tomato {
background-color: tomato;
}
#blue {
background-color: blue;
}
#deleteGreen {
text-align: center;
line-height: 50px;
}
<div id="app">
<canvas id="canvas">:( </canvas>
<div id="colors" >
<span id="green"></span>
<span id="gold"></span>
<span id="tomato"></span>
<span id="blue"></span>
<div id="deleteGreen">D</div>
</div>
</div>
Related
So I made this canvas on which you can paint on. The problem is that when you erase your drawings it will also erase the background.
// SETTING ALL VARIABLES
var isMouseDown=false;
var canvas = document.createElement('canvas');
var body = document.getElementsByTagName("body")[0];
var ctx = canvas.getContext('2d');
var linesArray = [];
currentSize = 5;
var currentColor = "rgb(200,20,100)";
var currentBg = "white";
let newImage = new Image();
newImage.src = 'https://www.arnoldvanhooft.nl/wp-content/uploads/2019/06/ja-knop.png'
// INITIAL LAUNCH
newImage.onload = () => {
ctx.drawImage(newImage, 0, 0, 500, 500);
}
createCanvas();
// BUTTON EVENT HANDLERS
document.getElementById('canvasUpdate').addEventListener('click', function() {
createCanvas();
redraw();
});
document.getElementById('colorpicker').addEventListener('change', function() {
currentColor = this.value;
});
document.getElementById('bgcolorpicker').addEventListener('change', function() {
ctx.fillStyle = this.value;
ctx.fillRect(0, 0, canvas.width, canvas.height);
redraw();
currentBg = ctx.fillStyle;
});
document.getElementById('controlSize').addEventListener('change', function() {
currentSize = this.value;
document.getElementById("showSize").innerHTML = this.value;
});
document.getElementById('saveToImage').addEventListener('click', function() {
downloadCanvas(this, 'canvas', 'masterpiece.png');
}, false);
document.getElementById('eraser').addEventListener('click', eraser);
document.getElementById('clear').addEventListener('click', createCanvas);
document.getElementById('save').addEventListener('click', save);
document.getElementById('load').addEventListener('click', load);
document.getElementById('clearCache').addEventListener('click', function() {
localStorage.removeItem("savedCanvas");
linesArray = [];
console.log("Cache cleared!");
});
// REDRAW
function redraw() {
for (var i = 1; i < linesArray.length; i++) {
ctx.beginPath();
ctx.moveTo(linesArray[i-1].x, linesArray[i-1].y);
ctx.lineWidth = linesArray[i].size;
ctx.lineCap = "round";
ctx.strokeStyle = linesArray[i].color;
ctx.lineTo(linesArray[i].x, linesArray[i].y);
ctx.stroke();
}
}
// DRAWING EVENT HANDLERS
canvas.addEventListener('mousedown', function() {mousedown(canvas, event);});
canvas.addEventListener('mousemove',function() {mousemove(canvas, event);});
canvas.addEventListener('mouseup',mouseup);
// CREATE CANVAS
function createCanvas() {
canvas.id = "canvas";
canvas.width = parseInt(document.getElementById("sizeX").value);
canvas.height = parseInt(document.getElementById("sizeY").value);
canvas.style.zIndex = 8;
canvas.style.position = "absolute";
canvas.style.border = "1px solid";
ctx.fillStyle = currentBg;
ctx.fillRect(0, 0, canvas.width, canvas.height);
body.appendChild(canvas);
}
// DOWNLOAD CANVAS
function downloadCanvas(link, canvas, filename) {
link.href = document.getElementById(canvas).toDataURL();
link.download = filename;
}
// SAVE FUNCTION
function save() {
localStorage.removeItem("savedCanvas");
localStorage.setItem("savedCanvas", JSON.stringify(linesArray));
console.log("Saved canvas!");
}
// LOAD FUNCTION
function load() {
if (localStorage.getItem("savedCanvas") != null) {
linesArray = JSON.parse(localStorage.savedCanvas);
var lines = JSON.parse(localStorage.getItem("savedCanvas"));
for (var i = 1; i < lines.length; i++) {
ctx.beginPath();
ctx.moveTo(linesArray[i-1].x, linesArray[i-1].y);
ctx.lineWidth = linesArray[i].size;
ctx.lineCap = "round";
ctx.strokeStyle = linesArray[i].color;
ctx.lineTo(linesArray[i].x, linesArray[i].y);
ctx.stroke();
}
console.log("Canvas loaded.");
}
else {
console.log("No canvas in memory!");
}
}
// ERASER HANDLING
function eraser() {
currentSize = 50;
currentColor = ctx.fillStyle
}
// GET MOUSE POSITION
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
// ON MOUSE DOWN
function mousedown(canvas, evt) {
var mousePos = getMousePos(canvas, evt);
isMouseDown=true
var currentPosition = getMousePos(canvas, evt);
ctx.moveTo(currentPosition.x, currentPosition.y)
ctx.beginPath();
ctx.lineWidth = currentSize;
ctx.lineCap = "round";
ctx.strokeStyle = currentColor;
}
// ON MOUSE MOVE
function mousemove(canvas, evt) {
if(isMouseDown){
var currentPosition = getMousePos(canvas, evt);
ctx.lineTo(currentPosition.x, currentPosition.y)
ctx.stroke();
store(currentPosition.x, currentPosition.y, currentSize, currentColor);
}
}
// STORE DATA
function store(x, y, s, c) {
var line = {
"x": x,
"y": y,
"size": s,
"color": c
}
linesArray.push(line);
}
// ON MOUSE UP
function mouseup() {
isMouseDown=false
store()
}
.colorButtons {
display: block;
margin: 20px 0;
}
canvas {
cursor: crosshair;
}
div#sidebar {
position: absolute;
left: 0;
width: 150px;
padding: 20px 20px;
top: 0;
}
canvas#canvas {
left: 150px;
top: 45px;
}
.btn {
margin-bottom: 10px;
width: 100%;
}
input {
width: 100%;
margin-bottom: 10px;
}
.input-group {
margin-bottom: 10px;
}
.toolsButtons .btn {
width: 48%;
}
.sizeButtons .btn {
width: 48%;
}
.colorpicker {
background: transparent;
height: 40px;
}
<!-- using Bootstrap CSS because lazy to write 3 classes -->
<body>
<div id="sidebar">
<div class="colorButtons">
<h3>Colour</h3>
<input type="color" id="colorpicker" value="#c81464" class="colorpicker">
</div>
<div class="colorButtons">
<h3>Bg Color</h3>
<input type="color" value="#ffffff" id="bgcolorpicker" class="colorpicker">
</div>
<div class="toolsButtons">
<h3>Tools</h3>
<button id="eraser" class="btn btn-default">eraser</span></button>
<button id="clear" class="btn btn-danger"> <span class="glyphicon glyphicon-repeat" aria-hidden="true"></span></button>
</div>
<div class="buttonSize">
<h3>Size (<span id="showSize">5</span>)</h3>
<input type="range" min="1" max="50" value="5" step="1" id="controlSize">
</div>
<div class="canvasSize">
<h3>Canvas</h3>
<div class="input-group">
<span class="input-group-addon">X</span>
<input type="number" id="sizeX" class="form-control" placeholder="sizeX" value="800" class="size">
</div>
<div class="input-group">
<span class="input-group-addon">Y</span>
<input type="number" id="sizeY" class="form-control" placeholder="sizeY" value="800" class="size">
</div>
<input type="button" class="updateSize btn btn-success" value="Update" id="canvasUpdate">
</div>
<div class="Storage">
<h3>Storage</h3>
<input type="button" value="Save" class="btn btn-warning" id="save">
<input type="button" value="Load" class="btn btn-warning" id="load">
<input type="button" value="Clear" class="btn btn-warning" id="clearCache">
</div>
<div class="extra">
<h3>Extra</h3>
<a id="saveToImage" class="btn btn-warning">Download</a>
</div>
</div>
</body>
I have tried by adding the photo in a different way but that way it wouldn't be saved the right way. I also have tried changing layers with CSS and index but that also didn't work
Using layers
A canvas drawing app can use many canvases to define layers. Layers can include things like backgrounds, drawing layers, composite layers (multiply, screen, etc) and much more. Much the same as layers are used in apps like photoshop.
A bonus when using layers is that the immediate drawing state can be displayed without affecting the existing layers, as you can draw the pen on the output layer when the mouse button is not down. (see example)
To get the most from canvas layers you should become familiar with the many ctx.globalCompositeOperation modes.
The example uses the following ctx.globalCompositeOperation modes
"copy" copies pixels from source to destination including transparent pixels.
"source-over" (used in example draw mode) The default drawing mode. Copies pixels ignoring transparent pixels and blending semi transparent pixels.
"destination-out" (used in example erase mode) Removes pixels from the destination canvas where you draw opaque pixels, and partially removes pixels where you draw semi transparent pixels.
Performance
Even lowend devices can handle many canvas layers easily as long as you ensure that the canvas resolution does not exceed the device display size by many factors as performance is regulated by the availability of GPU RAM
You may be tempted to have the DOM handle the layer composition. It turns out that using the CanvasRenderingContext2D API to do layering is more efficient than letting the DOM handle it
Example
Below is a very basic drawing example. It uses 2 canvas layers, one for the background, and one for the drawing layer.
The background is loaded and then drawn to scale on the bg canvas.
When the mouse button is down the update function draws or erases to/from the drawing layer.
A 3rd canvas is used to show the result. This canvas is added to the DOM and the update function renders the layers to it as needed.
To save the result of the layers you can download the content of the 3rd canvas, or create a new canvas (if the display canvas size does not match the drawing size), draw the layers to it, and download its content.
Useage: Use mouse (left click) to draw / erase on drawing layer. Use button to toggle drawing mode (Draw / Erase)
;(()=>{
setTimeout(start, 0);
var ctx1, ctx2, ctx3;
const SIZE = 180;
const PEN_SIZE = 30;
function start() {
const button = tag("button", {textContent: "Draw", title: "Toggle erase / draw mode", className: "floatBtn"});
const canProps = {width: SIZE, height: SIZE};
ctx1 = tag("canvas", canProps).getContext("2d"); // BG layer
ctx2 = tag("canvas", canProps).getContext("2d"); // drawing layer
ctx3 = tag("canvas", canProps).getContext("2d"); // display canvas context
ctx2.lineWidth = ctx3.lineWidth = PEN_SIZE;
ctx2.lineCap = ctx3.lineCap = "round";
ctx2.lineJoin = ctx3.lineJoin = "round";
ctx2.strokeStyle = ctx3.strokeStyle = "BLUE";
append(BODY, ctx3.canvas, button);
// Load BG image and draw on bg canvas when loaded. Note bg is
// scaled to fit 180 by 180 canvas
const bgImg = new Image;
bgImg.src = "https://i.stack.imgur.com/C7qq2.png?s=256&g=1";
listener(bgImg, "load", () => (ctx1.drawImage(bgImg, 0, 0, 180, 180), mouse.update = true), {once: true});
listener(button, "click", () => {
mouse.draw = !mouse.draw; // Toggle drawing mode
button.textContent = mouse.draw ? "Draw" : "Erase";
});
mouse.update = true;
update();
}
function update() {
requestAnimationFrame(update)
if (!mouse.update) { return }
ctx3.globalCompositeOperation = "copy"; // to draw bg image
ctx3.drawImage(ctx1.canvas, 0 , 0);
if (mouse.lastX !== undefined) { // Avoid line from zero when mouse first over body
ctx3.globalCompositeOperation = "source-over"; // to draw drawing layer
if (mouse.button) { // draw on drawing layer if mouse down
ctx2.globalCompositeOperation = mouse.draw ? "source-over" : "destination-out";
ctx2.beginPath();
ctx2.lineTo(mouse.lastX, mouse.lastY);
ctx2.lineTo(mouse.x, mouse.y + 0.01); // Small 100th px offset
// ensures line is drawn
ctx2.stroke();
}
ctx3.drawImage(ctx2.canvas, 0 , 0);
if (!mouse.button) {
ctx3.strokeStyle = mouse.draw ? "BLUE" : "RED";
ctx3.beginPath();
ctx3.lineTo(mouse.lastX, mouse.lastY);
ctx3.lineTo(mouse.x, mouse.y + 0.01);
ctx3.stroke();
}
mouse.lastX = mouse.x;
mouse.lastY = mouse.y;
}
mouse.update = false;
}
const TAU = Math.PI * 2;
const DOC = document, BODY = DOC.body, assign = Object.assign;
const isArr = Array.isArray;
const tag = (tag, props = {}) => assign(DOC.createElement(tag), props);
const append = (el, ...sibs) => sibs.reduce((p, sib) => ((isArr(sib) ? append(p, ...sib) : p.appendChild(sib)), p), el);
const listener = (qe, name, call, opt = {}) => (qe.addEventListener(name, call, opt), qe);
const mouse = {x: 0, y: 0, button: false, lastX: undefined, lastY: undefined, draw: true, update: true}
function mouseEvents(e) {
mouse.update = true;
mouse.x = e.pageX;
mouse.y = e.pageY;
if (mouse.lastX === undefined) {
mouse.lastX = mouse.x;
mouse.lastY = mouse.y;
}
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
})();
canvas { position: absolute; top: 0px; left: 0px; cursor: crosshair}
.floatBtn { position : absolute; top: 0px; left: 180px; cursor: pointer}
I would like to clip an animated canvas as background to a <h1 />. The requirements are:
Use an actual <h1 /> tag instead of rendered heading in canvas.
Use of images to be rendered with ctx.drawImage().
h1 {
color: transparant;
background-image: <some-canvas>;
background-clip: text;
}
There are several approaches I've tried so far with varying success:
Creating a regular canvas and setting it as background of the <h1 />-tag using -webkit-canvas and -moz-element. This approach ticked all of my requirements but unfortunatly -webkit-canvas was deprecated along with document.getCSSCanvasContext("2d") in Chromium. Safari is the only working browser.
Using the CSS Paint API (Houdini). Using a requestAnimationFrame() to update a css var I can keep ticking the animation and do the animation I would like to implement. However, in Chromium, passing in images has to be done using a workaround (instead of creating a property of type image, images have to be passed in using background-image, making me unable to use the background-image to tell CSS Paint to use my worklet as background. The spec is not entirely implemented.
Creating an inline svg as background-image: url("data:image/svg+xml;utf8,<svg><foreignObject><canvas id='..'/></foreignObject></svg>"); and trying to update that canvas using requestAnimationFrame. Does not work at all.
Are there any other methods I could try?
If it doesn't have to be a background-image, you could put the canvas element inside the h1 element, match the canvas width and height to that of the h1 element, give it a lower z-index than the text in the h1 element so that it appears to be behind the text, acting like a background.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
/*
Set the dimensions of the canvas to
match the parent h1 element
----------------------------------------------------------*/
setCanvasSize();
// (and for demonstrative purposes, scale on window resize)
window.addEventListener('resize', setCanvasSize, false);
function setCanvasSize () {
var _w = canvas.parentNode.clientWidth;
var _h = canvas.parentNode.clientHeight;
canvas.width = _w;
canvas.height = _h;
canvas.style.width = "'" + _w + "px'";
canvas.style.height = "'" + _h + "px'";
}
/*--------------------------------------------------------*/
/*--------------------------------------------------------
All this code below is just a ball animation borrowed from MDN
to illustrate what I'm suggesting.
Source: MDN
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Advanced_animations
*/
var raf;
var running = false;
var ball = {
x: 100,
y: 100,
vx: 5,
vy: 1,
radius: 50,
color: 'blue',
draw: function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
}
};
function clear() {
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.fillRect(0,0,canvas.width,canvas.height);
}
function draw() {
clear();
ball.draw();
ball.x += ball.vx;
ball.y += ball.vy;
if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
ball.vy = -ball.vy;
}
if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
ball.vx = -ball.vx;
}
raf = window.requestAnimationFrame(draw);
}
if (!running) {
raf = window.requestAnimationFrame(draw);
running = true;
}
ball.draw();
/*--------------------------------------------------------*/
h1 {
/* Important for positioning the span element */
position: relative;
/* Cosmetic/demonstrative */
border: 2px solid red;
height: 100%;
text-align: center;
color: red;
font-size: 32px;
font-family: Roboto,sans-serif;
margin: 0 auto;
}
h1 span {
/* Vertically and horizontally center the text */
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
/*
Text will appear above canvas as long as its
z-index > canvas' z-index
*/
z-index: 2;
}
h1 canvas {
/* this will make the canvas appear behind the text */
position: relative;
z-index: 1;
}
<h1>
<span>Lorem Ipsum etc.</span>
<canvas id="canvas"></canvas>
</h1>
Just a thought anyway, maybe you can try it out and expand on the idea to suit your needs. I'm not 100% sure what you're working on so apologies if it's not helpful.
I need to have a cursor to drag and take screenshot of dragged area on HTML webpage. I tried using HTML canvas but it takes screenshot of specific div not the selected region on HTML webpage.
The new html2canvas version 1 has width, height, x and y options.
You can make use of these options to achieve a cropping feature the Firefox's Screenshot's way.
document.onmousedown = startDrag;
document.onmouseup = endDrag;
document.onmousemove = expandDrag;
var dragging = false,
dragStart = {
x: 0,
y: 0
},
dragEnd = {
x: 0,
y: 0
};
function updateDragger() {
dragger.classList.add('visible');
var s = dragger.style;
s.top = Math.min(dragStart.y, dragEnd.y) + 'px';
s.left = Math.min(dragStart.x, dragEnd.x) + 'px';
s.height = Math.abs(dragStart.y - dragEnd.y) + 'px';
s.width = Math.abs(dragStart.x - dragEnd.x) + 'px';
}
function startDrag(evt) {
evt.preventDefault();
dragging = true;
dragStart.x = dragEnd.x = evt.clientX;
dragStart.y = dragEnd.y = evt.clientY;
updateDragger();
}
function expandDrag(evt) {
if (!dragging) return;
dragEnd.x = evt.clientX;
dragEnd.y = evt.clientY;
updateDragger();
}
function endDrag(evt) {
dragging = false;
dragger.classList.remove('visible');
// here is the important part
html2canvas(document.body, {
width: Math.abs(dragStart.x - dragEnd.x),
height: Math.abs(dragStart.y - dragEnd.y),
x: Math.min(dragStart.x, dragEnd.x),
y: Math.min(dragStart.y, dragEnd.y)
})
.then(function(c) {
document.body.appendChild(c);
});
dragStart.x = dragStart.y = dragEnd.x = dragEnd.y = 0;
}
* {
user-select: none;
}
#dragger {
position: fixed;
background: rgba(0, 0, 0, .5);
border: 1px dashed white;
pointer-events: none;
display: none;
}
#dragger.visible {
display: block;
}
canvas {
border: 1px solid;
}
<script src="https://github.com/niklasvh/html2canvas/releases/download/v1.0.0-alpha.1/html2canvas.js"></script>
<div id="wrapper">
<p> Drag to take a screenshot ...</p>
<img crossOrigin src="https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png" width="120" height="120">
</div>
<div id="dragger" tabindex></div>
Why does html5 canvas become very slow when I draw 2000 images and more ?
How can I optimise it?
Here's a demo that I've made, disable the "safe mode" by left clicking on the canvas and start moving your mouse until you get ~2000 images drawn
var img = new Image()
img.src = "http://i.imgur.com/oVOibrL.png";
img.onload = Draw;
var canvas = $("canvas")[0];
var ctx = canvas.getContext("2d")
var cnv = $("canvas");
var draw = [];
$("canvas").mousemove(add)
function add(event) {
draw.push({x: event.clientX, y: event.clientY})
}
canvas.width = cnv.width();
canvas.height = cnv.height();
var safe = true;
cnv.contextmenu(function(e) { e.preventDefault() })
cnv.mousedown(function(event) {
if(event.which == 1) safe = !safe;
if(event.which == 3) draw = []
});
function Draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
requestAnimationFrame(Draw);
for(var i of draw) {
ctx.drawImage(img, i.x, i.y)
}
if(safe && draw.length > 300) draw = []
ctx.fillText("Images count: "+ draw.length,10, 50);
ctx.fillText("Left click to toggle the 300 images limit",10, 70);
ctx.fillText("Right click to clear canvas",10, 90);
}
Draw();
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
position: absolute;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
z-index: 99999999999;
cursor: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas></canvas>
Codepen: http://codepen.io/anon/pen/PpeNme
The simple way with the actual code :
Don't redraw all your images at this rate.
Since in your example, the images are static, you actually don't need to redraw everything every frame : Just draw the latest ones.
Also, if you've got other drawings occurring (e.g your texts), you may want to use an offscreen canvas for only the images, that you'll redraw on the onscreen canvas + other drawings.
var img = new Image()
img.src = "http://i.imgur.com/oVOibrL.png";
img.onload = Draw;
var canvas = $("canvas")[0];
var ctx = canvas.getContext("2d")
var cnv = $("canvas");
var draw = [];
$("canvas").mousemove(add)
function add(event) {
draw.push({
x: event.clientX,
y: event.clientY
})
}
canvas.width = cnv.width();
canvas.height = cnv.height();
// create an offscreen clone of our canvas for the images
var imgCan = canvas.cloneNode();
var imgCtx = imgCan.getContext('2d');
var drawn = 0; // a counter to know how much image we've to draw
var safe = true;
cnv.contextmenu(function(e) {
e.preventDefault()
})
cnv.mousedown(function(event) {
if (event.which == 1) safe = !safe;
if (event.which == 3) draw = []
});
function Draw() {
// clear the visible canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
requestAnimationFrame(Draw);
if (draw.length) { // onmy if we've got some objects to draw
for (drawn; drawn < draw.length; drawn++) { // only the latest ones
let i = draw[drawn];
// draw it on the offscreen canvas
imgCtx.drawImage(img, i.x, i.y)
}
}
// should not be needed anymore but...
if (safe && draw.length > 300) {
draw = [];
drawn = 0; // reset our counter
// clear the offscren canvas
imgCtx.clearRect(0, 0, canvas.width, canvas.height);
}
// draw the offscreen canvas on the visible one
ctx.drawImage(imgCan, 0, 0);
// do the other drawings
ctx.fillText("Images count: " + draw.length, 10, 50);
ctx.fillText("Left click to toggle the 300 images limit", 10, 70);
ctx.fillText("Right click to clear canvas", 10, 90);
}
Draw();
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
position: absolute;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
z-index: 99999999999;
cursor: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas></canvas>
Now, if you need these images to be dynamic (i.e move at every frame), you may consider using imageDatas.
You can see this original post by user #Loktar, which explains how to parse your drawn image imageData, and then redraw it pixel per pixel on the visible canvas' imageData. You can also see this follow-up Q/A, which provides a color implementation of Loktar's idea.
On small images, this actually improves drastically the performances, but it has the huge inconvenient to not support alpha channel multiplication. You will only have fully transparent and fully opaque pixels. An other cons is that it may be harder to implement, but this is just your problem ;-)
I’m trying to ‘merge’ some javascript code that changes pictures with a canvas that has also animation to it, without them cancelling eachother out. In this canvas there are some clouds that move and I want to throw kittens into it.
I understand this this might be a basic HTML5 canvas question but I’m terrible using canvas though. This is the first time I’m really working with it. Whenever I try to implement the code that applies to the kittens, the canvas screen just goes white and nothing shows.
I want to stick with most of the code that I have. I really want to keep the canvas the way it is and change nothing there but add those kittens in there too. Can someone puzzle out for me how to appropiately do this?
I'm guessing I would need to tweak the animation function somehow?
function animate(){
ctx.save();
ctx.clearRect(0, 0, cW, cH);
background.render();
foreground.render();
ctx.restore();
}
var animateInterval = setInterval(animate, 30);
}
The code is all in a FIDDLE, because it’s too much to show it on here.
EDIT: To be clear, I'm asking for the pictures of the kittens to be laid over the canvas, but I want the clouds in the canvas to overlay the kittens.
What you need to know is that the canvas element is transparent by default. So notice the minor change I made here :
body{
background:#667;
}
canvas {
width:50vw;
height:50vh;
margin-left: 20%;
}
#image {
border:#000 1px solid;
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
width: 33%;
height: 100%;
position: relative;
top:140px;
z-index: -1;
}
#my_canvas{
border:#000 1px solid;
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
width: 33%;
height: 100%;
}
<link href="css.css" rel="stylesheet" type="text/css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<img src="" id="image"/>
<script>
var bg = new Image();
bg.src = "http://proverum.ru/templates/oblaka/img/cloud.png";
var fg = new Image();
fg.src = "http://www.tourisme-fumel.com/img_interf/meteo/cloudy.png";
function initCanvas(){
var lastTick = 0;
var position = { x:0, y:0 };
var rain = document.getElementById('rain');
var ctx = document.getElementById('my_canvas').getContext('2d');
var canvas_container = document.getElementById('my_canvas2');
var cW = ctx.canvas.width, cH = ctx.canvas.height;
function Background(){
this.x = 0, this.y = 0, this.w = bg.width, this.h = bg.height;
this.render = function(){
ctx.drawImage(bg, this.x--, 0);
if(this.x <= -250){
this.x = 0;
}
}
}
var background = new Background();
var image1 = "http://www.catsofaustralia.com/images/three_kittens.jpg";
var image2 = "http://thecatpalace.com.au/wp-content/uploads/2015/05/kitten1.jpg";
var image3 = "http://www.keepingkittens.com/images/cute-little-kitten-minka-rose-the-real-cinderella-story-21652512.jpg";
$(function() {
$("#image").prop("src", image1);
setInterval(function() {
$("#image").prop("src", image2);
setTimeout(function() {
$("#image").prop("src", image2);
setTimeout(function() {
$("#image").prop("src", image3);
}, 50);
setTimeout(function() {
$("#image").prop("src", image1);
}, 500);
}, 10);
}, 5000);
});
function Foreground(){
this.x = 0, this.y = 0, this.w = fg.width, this.h = fg.height;
this.render = function(){
ctx.drawImage(fg, this.x--, 0);
if(this.x <= -499){
this.x = 0;
}
}
}
var foreground = new Foreground();
function animate(){
ctx.save();
ctx.clearRect(0, 0, cW, cH);
background.render();
foreground.render();
ctx.restore();
}
var animateInterval = setInterval(animate, 30);
}
window.addEventListener('load', function(event) {
initCanvas();
});
</script>
</head>
<body>
<canvas id="my_canvas" width="611" height="864"></canvas>
<h1 id="status"></h1>
</body>
</html>
I just removed the
background:#FFF;
from the canvas css and the canvas became transparent.
How you will line up the canvas over the kittens is up to you, I just quickly used position:relative and z-index to prove my point.