HTML5 canvas: Text in perspective - javascript

I'm trying to create my first game to HTML5. And I search for hours like leaving a text persperctiva (for canvas).
See attached what I need. Are two "points" in text that needs to be modified to the effect that I need.
Image: https://pbs.twimg.com/media/BVbuU1PCUAA7d8a.png
PS: I managed to leave with only the text "rotation" (basic) and that is not right for my purpose.
All topics that I found say in response is not possible.

Canvas's 2d context can't do the non-parallel transforming that is shown in your link.
To do perspective-like warping, you will need to use the canvas 3d context (webGL).
Alternatively, here is a post on how to interpolate pixels from an original triangle into a distorted triangle:
http://codeslashslashcomment.com/2012/12/12/dynamic-image-distortion-html5-canvas/
This will allow you to "manually" do perspective distortions in 2d context.

It doesn't look like there's much perspective involved, so you might get away with a simple skew:
var angle = -0.2;
context.setTransform(1, 0, angle, 1, 0, 0);
context.drawImage(img, 100, 0, 350, 100);
http://jsfiddle.net/fTQcn/

Related

HTML5 Canvas efficiency of panning image by Translate vs. Clipping

There's a bunch of questions on panning a background image in a canvas (i.e. to simulate a 'camera' in a game with the character in the center) - and most answers suggest using the canvas' translate method.
But since you have to re-draw the image in each frame anyway, why not just clip it? Does it matter in terms of efficiency?
In particular, is panning like this a bad idea? (This example shows a simplified pan of the camera moving in one direction)
let drawing = new Image();
drawing.src = "img_src";
drawing.onload = function () {
ctx.drawImage(drawing, 0, 0);
let pos = 0
setInterval(() => {
pos += 1
ctx.clearRect(0, 0, cnvs.width, cnvs.height);
ctx.drawImage(drawing, -pos, 0 ); // "pans" the image to the left using the option `drawImage` parameters `sx` and `sy`
}, 33);
};
Example result: fiddle
Thanks in advance!
The main advantage of using the transform matrix to control your camera is that you don't have to update all the elements in your world, you just move the world instead.
So yes, if you are willing to move only a single element (be it the background like in your case), moving only that one element might be a better choice.
But if you need several layers of elements to all move relatively to the camera, then using the transformation matrix is a better choice.
As for the perfs, I didn't ran any benchmarks on this, but I'd suspect it's exactly the same, though beware when messing with the cropping features of drawImage, at least Safari doesn't handle cropping from outside of a source canvas correctly.

JavaScript Canvas Transform Text

I have the following problem:
I need to transform text on a HTML canvas, e.g. give it a trapezoid shape.
Here's what I tried:
context.beginPath();
context.moveTo(0, 0);
context.lineTo(200, 0);
context.lineTo(100, -100);
context.lineTo(0, -100);
context.closePath();
context.clip();
context.fillText("Hello World", this.x, this.y);
As you might have guessed, the text gets cut off instead of transformed to fit the shape. Below are images of what I am trying to do and what I managed to do.
Any help is appreciated :)
What I managed to do:
What I want to do:
What you can do is get a dummy canvas element in which you put your normal text, then you can apply your own maths to the pixel color values in textCtx.getImageData onto another imageData array that you create, so you can then put that into another canvas with the correct transform with transformedTextCtx.putImageData, and finally draw its canvas onto the original context with ctx.drawImage.
I'm going to assume you know the kind of maths that you want to apply, and if not I suggest you look into skewing and scaling, and find out how you can combine the two.
The mdn resources for the imageData methods are in the "Pixel Manipulation" subsection of the canvas rendering context page: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D#Pixel_manipulation
General Answer: How to transform text
You can transform your text (or anything else) by using canvasContext.setTransform before drawing. You don't need any second canvas.
Examples:
canvasContext.setTransform(1, 0, 0, 1, 0, 0); is the default
canvasContext.setTransform(0.5, 0, 0, 1, 0, 0); reduces width to 50%
canvasContext.setTransform(1, 0, 0, 0.5, 0, 0); reduces height to 50%
Specific Answer:
I couldn't figure this out quickly. I'll leave it up to the next user to figure it out.

Javascript ROY G BIV Line Spectrum?

