i'm creating a browser game which is meant to be played as a hologram.
The screen should be displaying something like this:
https://www.youtube.com/watch?v=Y60mfBvXCj8
Therefore i thought i have to create 4 canvas (no problem), but three of them should only display whats happening on the first.
I've tried to let it draw an Image of the canvas and let it display to the other canvas.
Any help would be appreciated!
The game is created with Box2D.
edit:
i want the space ship to be drawn in every canvas, but only controlled in one.
my code: http://s000.tinyupload.com/index.php?file_id=68837773176112789787
the problem is, that its only displaying on one canvas!
what i've put in the HTML:
<canvas id="canvas1" width="500" height="500"></canvas>
<canvas id="canvas2" width="500" height="500"></canvas>
<canvas id="canvas3" width="500" height="500"></canvas>
<canvas id="canvas4" width="500" height="500"></canvas>
what is meant to print it to the others:
JS
var sourceCtx, destinationCtx, imageData;
//get the context of each canvas
sourceCtx = canvas2.getContext('2d');
canvas2Ctx = canvas3.getContext('2d');
//copy the data
imageData = sourceCtx.getImageData(0, 0, canvas2.width - 1, canvas2.height - 1);
//apply the image data
canvas3Ctx.putImageData(imageData, 0, 0);
//done
Holographic pyramid display
How to render for a pyramid reflecting display.
To do this use a single display canvas in the HTML and a canvas stored in memory for rendering.
Mirrored render canvas
The rendering canvas is clipped to a triangle to prevent pixels overlapping and the transform is mirrored so that the final effect is correctly seen. Eg text is back to front.
The offscreen rendering canvas is then rendered to the display canvas, starting at the top and making a total of 4 copies each rotated 90deg.
The rendering canvas width will be the minimum of the display width or height and half that for the height in order to fit the display.
Needs fullscreen mode
For the FX to work you will need to enter fullscreen mode. I have not included how this is done but I am sure there is a QA on stackoverflow that will step you through the process.
Dead zone
At the center of the display is a area on which the pyramid will rest (I call it the dead zone) As many of these displays are homemade the size of the dead zone will vary. In the very first line of the demo below is a constant deadZoneSize that will set the dead zone size. It is currently set at 0.1 which is 10% of the view size. You may need to adjust this value to suit your particular reflecting display.
Example code
The code example is full of comments in the relevant parts. It will create and setup the display canvas and render canvas. Create the clip area and set up the mirrored rendering transform, so you can render as normal. A mainLoop function will call a function called renderContent with the first argument as being the context of the render canvas. Just render your content as normal (use size and hSize for the width and height of the visible render area (maybe I should have used a better name))
The demo includes an example rendering just for the fun of it, that is all at the bottom and has minimum comments as not really relevant to the question.
const deadZoneSize = 0.1; // As fraction of fitted box size
// for FX em and em4 are just custom unit size and 1/4 size
var em,em4;
// to fit all four views use the min width or height
var size = Math.min(innerWidth,innerHeight);
// half size
var hSize = size / 2 | 0;
// there is a small area where nothing should be displayed.
// This will depend on the pyrimide being used.
var deadZone = size * 0.1 | 0; // about 10% of view area
// Display canvas d for display
const dCanvas = document.createElement("canvas");
// Render canvas
const rCanvas = document.createElement("canvas");
// get rendering context for both
const dCtx = dCanvas.getContext("2d");
const rCtx = rCanvas.getContext("2d");
// Set the display canvas to fill the page
Object.assign(dCanvas.style,{
position : "absolute",
zIndex : 10, // place above
top : "0px",
left : "0px",
background : "black",
})
// add the display canvas to the DOM
document.body.appendChild(dCanvas);
//Size function resizes canvases when needed
function resize(){
startTime = undefined;
size = Math.min(innerWidth,innerHeight);
hSize = size / 2 | 0;
deadZone = size * deadZoneSize | 0; // about 10% of view area
dCanvas.width = innerWidth;
dCanvas.height = innerHeight;
rCanvas.width = size;
rCanvas.height = hSize; // half height
em = size * 0.1 | 0; // define our own unit size
em4 = Math.max(1,em * 0.25 | 0); // define quarter unit size min of 1
}
// To ensure pixels do not stray outside the view area and overlap use a clip on the render canvas
// ctx the context to appy the clip path to
function defineClip(ctx){
ctx.beginPath();
ctx.lineTo(0,0);
ctx.lineTo(size,0);
ctx.lineTo(hSize + deadZone, hSize - deadZone);
ctx.lineTo(hSize - deadZone, hSize - deadZone);
ctx.clip();
// The rendering is mirrored from the holo pyramid
// to avoid seeing text mirrored you need to mirror the
// rendering transform
ctx.setTransform(-1,0,0,1,size,0); // x axis from right to left, origin at top right
}
// Copying the rendered canvas to the display canvas
// ctx is the display canvas context
// image is the rendered canvas
function display(ctx,image) {
// for each face of the pyramid render a view
// Each image is just rotated 90 deg
// first clear the canvas
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
// top
// use the center of the display canvas as the origin
ctx.setTransform(1,0,0,1,ctx.canvas.width / 2 | 0, ctx.canvas.height / 2 | 0);
// draw the image
ctx.drawImage(image,-hSize,-hSize);
// Right
ctx.transform(0,1,-1,0,0,0); // rotate 90 deg. This is better than ctx.rotate as it can have slight
// problems due to floating point errors if not done correctly
ctx.drawImage(image,-hSize,-hSize);
// bottom
ctx.transform(0,1,-1,0,0,0);
ctx.drawImage(image,-hSize,-hSize);
// left
ctx.transform(0,1,-1,0,0,0);
ctx.drawImage(image,-hSize,-hSize);
// restore the default transform;
ctx.setTransform(1,0,0,1,0,0);
}
// the main render loop
var globalTime;
var startTime;
function mainLoop(time){
// check canvas size. If not matching page then resize
if(dCanvas.width !== innerWidth || dCanvas.height !== innerHeight) {
resize();
}
if(startTime === undefined){ startTime = time }
globalTime = time - startTime;
// clear the render canvas ready for next render
rCtx.setTransform(1,0,0,1,0,0); // reset transform
rCtx.globalAlpha = 1; // reset alpha
rCtx.clearRect(0,0,size,hSize);
// save the context state so that the clip can be removed
rCtx.save();
defineClip(rCtx); // set the clip
renderContent(rCtx); // call the rendering function
// restore the context state which removes the clip
rCtx.restore();
// rendering is ready for display so render the holo view
// on to the display canvas's context
display(dCtx, rCanvas);
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
//=====================================================================================================
// The following is just something interesting to display and is not directly related to the answer
//=====================================================================================================
// The main rendering function
// This is where you render your content. It can be anything from a game to just plain old text
// You can even use a video element and display a video.
// The rendering context is already set up to correctly mirror the content so just render everything as normal
const randG = (min, max , p = 2) => (max + min) / 2 + (Math.pow(Math.random(), p) * (max - min) * 0.5) * (Math.random() < 0.5 ? 1 : -1);
const bootUp = ["Power On",1,1000,"Sub system test",0.5, 3000, "Calibrating scanner",0.5, 6000, "Welcome",1,8000];
function noisyText(ctx){
var textTime = globalTime / 8000; // 8 second boot up
if(screenFlashDone){
if(globalTime > screenFlashes[0]) { // play screen flash seq
screenFlashes.shift();
screenFlash(ctx,true,screenFlashes.shift(),screenFlashes.shift());
}
}else{
screenFlash(ctx);
}
ctx.font = ((bootUp[1] * em) | 0) + "px monospace";
ctx.textAlign = "center";
ctx.textBaseline = "center";
var tx = randG(-em4 * 4, em4 * 4, 64); // G for kind of a bit like gausian. Last num controls distrubution
var ty = randG(-em4 * 4, em4 * 4, 64);
var xx = size / 2 + tx;
var yy = em * 2 + ty;
ctx.fillStyle = `hsl(${randG(160,250,32)|0},100%,50%)`;
if(bootUp[2] < globalTime){
bootUp.shift();
bootUp.shift();
bootUp.shift();
}
ctx.fillText(bootUp[0], xx, yy);
ctx.save(); // need the normal non mirror transform for the noise FX
ctx.setTransform(1,0,0,1,0,0);
for(var y = -em/1.2|0; y < em/2; y += 1){
if((yy+y) % 3 === 0){
ctx.clearRect(0,yy+y,size,1); // give scan line look
}else{
if(Math.random() < 0.1){ // only on 10% of lines.
ctx.drawImage(ctx.canvas,0,yy + y, size, 2,randG(-em4 * 4,em4 * 4,32),yy + y, size, 2);
}
}
}
ctx.fillRect(0,((globalTime / 4000) * hSize)%hSize,size,2);
ctx.filter = `blur(${randG(em4/2,em4,2)|0}px)`;
ctx.drawImage(ctx.canvas,0,0);
ctx.restore();
}
const screenFlashes = [0,500,3,1000,200,2,4000,100,3,6000,100,1,7500,50,1,7800,50,1, 9000];
var screenFlashStart;
var screenFlashLen;
var screenFlashDone = true;
var screenFlashLayers = 1;
function screenFlash(ctx,start,length,layers){
if(start){
screenFlashStart = globalTime;
screenFlashLen = length;
screenFlashDone = false;
screenFlashLayers = layers;
}
var normTime = (globalTime - screenFlashStart) / screenFlashLen;
if(normTime >= 1){
screenFlashDone = true;
normTime = 1;
}
for(var i = 0; i < screenFlashLayers; i++){
var tx = randG(-em4 * 4, em4 * 4, 64); // G for kind of a bit like gausian. Last num controls distrubution
var ty = randG(-em4 * 4, em4 * 4, 64);
ctx.globalAlpha = (1-normTime) * Math.random();
ctx.fillStyle = `hsl(${randG(160,250,32)|0},100%,50%)`;
ctx.fillRect(tx,ty,size,hSize);
}
ctx.globalAlpha = 1;
}
function randomBlur(ctx) {
ctx.save(); // need the normal non mirror transform for the noise FX
ctx.filter = `blur(${randG(em4/2,em4,2)|0}px)`;
ctx.drawImage(ctx.canvas,0,0);
ctx.restore();
}
function ready(ctx) {
ctx.fillStyle = "#0F0";
ctx.font = em + "px monospace";
ctx.textAlign = "center";
ctx.textBaseline = "center";
ctx.fillText("Holographic",hSize,em);
ctx.font = em/2 + "px monospace";
ctx.fillText("display ready.",hSize,em * 2);
// draw edges
ctx.strokeStyle = "#0F0";
ctx.lineWidth = em4;
ctx.beginPath();
ctx.lineTo(0,0);
ctx.lineTo(size,0);
ctx.lineTo(hSize + deadZone, hSize - deadZone);
ctx.lineTo(hSize - deadZone, hSize - deadZone);
ctx.closePath();
ctx.stroke();
}
function renderContent(ctx){
// all rendering is mirrored, but the transform takes care of that for you
// just render as normal. Remember you can only see the
// triangular area with the wide part at the top
// and narrow at the bottom.
// Anything below hSize - deadZone will also not appear
if(globalTime < 8000){
noisyText(ctx);
randomBlur(ctx);
}else{
ready(ctx);
}
randomBlur(ctx);
}
A quick side note. I feel your question meets the SO requirements and is not off topic, nor are you asking for someone to write the code. You have shown that you have put some effort into research. This question will be of interest to others. I hope this answer helps, good luck in your project and welcome to SO.
I use this code to resize my canvas width and height to the viewport of the browser
function scaleCanvas(){
c.width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
c.height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
drawMenu();
}
It works really great but now I want to put the Coordinates of my objects text etc in relations to the size of the canvas I tried this
// Canvas grösse
c.width = 1280;
c.height = 720;
// Text Schwer
var schwerx = 890;
var schwery = 52;
var schwerw = 100;
var schwerh = 30;
var schwerf = 22;
// Basis Höhe und Breite
var basex = 1280;
var basey = 720;
// Function Schwer
function schwer(){
var rx = schwerx / basex;
var x = rx * c.width;
var ry = schwery / basey;
var y = ry * c.height;
var rw = schwerw / basex;
var w = rw * c.width;
var rh = schwerh / basey;
var h = rh * c.height;
ctx.save();
ctx.rotate(16.3*Math.PI/180);
ctx.font = getFont();
ctx.fillStyle = "#feec47";
ctx.fillText('SCHWER', x, y, w, h);
ctx.restore();
function getFont() {
var ratio = schwerf / basex;
var size = c.width * ratio;
return (size|0) + 'px Pokemon';
}
}
This works great for the font size and on some Width and Heights of the canvas but not on all scales.
You need to start with a base size which is used for calculations, say 1280x720 as in your code.
From there you need two factors, one for horizontal scaling and one for vertical.
Normalize current size using your base size - this will be the scale factors you use to scale the two dimensions:
var factorX = newWidth / baseWidth;
var factorY = newHeight / baseHeight;
You can now scale using one of two approaches: either scale transforming the scale of the context:
ctx.setTransform(factorX, 0, 0, factorY, 0, 0);
or generate new temporary points of the existing points:
var schwerx = 890;
var schwery = 52;
var nSchwerx *= factorX;
var nSchwery *= factorY;
etc...
If can be convenient to store these coordinates in an object or array so they can be processed using a for-loop.
In the case of fonts you will probably use transformation - it's possible to approximate the size without - here is one method which can be used with a bounding box to define the area the text should fit (predefined using the base size and the text/base font. A method to get the height of the font is shown in the same link).
Pros and cons
There are pros and cons with both: transformation matrix makes it easy to scale to any size without doing much with existing coordinates. However, it will also interpolate/resample the graphics so with large factors things will start to look blurry, text may look squeezed etc.
Scaling coordinates (path) will obtain full quality but requires a few more steps as well as being to maintain the original coordinates. It's also more complicated to get text to fit as its size is based on height and width will follow. Width may also be non-linear relative to height depending on the typeface and how it is optimized at the height it's being used, and will therefor require special treatment.
How to draw outer and inner border around any canvas shape?
I'm drawing several stroke-only shapes on an html canvas, and I would like to draw an inner and outer border around them.
draft example:
Is there a generic why to do it for any shape (assuming it's a closed stroke-only shape)?
Two methods
There is no inbuilt way to do this and there are two programmatic ways that I use. The first is complicated and involves expanding and contracting the path then drawing along that path. This works for most situations but will fail in complex situation, and the solution has many variables and options to account for these complications and how to handle them.
The better of the two
The second and easiest way that I present below is by using the ctx.globalCompositeOperation setting to mask out what you want drawn or not. As the stroke is drawn along the center and the fill fills up to the center you can draw the stroke at twice the desired width and then either mask in or mask out the inner or outer part.
This does become problematic when you start to create very complex images as the masking (Global Composite Operation) will interfere with what has already been drawn.
To simplify the process you can create a second canvas the same size as the original as a scratch space. You can then draw the shape on he scratch canvas do the masking and then draw the scratch canvas onto the working one.
Though this method is not as fast as computing the expanded or shrunk path, it does not suffer from the ambiguities faced by moving points in the path. Nor does this method create the lines with the correct line join or mitering for the inside or outside edges, for that you must use a the other method. For most purposes the masking it is a good solution.
Below is a demo of the masking method to draw an inner or outer path. If you modify the mask by including drawing a stroke along with the fill you can also set an offset so that the outline or inline will be offset by a number of pixels. I have left that for you. (hint add stroke and set the line width to twice the offset distance when drawing the mask).
var demo = function(){
/** fullScreenCanvas.js begin **/
var canvas = ( function () {
canvas = document.getElementById("canv");
if(canvas !== null){
document.body.removeChild(canvas);
}
// creates a blank image with 2d context
canvas = document.createElement("canvas");
canvas.id = "canv";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.style.zIndex = 1000;
canvas.ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
return canvas;
})();
var ctx = canvas.ctx;
/** fullScreenCanvas.js end **/
/** CreateImage.js begin **/
// creates a blank image with 2d context
var createImage = function(w,h){
var image = document.createElement("canvas");
image.width = w;
image.height =h;
image.ctx = image.getContext("2d");
return image;
}
/** CreateImage.js end **/
// define a shape for demo
var shape = [0.1,0.1,0.9,0.1,0.5,0.5,0.8,0.9,0.1,0.9];
// draws the shape as a stroke
var strokeShape = function (ctx) {
var w, h, i;
w = canvas.width;
h = canvas.height;
ctx.beginPath();
ctx.moveTo(shape[0] *w, shape[1] *h)
for (i = 2; i < shape.length; i += 2) {
ctx.lineTo(shape[i] * w, shape[i + 1] * h);
}
ctx.closePath();
ctx.stroke();
}
// draws the shape as filled
var fillShape = function (ctx) {
var w, h, i;
w = canvas.width;
h = canvas.height;
ctx.beginPath();
ctx.moveTo(shape[0] * w,shape[1] * h)
for (i = 2; i < shape.length; i += 2) {
ctx.lineTo(shape[i]*w,shape[i+1]*h);
}
ctx.closePath();
ctx.fill();
}
var drawInOutStroke = function(width,style,where){
// clear the workspace
workCtx.ctx.globalCompositeOperation ="source-over";
workCtx.ctx.clearRect(0, 0, workCtx.width, workCtx.height);
// set the width to double
workCtx.ctx.lineWidth = width*2;
workCtx.ctx.strokeStyle = style;
// fill colour does not matter here as its not seen
workCtx.ctx.fillStyle = "white";
// can use any join type
workCtx.ctx.lineJoin = "round";
// draw the shape outline at double width
strokeShape(workCtx.ctx);
// set comp to in.
// in means leave only pixel that are both in the source and destination
if (where.toLowerCase() === "in") {
workCtx.ctx.globalCompositeOperation ="destination-in";
} else {
// out means only pixels on the destination that are not part of the source
workCtx.ctx.globalCompositeOperation ="destination-out";
}
fillShape(workCtx.ctx);
ctx.drawImage(workCtx, 0, 0);
}
// clear in case of resize
ctx.globalCompositeOperation ="source-over";
ctx.clearRect(0,0,canvas.width,canvas.height);
// create the workspace canvas
var workCtx = createImage(canvas.width, canvas.height);
// draw the outer stroke
drawInOutStroke((canvas.width + canvas.height) / 45, "black", "out");
// draw the inner stroke
drawInOutStroke((canvas.width + canvas.height) / 45, "red", "in");
// draw the shape outline just to highlight the effect
ctx.strokeStyle = "white";
ctx.lineJoin = "round";
ctx.lineWidth = (canvas.width + canvas.height) / 140;
strokeShape(ctx);
};
// run the demo
demo();
// incase fullscreen redraw it all
window.addEventListener("resize",demo)
I know how to get this height of a font:
By placing the text in a div and getting offset height of the div.
But I would like to get this actual height (Which will depend on font family):
Is that in any way possible using web based programming?
Is there a simple solution? I think the answer is no.
If you're ok with a more involved (and processor-intensive) solution, you could try this:
Render the text to a canvas, then use canvasCtx.getImageData(..) to retrieve pixel information. Next you would do something similar to what this pseudo code describes:
first_y : null
last_y : null
for each y:
for each x:
if imageData[x][y] is black:
if first_y is null:
first_y = y
last_y = y
height = last_y - first_y
This basically looks for the top (lowest y-index) of the lettering (black pixels) and the bottom (highest y-index) then subtracts to retrieve the height.
I was writing the code while Jason answered, but I decided to post it anyway:
http://jsfiddle.net/adtn8/2/
If you follow the comments you should get the idea what's going on and why. It works pretty fast and it's not so complicated as it may sound. Checked with GIMP and it is accurate.
(code to be sure it wont be lost):
// setup variables
var c = document.createElement('canvas'),
div = document.getElementsByTagName('div')[0],
out = document.getElementsByTagName('output')[0];
// set canvas's size to be equal with div
c.width = div.offsetWidth;
c.height = div.offsetHeight;
var ctx = c.getContext('2d');
// get div's font from computed style and apply it to context
ctx.font = window.getComputedStyle(div).font;
// use color other than black because all pixels are 0 when black and transparent
ctx.fillStyle = '#bbb';
// draw the text near the bottom of the canvas
ctx.fillText(div.innerText, 0, div.offsetHeight);
// loop trough the canvas' data to find first colored pixel
var data = ctx.getImageData(0, 0, c.width, c.height).data,
minY = 0, len = data.length;
for (var i = 0; i < len; i += 4) {
// when you found it
if (data[i] != 0) {
// get pixel's y position
minY = Math.floor(i / 4 / c.width);
break;
}
}
// and print out the results
out.innerText = c.height - minY + 'px';
EDIT:
I even made jQuery plugin for this: https://github.com/maciek134/jquery-textHeight
Enjoy.
I managed to draw a line on a canvas using html5:
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
This works. I now want to "annotate" the line with text. So basically, I want there to be custom (e.g. whatever I pass in) text appearing along the length of the line. The difficulty is that the line can appear in any orientation (e.g. have any slope) so the text needs to be oriented accordingly. Any ideas how to start?
I have created an example of this on my website. In general, you want to:
translate the context to the anchor point of the text, then
rotate the context by the amount (in radians) you desire, and then
fillText as normal.
I have included the relevant portion of my example below; I leave it as an exercise to the reader to detect when the text is upside down and handle it as desired.
Edit: view the source on my site for additional code that keeps the text upright and also auto-truncates it.
function drawLabel( ctx, text, p1, p2, alignment, padding ){
if (!alignment) alignment = 'center';
if (!padding) padding = 0;
var dx = p2.x - p1.x;
var dy = p2.y - p1.y;
var p, pad;
if (alignment=='center'){
p = p1;
pad = 1/2;
} else {
var left = alignment=='left';
p = left ? p1 : p2;
pad = padding / Math.sqrt(dx*dx+dy*dy) * (left ? 1 : -1);
}
ctx.save();
ctx.textAlign = alignment;
ctx.translate(p.x+dx*pad,p.y+dy*pad);
ctx.rotate(Math.atan2(dy,dx));
ctx.fillText(text,0,0);
ctx.restore();
}
For Firefox only you also have the option of using mozTextAlongPath. (Deprecated)
I used it and it worked =) I just changed something so that when I make the node spin, the label is always in a good position to be read:
In my redraw function I put something like this:
particleSystem.eachEdge(function(edge, pt1, pt2){
// edge: {source:Node, target:Node, length:#, data:{}}
// pt1: {x:#, y:#} source position in screen coords
// pt2: {x:#, y:#} target position in screen coords
// draw a line from pt1 to pt2
var dx = (pt2.x - pt1.x);
var dy = (pt2.y - pt1.y);
var p, pad;
var alignment = "center";
//ctx.label(edge.data.role,dx,dy,5,90,14);
ctx.strokeStyle = "rgba(0,0,0, .333)";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(pt1.x, pt1.y);
ctx.lineTo(pt2.x, pt2.y);
ctx.stroke();
p = pt1;
pad = 1/2;
ctx.save();
ctx.textAlign = alignment;
ctx.translate(p.x+dx*pad,p.y+dy*pad);
if(dx < 0)
{
ctx.rotate(Math.atan2(dy,dx) - Math.PI); //to avoid label upside down
}
else
{
ctx.rotate(Math.atan2(dy,dx));
}
ctx.fillStyle = "black"
ctx.fillText(edge.data.role,0,0);
ctx.restore();
})
Thanks,
Dámaris.