Assuming that I have a WebGL canvas (by calling getContext("experimental-webgl")).
Is there any way to switch context later for using a "2d" one ?
The goal of such thing would be to display a debug BSOD-like when an error happening during rendering.
If it's not possible, then :
Can I embed an html element over a canvas, and force this element to have exactly the same same that the canvas (even if this last is resized) ?
Can I replace an dom node, and update every reference about the old one to reflect the changement ?
[edit] This is my current minimal call code. Canvas is a DOM node containing a canvas which is filled by WebGL API, and callback is a function which process a single frame.
function failure(cvs, e) {
var ctx = cvs.getContext('2d'); // Fail here, returns `null' if cvs.getContext('webgl') has been called
ctx.fillStyle = 'rgb(0, 0, 0)';
ctx.fillRect(0, 0, cvs.width, cvs.height);
ctx.fillStyle = 'rgb(255, 255, 255)';
ctx.font = 'bold 12px sans-serif';
ctx.fillText(e.toString(), 0, 0);
}
function foobar(canvas, callback) {
try {
callback();
} catch (e) {
failure(canvas, e);
throw e;
} finally {
requestAnimationFrame(arguments.callee);
}
}
The short answer is pretty much no, according to the spec.
Every canvas has what is called a primary context. This is the first context that is invoked on a canvas. Making a non-primary context on a canvas might do some things on different browsers but I would never, ever depend on it.
I would instead have a second canvas that is overlaid over the first and maintains the same width and height attributes. I would then hide one and unhide the other (or just unhide the 2D one when you want it seen).
OR just use a PNG for simplicity's sake., centered inside of a DIV that also holds the canvas. In other words:
Div container has black background and holds:
PNG (centered)
3D Canvas
Then when you want the error png to be displayed you just hide the 3D canvas (and optionally unhide the PNG)
Rather than have two canvases overlaying, the solution I went with was to replace the existing canvas with a clone of itself.
var newCvs = cvs.cloneNode(false);
cvs.parentNode.replaceChild(newCvs, cvs);
cvs = newCvs;
All the properties of the original canvas will be retained but the context will be freed up to allocate as you wish.
Related
What I would like to do is draw a circle on a canvas and then when a function is called delete the previous circle and draw a new one. Is this possible without having to redraw the whole canvas?
Take the code below for example
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(95, 50, 40, 0, 2 * Math.PI);
ctx.stroke();
A canvas is a collection of pixels (it's exactly like an image, however it's computed from code instead of being just loaded from file).
The only way to remove the old circle and draw the new one (if you want pixel perfect result) is repainting everything with the circle in the new position.
A possible solution could be using three distinct canvas elements layered one on top of the other:
whatever is behind the changing parts
the changing parts
whatever is covering the changing parts
1 and 3 can be kept fixed, you need to redraw only the second canvas.
Here's the result I want to realize.As is shown, the QR Code consists of black and white , but I want the black part to be transparent so as to blend with the background outside. The background outside may change, it's not always green.
I've tried to use background blend mode with CSS like this:
background-image: url('');
background-color: rgba(0,0,0,0);
background-blend-mode: screen;
It does nothing.
Thx for help.
You can actually achieve this effect through canvas pixel manipulation, but as other users have stated it would be simpler to make your image transparent through an image editing program.
I have created an example of how to achieve this in the fiddle here.
What this code first does is retrieve the image element <img id="code" src="url.png">, construct a canvas object and draw the contents of the image into the canvas through the context.drawImage call.
// Get the image element.
var el = document.getElementById('code');
// Create a canvas and set its width and height.
var canvas = document.createElement('canvas');
canvas.setAttribute('width', 250);
canvas.setAttribute('height', 250);
// Get the canvas drawing context, and draw the image to it.
var context = canvas.getContext('2d');
context.drawImage(el, 0, 0, canvas.width, canvas.height);
Next,
// Get the canvas image data.
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
// Create a function for preserving a specified colour.
var preserveColor = function(imageData, color) {
// Get the pixel data from the source.
var data = imageData.data;
// Iterate through all the pixels.
for (var i = 0; i < data.length; i += 4) {
// Check if the current pixel should have preserved transparency. This simply compares whether the color we passed in is equivalent to our pixel data.
var preserve = data[i] === color.r
&& data[i + 1] === color.g
&& data[i + 2] === color.b
&& data[i + 3] === color.a;
// Either preserve the initial transparency or set the transparency to 0.
data[i + 3] = preserve ? data[i + 3] : 0;
}
return imageData;
};
// Get the new pixel data and set it to the canvas context.
var newData = preserveColor(imageData, {r: 255, g: 255, b: 255, a: 255});
context.putImageData(newData, 0, 0);
Finally, we add the element to our page using document.body.appendChild(canvas);. You can alternatively completely replace the original image element with the canvas.
Obviously, this isn't an ideal solution because iterating through pixel data can be very slow and only increases with large image sizes. You could also easily change the preserveColor function to instead do the opposite with makeTransparent where the color specified becomes transparent. However, this would require a bit more logic for colors that are similar to the one you specify. Alternatively, you could implement this as a shader so that the processing is more efficient by shifting it to the GPU instead of the CPU.
You have the correct blend mode, and I assume the background-image url is omitted on purpose, so the first reason I would resort to is browser compatibility. For this, take a look at the compatibility chart on caniuse.com, for example.
I'd suggest using a transparent PNG here, as that's exactly what it's indended for. Even if you get background-blend-mode to work, unless the background is perfectly black (meaning #000) - looking at your example image, that isn't the case, so you wouldn't get the effect you want one way or the other.
Another issue I can think of here is that you probably want that QR code as in img element, for the following reasons:
a user may want to save the image in the intended way
background images don't render as block elements, so depending on your layout, they may be easily overlapped
background images often get discarded when printing pages, so they shouldn't be used for content
screen readers will likely ignore backgound images, assuming they're just design
I continue my experiments with HTML5 Canvas and JavaScript. I continue making a simple interactive game for my kids. Here is jsfiddle for my template: jsfiddle.net/LmCwZ/
The question is how I can work with each tab and populate it with a content it separately? Because in some of them I want to insert images and text. Thank you in advance.
You can simply extend the object with more properties to hold your content such as text, images or draw commands etc.
For example, to add text capability:
var Tab = function(id, x, y, width, height, text, color) {
...snipped
this.content = null;
...snipped
and then after creating the tab (or extend its constructor) you define the text:
myTab.content = 'Text for this tab!';
Now in the update method you can render the text of the current tab if there is any:
function showContent(tab) {
ctx.fillStyle = tab.color;
ctx.fillRect(0, tab.height, ctx.canvas.width, 700);
// has text?
if (tab.content !== null) {
ctx.fillStyle = tab.textColor;
ctx.textAlign = 'left';
ctx.font = '40px sans-serif';
ctx.fillText(tab.content, 30, 160);
}
}
Updated fiddle here
The text color, position could just as easily be part of the properties of the object (tab.textX, tab.textY etc.).
With this approach you do not need to worry about indexes (which is a nice alternative though) as everything is self-contained in the object.
Do the same for image but be aware that image loading is asynchronous so it is an advantage to store the image object itself on the tab object rather than an url (or use the constructor phase to pre-load the image).
Also, you will have to manually format text as this is canvas (or use SVG in combination with canvas for more complex stuff as I show in this article). This includes text wrapping, lines etc.
If you need advanced formatting in each tab I would recommend standard HTML and CSS instead of using canvas.
I would like to export my canvas onto a PDF and only render the elements added to the canvas. For example:
I have this canvas, with a background-image set to it.
http://i49.tinypic.com/n7lv.png
This is my result when I render it to PDF (using Bytescout library)
http://i50.tinypic.com/346ud7m.png
This is how I want it to end up as:
http://i50.tinypic.com/2q1s9hv.png
Meaning, I want it to end up with no rounded corners, without the background image. The canvas is done using the fabric framework. My idea is to get all the elements added to the canvas, except background image, then render the PDF from there. Any guidelines? Is that the way to go?
You simply redraw the entire scene, omitting the parts you don't want to write to a PDF.
If you don't feel like keeping track of everything to redraw, then create a second, in-memory canvas (document.createElement('canvas')) and do every drawing operation to that canvas instead of your normal one, then draw that canvas onto your normal one as the user edits instead of drawing directly onto your normal canvas.
The old way:
// First you round the corners permanently by making a clipping region:
ctx.roundedRect(etc)
ctx.clip();
//then a user draws something onto normal canvas, like an image
ctx.drawImage(myImage, 0, 0);
The new way:
// make a hidden canvas:
var hiddenCanvas = document.createElement('canvas');
var hCtx = hiddenCanvas.getContext('2d');
// First you round the corners permanently by making a clipping region:
ctx.roundedRect(etc)
ctx.clip();
//then a user draws something onto HIDDEN canvas, like an image
// This image never gets its corners cut
hCtx.drawImage(myImage, 0, 0);
// Then you draw the hidden canvas onto your normal one:
ctx.drawImage(hiddenCanvas, 0, 0);
When its time to print, you use your hidden canvas, which does not have a background image and does not have clipped corners.
How do I merge a smaller image on top of a larger background image on one canvas. The smaller image will move around. So I will need to keep reference of the original background pixel data, so that the each frame the canvas can be redrawn, with the overlay in its new position.
BgImage: 1280 x 400, overlayImage: 320 x 400, overlayOffsetX: mouseX
I think it is common to draw whole scene each time you want to change something, so:
context.drawImage(bgImage, 0, 0);
context.drawImage(overlayImage, overlayOffsetX, 0);
UPDATE
You could manually compose image data of two images with making copy of background image data
or
do something easier, probably faster. You could create new canvas element (without attaching to the document) which would store image data in easy to manage form. putImageData is good if you want to place rectangular image into the canvas. But if you want to put image with transparency, additional canvas will help. See if example below is satisfying you.
// preparation of canvas containing data of overlayImage
var OVERLAY_IMAGE_WIDTH = 320;
var OVERLAY_IMAGE_Height = 400;
var overlayImageCanvas = document.createElement('canvas');
overlayImageCanvas.width = OVERLAY_IMAGE_WIDTH;
overlayImageCanvas.height = OVERLAY_IMAGE_HEIGHT;
overlayImageCanvas.getContext('2d').putImageData(overlayImage, 0, 0);
// drawing scene, execute this block every time overlayOffsetX has changed
context.putImageData(bgImage, 0, 0);
context.drawImage(overlayImageCanvas, overlayOffsetX, 0);