I'm fairly new to javascript and I want to make a small line spectrum using html canvas and javascript so that I can later animate it when a button is clicked to show absorbtion lines and such just like in chemistry. I'm just not sure if pure javascript is able to handle this. Can anyone provide some information on how this could be done or if this is even possible? I can gladly provide more information if needed.
EDIT: Here is what I have so far but I don't know why the color gradient is slanting where the colors change and the widths of the colors need adjusted so they take up equal amounts along the rectangle.
HTML
<canvas id="spectralCanvas" width="600" height="100"></canvas>
JavaScript
function loadSpectralCanvas() {
var spectralCanvas = document.getElementById('spectralCanvas');
var spectralCtx = spectralCanvas.getContext('2d');
var backgroundCtx = spectralCanvas.getContext('2d');
spectralCtx.rect(0, 0, spectralCanvas.width, spectralCanvas.height / 2);
//Creates black underlay to represent absorption lines on the spectrum.
backgroundCtx.fillStyle = 'black';
backgroundCtx.fillRect(0, 0, spectralCanvas.width, spectralCanvas.height / 2);
//Creates the ROY G BIV line spectrum.
var gradient = spectralCtx.createLinearGradient(0, 0, spectralCanvas.width, spectralCanvas.height / 2);
gradient.addColorStop(0.14, '#882022'); //Red
gradient.addColorStop(0.28, '#E83B23'); //Orange
gradient.addColorStop(0.42, '#FFF101'); //Yellow
gradient.addColorStop(0.56, '#89C540'); //Green
gradient.addColorStop(0.70, '#1BBDC9'); //Blue
gradient.addColorStop(0.84, '#0279B9'); //Indigo
gradient.addColorStop(1.0, '#58235E'); //Violet
spectralCtx.fillStyle = gradient;
spectralCtx.fill();
}
loadSpectralCanvas();
JSFiddle
The last argument to the create gradient function should be 0, as this will produce a horizontal gradient that stretches the width of the canvas.
For your colour stops, you may be better off writing 1/7, 2/7 and so on to let JavaScript handle it for you.
It's definitely possible with pure JavaScript. The Mozilla Developer Network is a great resource to start with. They have a canvas tutorial that should be able to get you started in the right direction. Depending on how much experience you have you might consider going straight to the drawing shapes part of the tutorial. Good luck.

Taking advantage of foreground and background canvases in HTML5

Right now I have one large canvas where everything is drawn. The canvas is created directly in the JS file:
var canvas = document.createElement ("canvas");
var ctx = canvas.getContext("2d");
canvas.width = document.width;
canvas.height = document.height;
document.body.appendChild(canvas);
It resizes itself to the browser window.
I have a gradient background being drawn onto the one canvas along with all the other elements. The colors for the background are randomly generated at each game mode change (eg when the main menu is toggled to the game, then the level end screen, etc). Currently I'm drawing these onto the canvas like this:
var grad1 = ctx.createRadialGradient(canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, 500);
grad1.addColorStop(0, bgGradStop1);
grad1.addColorStop(1, bgGradStop2);
ctx.fillStyle = grad1;
ctx.fillRect(0, 0, canvas.width, canvas.height);
I read numerous times that having a separate canvas for the background is better for performance. Is there anything specific that I need to do to make this effective or can I simply create another canvas and draw the background using the same exact code, only modified to use the other canvas, like this:
var grad1 = ctxBg.createRadialGradient(canvasBg.width / 2, canvasBg.height / 2, 0, canvasBg.width / 2, canvasBg.height / 2, 500);
grad1.addColorStop(0, bgGradStop1);
grad1.addColorStop(1, bgGradStop2);
ctxBg.fillStyle = grad1;
ctxBg.fillRect(0, 0, canvasBg.width, canvasBg.height);
Or does getting any performance benefit involve some totally different method of using the second canvas?
Also, if it really is just a matter of doing the exact same thing but on a different canvas, would there be any benefit to also for example moving the HUD text to its own canvas while other entities are moving? Even separating various types of entities onto their own canvases? How far does the benefit of multiple canvases actually stretch?
Or does getting any performance benefit involve some totally different method of using the second canvas?
You've got the right idea of how to do it, though I don't think a second canvas is necessary in your case.
The important thing from a performance perspective is that you don't want to have to keep constructing and filling the gradient. If all you're doing in your draw loop is:
ctx.fillStyle = grad1;
ctx.fillRect(0, 0, canvas.width, canvas.height);
Then you should be pretty swell here. I don't think having a second canvas in the background will help you much after that. There might be a slight performance boost, but who wants to have to keep track of additional DOM elements if you don't have to?
It's a little hard to test the performance of having a second canvas behind your main one, but instead of having two large canvases in the DOM one similar alternative is to draw the gradient an in-memory canvas and always call ctx.drawImage(inMemCan, 0, 0); instead of setting the gradient and drawing it. Drawing images is known to be fast, and using two canvases this way is probably close to the speed you could expect from two on-page canvases (and hopefully it would be faster).
So we could test drawing the gradient from an in-memory canvas to your main canvas versus drawing the plain old gradient. Here's a simple test with a large canvas:
http://jsperf.com/gradient-vs-immemcan
They're pretty equal it seems. If this one thing is the only thing in your background I wouldn't worry about it. There are probably bigger performance fish for you to fry first.
I'd save the benefit of multiple canvases for when a relatively complicated background updates rarely but independently of the foreground. If your background was instead made with 30 gradients and some paths, then using a second canvas (or an in-memory canvas to cache an image) would give you a sizable boost.
It resizes itself to the browser window.
Just a reminder that the full screen API works pretty well in webkit and firefox if you want to look into it in the future.

