I've drawn a grid of rounded rectangles, and I need to fill them with an image background. Eventually I'll have many image backgrounds, but for now, I'm trying to get it to work with one. I'm close, my rectangles draw but the fill does something a little whacky - it overlaps itself and kills my pattern (except on the edges) basically filling the whole canvas with the image. I've tried to 'clip' my path but that just causes only one rectangle to fill. I'm not sure what I'm doing wrong, and I'm hoping a canvas expert can spot it?
/* build rounded rectangle */
var roundedRect=function(ctx,x,y,width,height,radius,fill,stroke)
{
ctx.save(); // save the context so we don't mess up others ctx.beginPath();
// draw top and top right corner ctx.moveTo(x+radius,y);
ctx.arcTo(x+width,y,x+width,y+radius,radius);
// draw right side and bottom right corner
ctx.arcTo(x+width,y+height,x+width-radius,y+height,radius);
// draw bottom and bottom left corner
ctx.arcTo(x,y+height,x,y+height-radius,radius);
// draw left and top left corner
ctx.arcTo(x,y,x+radius,y,radius);
ctx.clip();
if(fill){ ctx.fill(); }
if(stroke){ ctx.stroke(); }
ctx.restore(); // restore context to what it was on entry
}
/* onload, fill canvas with pattern of rounded rectangles separated by 2px */
window.onload = function() {
var canvas = document.getElementById("canvas");
/* rounded filled rectangle pattern */
var canvasWidth=530;
var canvasHeight=530;
var recWidth=42;
var recHeight=42;
var grout=2;
var radius=2;
var cols=canvasWidth/(recWidth+grout);
var rows=canvasWidth/(recHeight+grout);
/* loop through each row/column to build pattern */
alert("rows" + rows + " & cols " + cols);
for (i=1; i<rows; i++){
for (j=1; j<cols; j++){
var ctx = canvas.getContext("2d");
/* fill pattern */
var img=document.getElementById("poppy");
var pat=ctx.createPattern(img,"repeat");
ctx.fillStyle=pat;
roundedRect(ctx,(j*grout + (j-1)*recWidth),(i*grout + (i-1)*recHeight),recWidth,recHeight,radius,true,false);
}
}
};
Let's assume that your problem is reproduced in this JSFiddle: http://jsfiddle.net/hBEwr/. (If this is not the case, the rest of this answer is ~bunk; please edit to match your exact problem.)
If we remove the pattern from the equation altogether, we can see that it is not directly to blame.
http://jsfiddle.net/hBEwr/1/
Instead, the rounded rects are not leaving the grout necessary between them.
Since everything is running over each other, we then change the fill color to a low-opacity blue. Interesting!
http://jsfiddle.net/hBEwr/2/
We see that many of the paths are getting drawn again and again. This insight leads us to look at the implementation of roundedRect() more closely and notice that we never call beginPath(). Indeed, in your code from the question above it seems to have gotten eaten by a comment. Without this call, each invocation of roundedRect() adds additional rectangles to an ever-growing path, instead of starting over.
Adding the call to beginPath() back in, we see that we are on the path to success:
http://jsfiddle.net/hBEwr/3/
Now we can add back in the pattern (with some slight changes) and achieve glorious victory:
http://jsfiddle.net/hBEwr/4/
Some notes on other small tweaks I made:
Make sure you always var your local variables:
for (i=0; i<10; i++) // Oops! i is a global variable
for (var i=0; i<10; i++) // Much better
It's inefficient to re-get the ctx, img, and create the pattern for each tile. In the final code above I've moved these outside the loops for simplicity and speed.
Using console.log instead of alert for debugging is often far easier. (Open the developer console to see the output.)
I added the final outer row/col of grout the the column calculations to ensure that there is space for it to be included. Use or not as you like.
Related
I am trying to animate the drawing of a box that will then fill in when it is complete. I am taking a class in JS so the code has to be JS. The problem is that the right side of the box will not animate correctly. If I put in one set of coordinates for it it animates from the bottom to the top instead of from the corner where the top line started. When I reverse the coordinates it does animate from the proper corner but instead of drawing the line it starts with a solid line and takes away from it, like a disappearing line. Also both the left and right side lines seem to go off the area assigned. For example my area is 600 x 400 and the lines go off the bottom of the page. If I change the dimensions to 600 x 600 the lines still go off the page. The point of this whole project is that we have coded houses using the SVG library and I want to create an animation to make it look as though the house is being drawn before it fills in the colors. This is more for my own knowledge as it is no longer an assignment. There are 2 links to my jsfiddle, the first one is going to be the problem code of drawing the box. The second is what the house that I would like to animate is to look like.
drawing box
"use strict"
var draw = SVG('drawing').size(600, 400);
function makeBox()
{
var line1 = draw.line(25,175,26,175);
line1.stroke({width:1});
line1.animate(4000).size(550);
var line2 = draw.line(575,175,575,176);
line2.stroke({width:1});
line2.animate({duration:4000,delay:4000}).size(200).move(575,375);
var line3 = draw.line(575,375,574,375);
line3.stroke({width:1});
line3.animate({duration:4000,delay:8000}).move(25,375).size(550);
var line4 = draw.line(25,375,25,374);
line4.stroke({width:1});
line4.animate({duration:4000,delay:12000}).size(200).move(25,175);
}
makeBox();
function makeBaseb1(bx,by,c,s)
{
var Baseb1 = draw.rect(550,200).opacity(0).fill(c).stroke();
Baseb1.animate({delay:'16s'}).opacity(1).fill({color:c});
Baseb1.stroke({width:s,color:'black'});
Baseb1.move(bx,by);
}
makeBaseb1(25,175,'#FF9900',1);
house
There are 2 issues with the makeBox function:
The .size method taks 2 arguments instead of 1 (x and y dimension).
For the line drawing in the reverse direction wrt the coordinate system,
both, starting coordinates and line length, need to be animated.
The updated function:
function makeBox() {
var line1 = draw.line(25,175,26,175);
line1.stroke({width:1});
line1.animate(4000).size(550,0);
var line2 = draw.line(575,175,575,176);
line2.stroke({width:1});
line2.animate({duration:4000,delay:4000}).size(0,200);
var line3 = draw.line(574,375,575,375);
line3.stroke({width:1});
line3
.animate({duration:4000,delay:8000})
.during(
function (pos, morph, eased, situation) {
line3.x(morph(574,25));
line3.size(morph(1,550),morph(0,0));
}
)
;
var line4 = draw.line(25,374,25,375);
line4.stroke({width:1});
line4
.animate({duration:4000,delay:12000})
.during(
function (pos, morph, eased, situation) {
line4.y(morph(374,175));
line4.size(morph(0,0),morph(1,200));
}
)
;
}
The use of the .during method is documented here.
I am populating the canvas with small circles to form a shape and would like to destroy some of the circles where ever user clicks on the screen using the following function.
dotsArray : is the array of all circles
dot.ball: is the circle being drawn
bomb: is the circle drawn using user input being taken by the mouse click and hold.The bomb scales up in size as the mouse is pressed
function onMouseDown(event){
bomb.x = event.stageX;
bomb.y = event.stageY;
bomb.active = true;
}
function decimateBalls(){
for (var i = 0; i < dotsArray.length; i++){
for ( var j = 0; j < dotsArray[i].length; j++){
var dot = dotsArray[i][j];
var pt = dot.ball.localToLocal(dot.ball.x, dot.ball.y,bomb);
if(bomb.hitTest(pt.x,pt.y)){
dot.setType("empty"); // this changes dot's circle to white making it seem invisible
}
}
}
}
When I use the above code, even though i calculate the location of the small dots with respect to the bomb circle being drawn, the dots that disappear are the ones that are a lot offset from the bomb circle being drawn.
Am i doing something obviously wrong? Is there a better way to approach this problem?
Thanks a lot for your time.
I used the following work around to get the desired result.
made sure everything is added to stage. got rid of containers.
Use if(bomb.hitTest(dot.ball.x - bomb.x,dot.ball.y - bomb.y)) and got rid of localToLocal() function provided by createjs to find local coordinates of an object with respect to another object.
Hope this helps someone who comes across a similar issue.
I'm still a beginner at javascript, and I'm making a game about dying the whole screen white while the paint brush becomes smaller and smaller until in completely disappears.
I wanted to know, is there a simple way to figure out if the whole canvas has been painted, so I can put a winning screen?
I'm using the processing.js library, here is my code, if it's of any use:
background(255,0,0);
var eight = 100;
var draw = function(){
strokeWeight(eight);
point(mouseX,mouseY);
eight -= 0.2;
if(eight<0){
noStroke();
}
Here's a modestly efficient way of determining if the user has whited every pixel
Create an array where each canvas pixel is represented by an array element.
var pixels=new Array(canvas.width*canvas.height);
Initially fill the array with all zeros.
Create a variable that hold the # of unique pixels whited out so far.
var whited=0;
When the user passes over a pixel, see if the pixel has already been whited. If it hasn't been whited, change its array value to 1 and increment the whited variable.
var n = mouseY * canvas.width + mouseX
if(pixels[n]=0){
pixels[n]=1;
whited++;
}
You have a winner if the value of whited equals the number of pixels on the canvas.
if(whited==pixels.length){
alert('You have won!');
}
A thought: Instead of making the user find every (tiny) missed pixel, you might consider making a grid so the user has an easier time finding that 1 (larger) missed grid cell instead of finding one missed pixel in a sea of white.
You can go over all the pixels and check if they are not white
for (var i=0;i<imgData.data.length;i+=4)
{
if(imgData.data[i]==0&&imgData.data[i+1]==0&&imgData.data[i+2]==0&&imgData.data[i]+3==0){alert("white pixel")}
}
http://www.w3schools.com/tags/canvas_getimagedata.asp
Since you're using Processing, just walk over the pixels:
void setup() {
...
}
void draw() {
...
}
void yourCheckFunction() {
loadPixels();
boolean allWhite = true;
for(int c: pixels) {
if(brightness(c) < 255) {
// we found a not-white pixel!
allWhite = false;
break;
}
}
if (allWhite) {
// the paint surface is entirely white.
} else {
// there are non-white patches left
}
}
There are lots of ways to optimize this (like chopping up the surface into distinct areas with their own administrative true/false value so you can first check if they were all-white on a previous run, and if so, you don't need to recheck them) but this covers the basics:
assume the canvas is all white pixels
try to invalidate that assumption by finding a not-white pixel
immediately stop checking if you do
if there are none, your loop will end "naturally"
Alternatively, you can track how many pixels your user's action have painted. Once that number of pixels is equal to width*height, all pixels must necessarily be white (see markE's answer for that)
Im trying to create an interactive seating layout like this Seats.io. However I dont need the exact features but just few things such as:
Plotting seats anywhere on the screen
Plotting list of seats from one point to another
Seats hover as circle when plotting from one mouse click point to another
After much research in Jquery and simultaneously on raphaeljs, I have decided to start working with raphaeljs. Im totally new to the vector graphics. So obviously there might be something that I may be missing. I have followed this fiddle to draw a straight line. I have also created another script to plot circles anywhere on the window(the circles will mean seats) following is the script
window.onload = function () {
var height = $(document).outerHeight(true);
var width = $(document).width();
var radius = 10;
var paper = Raphael(0, 0, width, height);
var i = 0;
$(document).click(function (e) {
i = i + 1;
var x = e.pageX;
var y = e.pageY;
var seat = paper.circle(x, y, radius)
.attr({stroke: "none", fill: "#f00", opacity: .4})
.data("i", i);
seat.mouseover(function () {
this.attr("opacity", 1);
});
seat.mouseout(function () {
this.attr("opacity", .4);
});
});
}
using the above script I'm able to plot circles(seats) on my screen. Now based on the fiddle example lines are drawn using 'path', so is it possible to load circles on every path and draw them as sequential line of circles one after the other, or do I have to take any different approach.
Also on a side note is there any opensource project or code for the Seats.io
Any help would be really appreciated
Ben from seats.io here.
http://raphaeljs.com/reference.html#Element.getPointAtLength is indeed what we use. You'll basically need to
calculate a helper path between start and end point. You already have that.
calculate the distance between seats (based on seat size): helperPath.getTotalLength() / (numberOfSeats - 1);
for each seat, call getPointAtLength and draw a circle around that
point: helperPath.getPointAtLength(distanceBetweenSeatsOnHelperPath * i++)
Obviously, it gets more interesting if you want to snap to a grid to align rows, curve rows, etc, but you should be able to get started with the above.
Friends, i'm finding rotating a text canvas object a bit tricky. The thing is, I'm drawing a graphic, but sometimes the width of each bar is smaller than the 'value' of that bar. So I have to ratate the 'value' 90 degrees. It will work in most cases.
I am doing the following
a function(x, y, text, maxWidth...)
var context = this.element.getContext('2d');
var metric = context.measureText(text); //metric will receive the measures of the text
if(maxWidth != null){
if(metric.width > maxWidth) context.rotate(Math.PI / 2);
}
context.fillText(text, x, y);
Ok, but it doesn't really work. Problems that I have seen: The text duplicates in different angles. The angles are not what I want (perhaps just a matter of trigonometry).
Well I just don't know what to do. I read something about methods like 'save' and 'restore' but I don't what to do with them. I've made some attempts but no one worked.
Would you help me with this, guys?
This is a bit tricky to answer simply because there are a lot of concepts going on, so I've made you an example of what I think you'd like to do here:
http://jsfiddle.net/5UKE3/
The main part of it is this. I've put in a lot of comments to explain whats going on:
function drawSomeText(x, y, text, maxWidth) {
//metric will receive the measures of the text
var metric = ctx.measureText(text);
console.log(metric.width);
ctx.save(); // this will "save" the normal canvas to return to
if(maxWidth != null && metric.width > maxWidth) {
// These two methods will change EVERYTHING
// drawn on the canvas from this point forward
// Since we only want them to apply to this one fillText,
// we use save and restore before and after
// We want to find the center of the text (or whatever point you want) and rotate about it
var tx = x + (metric.width/2);
var ty = y + 5;
// Translate to near the center to rotate about the center
ctx.translate(tx,ty);
// Then rotate...
ctx.rotate(Math.PI / 2);
// Then translate back to draw in the right place!
ctx.translate(-tx,-ty);
}
ctx.fillText(text, x, y);
ctx.restore(); // This will un-translate and un-rotate the canvas
}
To rotate around the right spot you have to translate to that spot, then rotate, then translate back.
Once you rotate the canvas the context is rotated forever, so in order to stop all your new drawing operations from rotating when you dont want them to, you have to use save and restore to "remember" the normal, unrotated context.
If anything else doesn't make sense let me know. Have a good time making canvas apps!