I am trying to animate rectangles with background image using Raphaël.js, see this demo. While the Rectangle size has been set to 60x60 and also the image size is absolutely 60x60 image is not fitting inside the rectangles!
To me this is just happening when I use the animate() function and without that all images perfectly fit inside the rectangles.
Why is this happening and how I can solve the issue?
var r = Raphael('canvas', 500, 500);
r.canvas.style.backgroundColor = '#ccc';
var st = r.set();
st.push(
r.rect(100,100,60,60),
r.rect(180,100,60,60),
r.rect(100,200,60,60)
);
st.attr({fill:"url(http://cdn2.image-tmart.com/prodimgs/1/13010995/Rose-Bouquet-of-Peach-Heart-Home-Decoration-Simulation-Red-Inside-Pink-Outside_3_60x60.jpg?1363942767)", "stroke-width": 0});
st.animate({transform: "t200,100"}, 500);
In Raphael.js Element.attr({fill: 'url(...)'}) creates a tiled <pattern> to fill in shapes and texts. However, Raphael.js is aimed to be compatible with both SVG and VML (supported by IE 8), so in my opinion it makes some compromises like automatically adjusting the position of <pattern>. Thus, when you translate <rect>, the <pattern> is translated reversely so they look fixed inside the canvas.
Using Paper.image(src, x, y, w, h) is likely to solve your problem, with the same visual behavior. Because <image> coordinate will not be changed implicitly by Raphael.js. Like this:
var r = Raphael('canvas', 500, 500);
r.canvas.style.backgroundColor = '#ccc';
var st = r.set();
st.push(
r.image(null, 100,100,60,60),
r.image(null, 180,100,60,60),
r.image(null, 100,200,60,60)
);
st.attr({src:"http://cdn2.image-tmart.com/prodimgs/1/13010995/Rose-Bouquet-of-Peach-Heart-Home-Decoration-Simulation-Red-Inside-Pink-Outside_3_60x60.jpg?1363942767"});
st.animate({transform: "t200,100"}, 500);
I also recommend Snap.svg, which is from the same author as Raphael.js, but it is for SVG only and has less side effects.
Related
I am trying to create my first website using the p5.js library, with the end goal being an online digital portfolio. I am currently working on a splash screen, in which I have some large title text filling the center of the screen on a simple black background, which actively resizes to fill the window.
I would like to place a simple doodle in the background to add some interest. My challenge is that I would not like this doodle to draw on top of my text, but instead place it underneath my text. Initially I was thinking of infinitely redrawing the text so it stays at the top, however I have deduced there is no way to do this while still animating something beneath it.
My knowledge of HTML / CSS is minimal, however I was thinking of making the background of the title sketch transparent, a separate sketch with the doodle, and use the z index property in CSS to place the doodle beneath the title, is this even possible?
Thanks!
Further edits based on recommendations:
function preload() {
myFont = loadFont('assets/HighTide.otf');
}
function setup() {
canvas = createCanvas(window.innerWidth, window.innerHeight);
title = text("Welcome", width/2, height/2);
background(30);
fsize = window.innerHeight/4;
pg = createGraphics(window.innerWidth, window.innerHeight);
}
function draw() {
background(30);
pg.fill(random(0,255), random(0,255), random(0,255));
//pg.translate(width/2, height/2);
pg.ellipse(random(window.innerWidth), random(window.innerHeight), 60, 60)
image(pg, 0, 0);
textFont(myFont);
textSize(fsize);
textAlign(CENTER);
fill(255);
text("Welcome", width/2, height/2);
}
window.onresize = function() {
var w = window.innerWidth;
var h = window.innerHeight;
canvas.size(w,h);
fsize = window.innerHeight/4;
title.textSize(fsize);
width = w;
height = h;
}
It depends on exactly how you're drawing everything, but if you're doing this all in P5.js then you've already described exactly what you need to do.
Step 1: Each frame, clear out old frames by calling the background() function.
Step 2: Then draw your doodle.
Step 3: Finally, draw your text. Since you're drawing the text after the doodle, it shows up "on top" of the doodle.
This is how most P5.js sketches work: every frame, you clear out the old frames and then draw the next frame.
Edit: If you need a sketch that doesn't clear out old frames but still shows two different layers (your doodle and your text), then what you could do is draw your doodle to a buffer, then draw that buffer each frame, then draw the text on top of the buffer. Check out the createGraphics() function in the reference.
Im using snap.svg an snap.svg.zpd libraries. Same issue I have if I use snap.svg and jQuery panzoom library combination.
Code sample you can find here.
var mySvg = $("#plan")[0];
var snap = Snap("#plan");
//create an image
var imagePlan = snap.image("http://upload.wikimedia.org/wikipedia/commons/4/42/Cathedral_schematic_plan_fr_vectorial.svg", 10, 10, 900, 500);
var group = snap.group(imagePlan);
snap.zpd();
var pt = mySvg.createSVGPoint(); // create the point;
imagePlan.click(function(evt)
{
console.log(evt);
pt.x = evt.x;
pt.y = evt.y;
console.log(mySvg.getScreenCTM().inverse());
//When click, create a rect
var transformed = pt.matrixTransform(mySvg.getScreenCTM().inverse());
var rect1 = snap.rect(transformed.x, transformed.y, 40, 40);
group.add(rect1);
});
Problem is...if you click on initial svg it will add rectangle to the mouse position. If you pan/zoom image and then add rectangle it will be shiffted.
It looks like problem is in method mySvg.getScreenCTM().inverse(). Matrix returned is always same one, panning and zooming does not change it. It always use matrix from initialy rendered svg. However, if I inspect svg element, I can see that pann/zoom change transform matrix directly on element (image below).
Does anybody know how to fix this. My requirement is to be able to drag and drop elements outside svg into svg on any zoom scale or pan context, so I need transformation from mouse click point to svg offset coordinates. If you know any other approach or any other library combination that could done this, it would be ok for me.
Thanks in advance.
Problem is, the transform isn't in mySvg. Its on the 'g' group element thats inside the svg. Zpd will create a group to operate on as far as I know, so you want to look at that.
To hightlight this, take a look at
console.log(mySvg.firstElementChild.getScreenCTM().inverse());
In this case its the g element (there's more direct ways of accessing it, depending on whether you want to just work in js, or snap, or svg.js).
jsfiddle
Its not quite clear from your description where you want the rect (within the svg, separate or whatt) to go and at what scale etc though, and if you want it to be part of the zoom/panning, or static or whatever. So I'm not sure whether you need this or not.
I'm guessing you want something like this
var tpt = pt.matrixTransform( mySvg.firstElementChild.getScreenCTM().inverse() )
var rect1 = snap.rect(tpt.x, tpt.y, 40, 40);
I'm working on a small script that lets a user load a custom image into the canvas on the webpage. So far that works pretty neat. The canvas is initialized using the fabric.js script in order to let the user do some easy editing tasks.
The "uploaded" image is clipped by a simple rectangle. Now comes the tricky part: the user should then be able to move around, scale and rotate the image, whilst the rectangle stays in place; selecting the image section preferred. However, even
lockMovement = true;
or
lockMovementX = true;
lockMovementY = true;
do not keep that clipping mask in place. Any other way to achieve this?
Any help is greatly appreciated! Please find a demo here: http://jsfiddle.net/efmbrm4v/
I had the same problem and I solved it with following code:
image.clipTo = function (ctx) {
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0); // Reset transformation to default for canvas
ctx.rect(
100, 100, // Just x, y position starting from top left corner of canvas
200, 200 // Width and height of clipping rect
);
ctx.restore();
};
You can try it out here: http://jsfiddle.net/Jagi/efmbrm4v/1/
Let's put some text on a HTML5 <canvas> with
var canvas = document.getElementById('myCanvas'),
ctx = canvas.getContext('2d'),
ctx.textBaseline = 'top';
ctx.textAlign = 'left';
ctx.font = '14px sans-serif';
ctx.fillText('Bonjour', 10, 10);
When zooming the canvas on text, one can see pixelation.
Is there a way of zooming on a canvas without having pixelation on text ?
When you fillText on the canvas, it stops being letters and starts being a letter-shaped collection of pixels. When you zoom in on it, the pixels become bigger. That's how a canvas works.
When you want the text to scale as a vector-based font and not as pixels, don't draw them on the canvas. You could create <span> HTML elements instead and place them on top of the canvas using CSS positioning. That way the rendering engine will render the fonts in a higher resolution when you zoom in and they will stay sharp. But anything you draw on the canvas will zoom accordingly.
Alternatively, you could override the browsers zoom feature and create your own zooming algorithm, but this will be some work.
When the user zooms in or out of the window, the window.onresize event handler is triggered. You can use this trigger to adjust the width and the height of the canvas css styling accordingly (not the properties of the canvas. That's the internal rendering resolution. Change the width and height attributes of the style which is the resolution it is scaled to on the website).
Now you effectively disabled the users web browser from resizing the canvas, and also have a place where you can react on the scaling input events. You can use this to adjust the context.scale of your canvas to change the size of everything you draw, including fonts.
Here is an example:
<!DOCTYPE html>
<html>
<head>
<script type="application/javascript">
"use strict"
var canvas;
var context;
function redraw() {
// clears the canvas and draws a text label
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.font = "60pt sans-serif";
context.fillText("Hello World!", 100, 100);
}
function adjustSize() {
var width = window.innerWidth;
var height = window.innerHeight;
// resize the canvas to fill the whole screen
var style = canvas.style;
style.width = width + "px";
style.height = height + "px";
// backup the old current scaling factor
context.save();
// change the scaling according to the new zoom factor
context.scale(1000 / width, 1000 / height);
// redraw the canvas
redraw();
// restore the original scaling (important because multiple calls to scale are relative to the current scale factor)
context.restore();
}
window.onload = function() {
canvas = document.getElementById("myCanvas");
context = canvas.getContext("2d");
adjustSize();
}
window.onresize = adjustSize;
</script>
</head>
<body>
<canvas id ="myCanvas" width = 1000 height = 1000 ></canvas>
</body>
</html>
If you only need to scale text you can simply scale the font size.
A couple of notes on that however: fonts, or typefaces, are not just straight forward to scale meaning you will not get a smooth progress. This is because fonts are often optimized for certain sizes so the sizes in between so to speak are a result of the previous and next size. This can make the font look like it's moving around a little when scaled up and is normal and expected.
The approach here uses a simply size scale. If you need an absolute smooth scale for animation purposes you will have to use a very different technique.
The simple way is:
ctx.font = (fontSize * scale).toFixed(0) + 'px sans-serif';
An online demo here.
For animation purposes you would need to do the following:
Render a bigger size to an off-screen canvas which is then used to draw the different sizes
When the difference is too big and you get problems with interpolation you will have to render several of these cached text images at key sizes so you can switch between them when scaling factor exceeds a certain threshold.
In this demo you can see that at small sizes the pixels gets a bit "clumpy" but otherwise is much smoother than a pure text approach.
This is because the browser uses bi-linear interpolation rather than bi-cubic with canvas (this may or may not change in the future) so it's not able to interpolate properly when the difference gets to big (see below for solution with this issue).
The opposite happens at big sizes as the text gets blurry also due to interpolation.
This is where we would have to switch to a smaller (or bigger) cached version which we then scale within a certain range before we again switch.
The demo is simplified to show only a single cached version. You can see halfway through that this works fine. The principle would be in a full solution (sizes being just examples):
(Update Here is a demo of a switched image during scale).
-- Cached image (100px)
-- Draw cached image above scaled based on zoom between 51-100 pixels
-- Cached image (50px) generated from 100px version / 2
-- Draw cached image above scaled based on zoom between 26-50 pixels
-- Cached image (25px) generated from 50px version / 2
-- Draw cached image above scaled based on zoom between 1-25 pixels
Then use a "sweet spot" (which you find by experiment a little) to toggle between the cached versions before drawing them to screen.
var ctx = canvas.getContext('2d'),
scale = 1, /// initial scale
initialFactor = 6, /// fixed reduction scale of cached image
sweetSpot = 1, /// threshold to switch the cached images
/// create two off-screen canvases
ocanvas = document.createElement('canvas'),
octx = ocanvas.getContext('2d'),
ocanvas2 = document.createElement('canvas'),
octx2 = ocanvas2.getContext('2d');
ocanvas.width = 800;
ocanvas.height = 150;
ocanvas2.width = 400; /// 50% here, but maybe 75% in your case
ocanvas2.height = 75; /// experiment to find ideal size..
/// draw a big version of text to first off-screen canvas
octx.textBaseline = 'top';
octx.font = '140px sans-serif';
octx.fillText('Cached text on canvas', 10, 10);
/// draw a reduced version of that to second (50%)
octx2.drawImage(ocanvas, 0, 0, 400, 75);
Now we only need to check the sweet spot value to find out when to switch between these versions:
function draw() {
/// calc dimensions
var w = ocanvas.width / initialFactor * scale,
h = ocanvas.height / initialFactor * scale;
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (scale >= sweetSpot) {
ctx.drawImage(ocanvas, 10, 10, w, h); /// use cached image 1
} else {
ctx.drawImage(ocanvas2, 10, 10, w, h); /// use cached image 2
}
}
So why not just draw the second cached image with a font? You can do that but then you are back to the issue with fonts being optimized for certain sizes and it would generate a small jump when scaling. If you can live with that then use this as it will provide a little better quality (specially at small sizes). If you need smooth animation you will have to reduce a larger cached version in order to keep the size 100% proportional.
You can see this answer on how to get a large image resized without interpolation problems.
Hope this helps.
I'm writing a 2D game in html5 using Canvas which requires mouse click and hover events to be detected. There are 3 problems with this: detections must be pixel-perfect, objects are not rectangular (houses, weird-shaped UI buttons...), and it is required to be fast and responsive. (Obviously brute force is not an option)
So what I want to ask is how do I find out which object the mouse is on, and what are the possible optimizations.
P.S: I did some investigation and found a guy who used QuadTree here.
I have a (dated) tutorial that explains the concept of a ghost canvas which is decent for pixel-perfect hit detection. The tutorial is here. Ignore the warning about a newer tutorial, the newer one does not use the ghost canvas concept.
The idea is to draw the image in question to an in-memory canvas and then use getImageData to get the single pixel of the mouse click. Then you see if that single pixel is fully transparent or not.
If its not fully transparent, well, you've got your target.
If it is fully transparent, draw the next object to the in-memory canvas and repeat.
You only have to clear the in-memory canvas at the end.
getImageData is slow but it is your only option if you want pixel-perfect hit detection and aren't pre-computing anything.
Alternatively you could precompute a path or else an array of pixels with an offset. This would be a lot of work but might be faster. For instance if you have a 40x20 image with some transparency you'd compute an array[40][20] that would have true or false corresponding to transparent or not. Then you'd test that against the mouse position, with some offset, if the image is drawn at (25, 55) you'd want to subtract that from the mouse position and then test if the new position is true when you look at array[posx][posy].
That's my answer to your question. My Suggestion? Forget pixel-perfect detection if this is a game.
Seriously.
Instead make paths (not in canvas, in plain javascript code) that represent the objects but are not pixel perfect, for instance a house might be a square with a triangle on the top that is a very close approximation of the image but is used in its stead when it comes to hit testing. It is comparatively extremely fast to compute if a point is inside a path than it is to do pixel-perfect detection. Look up point in polygon winding number rule detection. That's your best bet, honestly.
The common solution in traditional game development is to build a click mask. You can re-render everything onto a separate off-screen canvas in a solid color (the rendering should be very quick). When you want to figure out what was clicked on, you simply sample the color at the x/y co-ordinate on the off-screen canvas. You end up building a color-->obj hash, akin to:
var map = {
'#000000' : obj1
, '#000001' : obj2
, ...
};
You can also optimize the rendering to the secondary canvas to only happen when the user clicks on something. And using various techniques, you can further optimize it to only draw the part of the canvas that the user has clicked on (for example, you can split you canvas into an NxN grid, e.g. a grid of 20x20 pixel squares, and flag all of the objects in that square -- you'd then only need to re-draw a small number of objects)
HTML5 Canvas is just a drawing plane, where you can set different transforms before calling each drawing API function. Objects cannot be created and there is no display list. So you have to build these features yourself or you can use different libraries available for this.
http://www.kineticjs.com/
http://easeljs.com/
A few months before I got interested in this and even wrote a library for this purpose. You can see it here : http://exsprite.com. Ended up facing a lot of performance issues, but because of lack of time I couldn't optimize it. It was really interesting, so waiting for some time to make it perfect.
I believe the comments should suffice. This is how I determine user intention in my 2d isometric scroller, currently located at http://untitled.servegame.com
var lastUp = 0;
function mouseUp(){
mousedown = false; //one of my program globals.
var timeNow = new Date().getTime();
if(mouseX == xmouse && mouseY == ymouse && timeNow > lastUp + 100){//if it was a centralized click. (mouseX = click down point, xmouse = mouse's most recent x) and is at least 1/10th of a second after the previous click.
lastUp = new Date().getTime();
var elem = document.elementFromPoint(mouseX, mouseY); //get the element under the mouse.
var url = extractUrl($(elem).css('background-image')); // function I found here: http://webdevel.blogspot.com/2009/07/jquery-quick-tip-extract-css-background.html
imgW = $("#hiddenCanvas").width(); //EVERY art file is 88px wide. thus my canvas element is set to 88px wide.
imgH = $(elem).css('height').split('p')[0]; //But they vary in height. (currently up to 200);
hiddenCanvas.clearRect(0, 0, imgW, imgH); //so only clear what is necessary.
var img = new Image();
img.src = url;
img.onload = function(){
//draw this elements image to the canvas at 0,0
hiddenCanvas.drawImage(img,0,0);
///This computes where the mouse is clicking the element.
var left = $(elem).css('left').split('p')[0]; //get this element's css absolute left.
var top = $(elem).css('top').split('p')[0];
offX = left - offsetLeft; //left minus the game rendering element's absolute left. gives us the element's position relative of document 0,0
offY = top - offsetTop;
offX = mouseX - offX; //apply the difference of the click point's x and y
offY = mouseY - offY;
var imgPixel = hiddenCanvas.getImageData(offX, offY, 1, 1); //Grab that pixel. Start at it's relative X and it's relative Y and only grab one pixel.
var opacity = imgPixel.data[3]; //get the opacity value of this pixel.
if(opacity == 0){//if that pixel is fully transparent
$(elem).hide();
var temp = document.elementFromPoint(mouseX, mouseY); //set the element right under this one
$(elem).show();
elem = temp;
}
//draw a circle on our hiddenCanvas so when it's not hidden we can see it working!
hiddenCanvas.beginPath();
hiddenCanvas.arc(offX, offY, 10, 0, Math.PI*2, true);
hiddenCanvas.closePath();
hiddenCanvas.fill();
$(elem).css("top", "+=1"); //apply something to the final element.
}
}
}
In conjunction with this:
<canvas id="hiddenCanvas" width="88" height="200"></canvas>
Set the CSS positioning absolute and x = -(width) to hide;