Canvas - Fill a rectangle in all areas that are fully transparent

I'm writing a simple 2D game engine using the HTML5 canvas. I've come to adding a lighting engine. Each light source has a radius value and an intensity value (0-1, eg 1 would be very bright). There's also an ambient light value that is used to light everything else in the world that isn't near a light source (0-1, eg 0.1 would be moonlight). The process of lighting is done on a separate canvas above the main canvas:
For each light source, a radial gradient is drawn at that position with the same radius as the light source. The gradient is given two stops: the center is black with an alpha of 1-intensity of the light and the end/edge is black with alpha of 1-ambient light value. That all works fine.
This is where it goes wrong :/ I need to fill the whole canvas with black with and alpha of 1-ambient light value and at the moment I do this by setting the context.globalCompositeOperation to source-out then fillRecting the whole canvas.
My code for this stuff is:
var amb = 'rgba(0,0,0,' + (1-f.ambientLight) + ')';
for(i in f.entities) {
var e = f.entities[i], p = f.toScreenPoint(e.position.x, e.position.y), radius = e.light.radius;
if(radius > 0) {
var g = cxLighting.createRadialGradient(p.x, p.y, 0, p.x, p.y, radius);
g.addColorStop(0, 'rgba(0,0,0,' + (1-e.light.intensity) + ')');
g.addColorStop(1, amb);
cxLighting.fillStyle = g;
cxLighting.beginPath();
cxLighting.arc(p.x, p.y, radius, 0, Math.PI*2, true);
cxLighting.closePath();
cxLighting.fill();
}
}
//Ambient light
cxLighting.globalCompositeOperation = 'source-out';
cxLighting.fillStyle = amb;
cxLighting.fillRect(0, 0, f.width, f.height);
cxLighting.globalCompositeOperation = 'source-over';
However instead of getting what I wan't out of the engine (left) I get a kind of reversed gradient (right). I think this is because when I draw the rectangle with the source-out composite operation it affects the colours of the gradient itself because they are semi-transparent.
Is there a way to do this differently or better? Using clipping maybe, or drawing the rect over everything first?
Also, I modified the Mozila Dev Centre's example on composting to replicate what I need to do and none of the composite modes seemed to work, check that out if it would help.
Thanks very much, any answer would be great :)
One trivial way would be to use imageData but that would be painfully slow. It's an option, but not a good one for a game engine.
Another way would be to think of the ambient light and the light-source as if they were one path. That would make it very easy to do:
http://jsfiddle.net/HADky/
Or see it with an image behind: http://jsfiddle.net/HADky/10/
The thing you're taking advantage of here is the fact that any intersection of a path on canvas is always only unioned and never compounded. So you're using a single gradient brush to draw the whole thing.
But it gets a bit trickier than that if there's more than one light-source. I'm not too sure how to cover that in an efficient way, especially if you plan for two light-sources to intersect.
What you should probably do instead is devise an alpha channel instead of this overlay thing, but I can't currently think of a good way to get it to work. I'll revisit this if I think of anything else.
EDIT: Hey! So I've done a bit of thinking and came up with a good solution.
What you need to do is draw a sort of alpha channel, where the dark spots mark the places where you want light to be. So if you had three light sources it would look like this:
Then you want to set the fill style to your ambient color and set the globalCompositeOperation to xor and xor the whole thing.
ctx.fillStyle = amb;
ctx.globalCompositeOperation = 'xor';
ctx.fillRect(0,0,500,500);
That will leave you with the "opposite" image except the transparent parts will be correctly ambient:
Here's a working example of the code:
http://jsfiddle.net/a2Age/
Extra optimization: You can actually achieve the effect without using any paths at all, by simply drawing the exact same radial gradients onto rects instead of circular paths:
http://jsfiddle.net/a2Age/2/
Hope that helps!
Just an idea, but since you're getting the opposite effect you're going for from your gradient, have you tried reversing the gradient?

Categories

Resources