I basically want to put a canvas on top of another and define the way their contents are blended.
I have one white canvas with black characters on it, and I want to highlight a part of it with a transparent blue rectangle, without having my black characters in the background turning dark blue. In fact, I need the aspect I'd get if I merged the two canvases with globalCompositeOperation set to "multiply" instead of default, while keeping both canvases separated and overlapping.
Here's what I have :
Here's what I want :
I am aware that globalCompositeOperation would allow me to do that if I merged the two canvases into one. But I'd rather keep both canvases : my background canvas is displayed by a lib. I can still draw in it, but that would complicate things a lot:
I'd be too dependent on their logic and would have to tweak mine and theirs to make it work,
performance is critical and this solution would imply much more drawing at 24fps,
I'd struggle every time the lib is updated...
All in all it seems way better to keep away from interfering with the lib. Is there a way to choose how overlapping canvases will behave?
Thanks in advance!
EDIT: We've also thought of transforming the white parts of the background canvas into transparent parts and adding our highlight canvas underneath, but that's also complicated, if not impossible.
Do I understand that the letters canvas the lib draws has an opaque (white) background rather than a transparent one?
If so, any options for applying highlighting will be relatively poor in performance.
Standard canvas compositing won't help as there is no blending mode that combines source-destination pixels.
What you're left with is .getImageData to get the letters pixels. Then apply blue pixels where they overlap white pixels but not where they overlap black(letter) pixels.
However .getImageData is not GPU accelerated and is therefore relatively slow.
Putting the blue highlighting canvas behind a non-opaque letters canvas would give you the best performance.
Bottom line: If performance is critical and you want 24+ fps then hack that library to make the background transparent instead of opaque-white. (sorry!)
Related
I need to set up a clickable image system for dynamically created content. The image consists of a background image, and several grey-scale mask images.
Background Image:
(source: goehler.dk)
Masks:
(source: goehler.dk)
,
(source: goehler.dk)
,
(source: goehler.dk)
,
(source: goehler.dk)
,
(source: goehler.dk)
Each area, defined by a mask, should be highlighted on mouse over, clickable on the image, and open a certain link.
How do I do this the smartest way? I need this to be responsive, and work with a couple of hundred masks.
I haven't tried anything yet, but I've done some research, which have resulted in two possible solutions:
A. Trace the masks, and create imagemap coordinates for each, which can be overlayed the original image. (seems difficult, especially with masks that have holes).
B. Layer all masks on top, and shuffle through them and search for white pixels. (seems processor intensive, shuffling though hundres of images).
I hope however, that there is a third, simpler, more optimized and more elegant solution?
Any advice?
I'd love to hear from anyone who have any experience with something similar.
You should try to precompute as much of this as possible, especially because it's probably not feasible to download hundreds of these mask images in the user's browser.
Your solution A seems like a good way to go, provided it's possible to compute coordinates from the pixel shapes.
Another idea could be combining the mask images in a single image by color-coding the mask shapes (filling each shape with a different color). Colors can be assigned randomly as long as they are used only once. Along with that, provide a simple lookup table for the color-to-shape mapping (e.g. #f00 => cube, #0f0 => donut, ...). Now, when the original image is clicked:
Find the pixel coordinate of the click
Lookup the color in the mask image at the same coordinate
Lookup the shape for the color in the lookup table
First of all, even with 100s of masks, this should not be slow, because the required algorithm has a complexity of O(n) and that is not slow.
The only bottleneck you will have is the pixel lookup, which is an expensive operation (unless you do certain modifications).
I would go with B.
Lets say your mouse coordinates are x:400, y:300, relative to your background image which has the dimensions 800x600.
You would iterate over all masks, and check:
mask.getPixel(400, 300) == white?
If so, use that mask, blend it over the original image with a specific alpha factor so the background get grayed out.
The bottleneck is: mask.getPixel()
You would have to do that n times if you have n masks and its the last one.
As I stated, its an expensive lookup; so can you optimise it?
Yes, cut out unnecessary look-ups by using: bounding boxes.
But to use bounding boxes, you must first create the bounding box data for each mask, which you could do once when you load (no problem).
The bounding box defines the upper left and bottom right corner that "bounds" the white area snugly. In other words, you must determine min and max X & Y coordinate where the pixel is white.
If the mouse coordinates are outside of this box, do not bother making a lookup, as it will certainly not be in the white area.
Edit: Since I was bored, I went ahead and implemented it...
Check it out.
//preProcessMasks() & trackMouse() is where everything happens
Gotto have the background image "img.jpg" and the masks "1.jpg" .. "5.jpg" in the same folder!
Works with firefox, chrome says "The canvas has been tainted by cross-origin data"... its a quick n dirty hack, do whatever you want with it, if its of any use to you!
I'm having trouble understand how DisplacementMapFilter works. Basically, I'm trying to create a revolving planet through a combination of fisheye/masking.
Also, how do I go about doing this via timeline? I'm not too familiar with coding within it, but this is more of an animation project than anything else, so classes are out of the question. Sorry for the lack of code -- I'm simply stuck.
As noted in the comments above, this probably only answers half the problem;
Generating a displacement map image isn't too difficult with the right tools. I'll assume you're using Photoshop, GIMP, Fireworks, or similar.
It's probably best to work on a 128x128 image or smaller with this method. Some editors have more specialised tools which let you work on pretty much any size of image, but this is a generic process that needs no special tools. You can always enlarge the end result, but the quality will begin to go down.
Start with a gradient fill. It should go from pure black on the left to dark red on the right (specifically 128,0,0). Add a vertical fill from black at the top to dark green at the bottom (specifically 0,128,0), and combine them with a LIGHTEN or ADD filter. You should now have an image which has black, red, green and yellow corners. Flatten it.
Copy this image to another layer / whatever the term-of-choice is for your editor. Apply whatever displacement filter you want to it (maybe a fish eye, maybe a manual smudge, maybe a perspective transform, anything)
Add a third layer between the two. Flood-fill it with dark yellow (128,128,0) and set it to ADD / ADDITION blend mode. Set the top layer to SUBTRACT / SUBTRACTION blend mode.
That's it. You should get a mostly yellow image which will function as a displacement map.
Update:
To use this in the example program (http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/filters/DisplacementMapFilter.html#includeExamplesSummary), replace the createBitmapData function with this:
private function createBitmapData():BitmapData {
return myBitmapObject.bitmapData;
}
where myBitmapObject is the instance name (I think) of your displacement Bitmap. There are tidier ways of setting that up, but this is the easiest.
I am slightly confused about the "correct" way in KineticJS to fill a shape with partial images (crops) from a combined image file (sprite).
Seems like one can either use fillPatternImage with a defined offset, which seems to draw the complete image, albeit with the rest of the image invisible. I only got acceptable performance after I moved those shapes to an extra layer as my sprite is relatively large and the impact of not cropping correctly decreased the fps dramatically.
All alternatives that I have found use the attribute "fill" with another attribute "image" in it, but this seems to result in black background every time.
Using an Image-shape would help, but is rarely usable since my shapes are seldom rectangular.
Since the KineticJS-documentation does not mention specifying crop coordinates ("just" offset, w/o width and height), what is the absolute correct way to do it?
The absolute "absolute correct way" would depend on the platform and your particular code, but.
Have you looked at sprites? http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-sprite-tutorial/.
To mask simple animated sprites I'd use this in adition of plain javascript after each draw.
context.globalCompositeOperation = 'destination-in';
The performance of drawImage with composite operations is better than drawing shapes manually on webkit, at least.
I'm trying to animate a circle and just moving it horizontally which works fine. However while the circle is moving, I have to do a clearRect over that circle so that it redraws it self in the horizontal direction. When I do a clearRect it also makes the background have white box around so effectively its going to be one white horizontal line in the direction the circle is moving.
Is there a way to clear the circle without clearRect?
If I have to keep redrawing the background after clearRect the canvas will flicker when theres say 10 circles moving in that area.
Any other approaches to solving this?
function drawcircle() {
clear();
context.beginPath();
context.arc(X, Y, R, 0, 2*Math.PI, false);
context.moveTo(X,Y);
context.lineWidth = 0.3;
context.strokeStyle = "#999999";
context.stroke();
if (X > 200)
{
clearTimeout(t); //stop
}
else
{
//move in x dir
X += dX;
t = setTimeout(drawcircle, 50);
}
}
function clear() {
context.clearRect(X-R, Y-R, 2*R, 2*R);
}
Basics: HTML5 Canvas as a Non-Retained Drawing Mode Graphics API
First, let us discuss the manner in which the HTML5 Canvas works. Like a real-world canvas with fast-drying oil paints, when you stroke() or fill() or drawImage() onto your canvas the paint becomes part of the canvas. Although you drew a 'circle' and see it as such, the pixels of the circle completely replaced the background (or in the case of anti-aliasing at the edges of the circle, blended with and forever changed them). What would Monet say if you asked him to 'move' one of the people in a painting a little bit to the right? You can't move the circle, you can't erase the circle, you can't detect a mouseover of the circle…because there is no circle, there is just a single 2D array of pixels.
Some Options
If your background is fully static, set it as a background image to your canvas element via CSS. This will be displayed and overlaid with content you draw, but will not be cleared when you clear your canvas.
If you cannot do the above, then you might as well just clear the entire canvas and re-paint it every frame. In my tests, the work needed to clear and redraw just a portion of the canvas is not worth the effort unless redrawing the canvas is very expensive.
For example, see this test: http://phrogz.net/tmp/image_move_sprites_canvas.html
In Safari v5.0.4 I see 59.4fps if I clear and re-draw the entire canvas once per frame, and 56.8fps if I use 20 clearRect() calls and 20 drawImage() calls to re-draw just the dirtied part of the background each frame. In this case it's slower to be clever and keep track of small dirty regions.
As another alternative, use a retained-drawing graphics system like SVG or HTML. With these, each element is maintained independently. You can change the position of the item and it will magically move; it is up to the browser to intelligently draw the update in the most efficient manner possible.
You can do this while retaining the power of custom canvas drawing by creating and layering multiple canvases in the same HTML page (using CSS absolute positioning and z-index). As seen in this performance test, moving 20 sprites via CSS is significantly faster than trying to do it all yourself on a single canvas.
Flickering?
You wrote:
If I have to keep redrawing the background after clearRect the canvas will flicker when theres say 10 circles moving in that area.
That has never been my experience. Can you provide a small example showing this 'flicker' problem you claim will occur (please specify OS, browser, and version that you experience this on)? Here are two comments by prominent browser developers noting that neither Firefox nor Safari should ever show any flickering.
This is actually very easy to accomplish by simply positioning more than one canvas on top of each other. You can draw your background on a canvas that is (wait for it...) in the background, and draw your circles on a second canvas that is in the foreground. (i.e. stacked in front of the background canvas)
Multiple canvases is actually one of the best ways to increase performance of anything animation where elements of the final image move independently and do not not necessarily move in every frame. This allows you avoid redrawing items that have not moved in every frame. However, one thing to keep in mind is that changing the relative depth (think z-index) of items drawn on different canvases now requires that the actual <canvas> elements be reordered in the dom. In practice, this is rarely an issue for 2D games and animations.
Contrary to what the accepted answer suggests; yes, you can restore previous draw states, and contrary to what the other answers imply; no, you don't need additional canvases to do so:
The CanvasRenderingContext2D API includes the functions getImageData() and putImageData(). After creating a background image, store the whole thing in a variable const background = context.getImageData(x, y, width, height) (a simple RGBA bitmap of type Uint8ClampedArray), then after wiping the canvas with clearRect() or whatever, restore the background image simply by passing that variable back in the opposite direction: context.putImageData(x, y, background).
There are two ways you can do it that may reduce the flickering, esp if you have many circles.
One is double buffering, and for a brief question on this you can look at:
Does HTML5/Canvas Support Double Buffering?
Basically, you draw on two canvases, and swap them in and out as needed.
This would be the preferable option, esp with many changes per frame, but, the other way I have done this is to just draw over the circle I want to erase, using the background color, then draw with the correct color the new circle.
The only problem is that there is a small chance that you may leave some evidence of the attempted erasing, as it seems that for some shapes it is hard to get it to draw exactly on top.
UPDATE:
Based on a comment you can look at this discussion about double buffering on the canvas:
HTML canvas double buffering frame-rate issues
The basic idea is to keep track of everything you have drawn, with the current position, then on a separate canvas, you redraw everything, then, flip them out, and then I would just redraw again, in the new positions, to ensure that the image looks exactly like it should. Swapping them in and out is a quick operation, the only problem would be if you put event handlers on the canvas, in this case, have them on the div or span surrounding the canvas, so this information doesn't get lost.
Information:
The images have large transparent sections, so each must be overlapped to create the needed effect. Specifically, the clickable portions of each image are in weird trapezoid shapes meant to be pressed up against each other.
Images have image maps with large portions being overlapped by the transparent portions of other nearby (trapezoid) images. I don't expect any change in z indexes will solve this...
Combining the image files into a larger single one to overlay a single image map for each section seems less than ideal, especially since I may need to re-order or rename them later and such. Never mind hover animations and other possibilities down the road.
What would be the best workaround?
Alright, after much tinkering I think I've found a solution: I just took a 1px transparent gif, scaled it up to cover the whole area (with a higher z-index, of course), and then mapped the image map polygons within that. Seems to work.