I'm trying to use offscreen canvas for rendering in webworker, which works perfectly fine in Chrome, but it seems that 2D context is not yet available in Firefox. How do I know that in advance? By the time I get to creating the context, it is already too late and the original canvas is no longer functional for drawing.
Basically what I'm doing
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({canvas: offscreen});
...
// in worker
canvas.getContext("2d") // exception!
// but there's no fallback now, because we already called transferControlToOffscreen
Edit: somehow it didn't occur to me to create another canvas and make it offscreen before "experimenting" with the main ones. That works, but seems like a hack.
The context returned by OffscreenCanvas.getContext('2d') is an OffscreenCanvasRenderingContext2D instance.
Browsers that will support this call, will have this interface available, so all you need is:
const supports = typeof OffscreenCanvasRenderingContext2D === "function";
console.log(`Your browser ${supports ? "supports" : "doesn't support" } the 2d context of the OffscreenCanvas`);
You have to concatenate both getContext and getContext('2d'):
function isCanvasSupported(){
var elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
}
Related
Chrome keep printing this warning: "Canvas2D: Multiple readback operations using getImageData are faster with the willReadFrequently attribute set to true.". I checked the code, where the warning triggers, and you can see I set the willReadFrequently attribute to true. What could be the problem? There was this warning in other places, but there the willReadFrequently attribute solved it.
The problem in Chrome 104-108 exists for sure. Btw, I am in a WebWorker. Could this be a chrome bug?
const offdesireCtx = offDesire.getContext("2d", { willReadFrequently: true });
if (!offdesireCtx) {
throw new Error("Desired OffscrenCanvas ctx undefined");
}
const offGetCtx = offGet.getContext("2d", { willReadFrequently: true });
if (!offGetCtx) {
throw new Error("Get OffscrenCanvas ctx undefined");
}
var imgd = offdesireCtx.getImageData(0, 0, tileSize, tileSize), pix = imgd.data; //Warning triggers
var imgdGet = offGetCtx.getImageData(0, 0, tileSize, tileSize), pixGet = imgdGet.data; //Warning triggers
As MDN says about willReadFrequently:
This will force the use of a software (instead of hardware accelerated) 2D canvas and can save memory when calling getImageData() frequently.
This means the canvas must be created, drawn to, and read from entirely on the CPU. Calling getContext provides a handle to the canvas buffer on the GPU by default, and if that call ever occurs with this canvas earlier on, that data is already on the GPU and it would have to be copied back to the CPU, defeating the goal of the performance warning.
I found in my case, I was creating the canvas and writing to it in one function, then returning the canvas. Later in my code, the result of that function call took the same canvas and created another context. It applied the
{ willReadFrequently: true } options argument to that call, but that was the second time getContext was called for this canvas. That means the texture buffer was already living on the GPU by this point and the second getContext call was ignoring the willReadFrequently suggestion as the data was already on the GPU.
So you need to trace back to where your canvas is first created, and then where its first getContext is called and drawn to. Subsequent canvas.getContext("2d", { willReadFrequently: true }) calls are too late to matter (and are likely even fine to omit the option). Think about when the texture buffer is being created and trace through your data flow to make sure it all lives on the CPU since its inception.
While drawing bitmap image onto off-screen canvas, Chrome console threw an error on me:
Failed to execute 'getContext' on 'OffscreenCanvas': The provided value 'bitmaprenderer' is not a valid enum value of type OffscreenRenderingContextType.
The problem occurs in both worker and window contexts:
var img = new Image(63, 177);
img.src = "https://cdn.sstatic.net/Img/unified/sprites.svg";
img.onload = _ => {
createImageBitmap(img).then(bitmap => {
var canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
canvas.getContext('bitmaprenderer');
}).catch(e => console.error(e.message));
};
I can get this canvas context type in window context while using (on-screen) canvas interface as so:
document.createElement('canvas').getContext('bitmaprenderer');
The canvas context is needed in worker, so I can't use (on-screen) canvas interface, there is no document access after all.
While I can just get two-dimensional rendering context of bitmap image by using '2d' identifier in getContext method, it would be OffscreenCanvasRenderingContext2D and not ImageBitmapRenderingContext. I don't know the difference in latency for both interfaces, but as far as I understand, bitmap can be transferred onto canvas immediately, while 2d context should be drawn.
My Chrome version is 74.0.3729.169, and linked article browser compatibility table stipulates, that bitmap rendering context interface is available from 66 version.
Am I doing something wrong, or is bitmaprenderer not supported as a valid value for off-screen canvas rendering context as of now? How can I access bitmap rendering context for off-screen canvas in Chrome? Thanks in advance.
Yes, ImageBitmapRenderingContext is still not available from an OffscreenCanvas, only webgl[-2] (in FF and Chrome) and 2D (currently in Chrome only) are.
Note that it makes very little sense to make this ImageBitmapRenderingContext available from OffscreenCanvas since this context is generally only used to display ImageBitmap objects. This thus implies that the associated canvas should be visible.
If you wanted to use it for something like being able to draw it to a an other context, then you can directly pass the ImageBitmap to either 2D drawImage or webgl texImage2d.
So the only use case I can see for it would be to use the OffscreenCanvas.convertToBlob() method from inside a Worker in order to convert an ImageBitmap to a BLob. I can understand that devs did set it as low priority, when you can anyway already do the same by other means (even though it might use a bit more memory to achieve it).
Also note that in your case, since you are in the main thread and not in a Worker, you absolutely don't need an OffscreenCanvas and can just do
var img = new Image();
img.crossOrigin = 'anonymous';
img.src = "https://upload.wikimedia.org/wikipedia/commons/a/a4/Fiore_con_petali_arancioni_SVG.svg";
img.onload = _ => {
createImageBitmap(img)
.then(bitmap => {
const canvas = Object.assign(document.createElement('canvas'), {
width: bitmap.width,
height: bitmap.height
});
const ctx = canvas.getContext('bitmaprenderer');
ctx.transferFromImageBitmap(bitmap);
return new Promise((res, rej) => {
canvas.toBlob(blob => {
if (!blob) rej('error');
res(blob);
});
});
})
.then(blob => {
console.log(blob);
result.src = URL.createObjectURL(blob);
})
.catch(e => console.error(e.message));
};
PNG: <img id="result">
I have no idea how to set up cursor image for drawing on the Canvas. I have noticed that I can set it only when
FABRICCANVAS.isDrawingMode = true;
However, the problem is that I have created dedicated drawing tools and I don't want to use those that are built into the Fabric.js.
Sample of my code (which doesn't work properly):
const FABRICCANVAS = new fabric.Canvas('canvas-draft');
const DRAFT = document.querySelector(".upper-canvas");
button.addEventListener('click', () => {
DRAFT.style.cursor = 'url(img/cursors/image.png) 0 34, auto';
});
But when I set isDrawingMode to true, it works. Unfortunately, I don't want to use built-in drawing tools because they leave paths (that can then be moved later, when FABRICCANVAS.selection = true).
Do you know any solution for this problem?
For the Canvas you can set different cursors:
e.g.
canvas.hoverCursor
canvas.defaultCursor
canvas.moveCursor
You can use absolute or relative paths to your cursor image:
canvas.moveCursor = 'url("...") 10 10, crosshair';
I was recently working with <canvas> in JavaScript, and discovered the possibility to create a really bad "memory leak" (more like a memory explosion). When working with the canvas context, you have the ability to do context.save() to add the drawing styles to the "state stack" and context.restore() to remove it. (See the documentation for the rendering context on MDN.)
The problem occurs when you happen to continually save to the state stack without restoring. In Chrome v50 and Firefox v45 this seems to just take up more and more private memory, eventually crashing the browser tab. (Incidentally the JavaScript memory is unaffected in Chrome, so it's very hard to debug this with the profiler/timeline tools.)
My question: How can you clear out or delete the state stack for a canvas context? With a normal array, you would be able to check on the length, trim it with splice or simply reset is back to empty [], but I haven't seen a way to do any of this with the state stack.
[I].. discovered the possibility to create a really bad "memory leak"
This is technically not a memory leak. A leak would be to allocate memory and loose the pointer to it so it could not be freed. In this case the pointer is tracked but the memory not released.
The problem occurs when you happen to continually save to the state stack without restoring.
That is to be expected. Allocating memory without freeing it will accumulate allocated memory blocks.
How can you clear out or delete the state stack for a canvas context?
The only way is to either restore all saved states, or to reset the context by setting some size to the canvas element (ie. canvas.width = canvas.width).
It's also safe to call restore() more times than save() (in which case it just returns without doing anything) so you could in theory run it through a loop of n number of iterations. This latter would be more in the bad practice category though.
But with that being said: if there is a mismatch in numbers of save and restore when it's suppose to be equal, usually indicates a problem somewhere else in the code. Working around the problem with a reset or running multiple restores in post probably will only contribute to cover up the actual problem.
Here's an example on how to track the count of save/restore calls -
// NOTE: this code needs to run before a canvas context is created
CanvasRenderingContext2D.prototype.__save = CanvasRenderingContext2D.prototype.save;
CanvasRenderingContext2D.prototype.__restore = CanvasRenderingContext2D.prototype.restore;
// Our patch vectors
CanvasRenderingContext2D.prototype.__tracker = 0;
CanvasRenderingContext2D.prototype.save = function() {
this.__tracker++;
console.log("Track save:", this.__tracker);
this.__save()
}
CanvasRenderingContext2D.prototype.restore = function() {
this.__tracker--;
console.log("Track restore:", this.__tracker);
this.__restore()
}
// custom method to dump status
CanvasRenderingContext2D.prototype.trackstat = function() {
if (this.__tracker)
console.warn("Track stat:", this.__tracker);
else
console.log("Track stat: OK");
}
var ctx = document.createElement("canvas").getContext("2d");
ctx.save(); // do a couple of save()s
ctx.save();
ctx.restore(); // single restore()
ctx.trackstat(); // should report mismatch of 1
ctx.restore(); // last restore()
ctx.trackstat(); // should report OK
I am working with a GameMaker Studio and exporting a game in HTML5.
A missing feature in GameMaker Studio is the ability to export an image of the screen to the photo album of iOS and Android devices.
My workaround idea is to use a 3rd party plugin for HTML5 that will allow me to save a Canvas.
However GameMaker obfuscates the final output of the javascript and canvas it creates.
All I need to do is find which global variable contains the Canvas and I can capture it.
I have gone through a sample of unobfuscated Javascript but I can't read the code well enough to know which global variable I am supposed to use to call the canvas in question (and I don't want to override the context of it will wipe the canvas).
I found where the Canvas is attached to the context. Can anyone help here?
A sample looks like this:
_j61.prototype._q61 = function () {
_C8 = document.getElementById("canvas").getContext("2d");
if (_C8) {
this._yr = new _e9._B9._ua();
this._yr._Gp(_C8);
this._yr._Jp(1.0 / this._xW);
this._yr._Rp(0.1);
this._yr._Mp(1.0);
this._yr._Bp(_e9._B9._ua._2q | _e9._B9._ua._3q);
this._Fm._xr(this._yr)
}
};
In my function (for example) what goes here?
HELPME.fillStyle = "#FF0000";
HELPME.fillRect(0,0,150,75);