(function() {(function() {Luv.Graphics.Canvas = Luv.Class('Luv.Graphics.Canvas', {represents a drawing surface, useful for precalculating costly drawing operations or applying effects.
Any Luv instance comes with a default canvas in luv.canvas.
Anything drawn into that canvas is made visible on the screen.
In addition to that, it's possible to create canvases for off-screen image manipulations. This can be done by invoking:
`luv.graphics.Canvas(el) to obtain a canvas attached to a given DOM element. The dimendions will be obtained from the element.
var luv = Luv();
// print on the default canvas (visible
luv.canvas.print("This is print off-screen", 100, 100);
// create an off-screen canvas
var buffer = luv.graphics.Canvas(320,200);
// print on the off-screen canvas
buffer.print("This is print inscreen", 100, 100);
// Draw the off-screen canvas on the screen
luv.canvas.draw(buffer, 200, 500);
The main canvas is cleared at the beginning of each draw cycle, before calling luv.draw()
init: function(width, height) {
var el;
if(width.getAttribute) {
el = width;
width = el.getAttribute('width');
height = el.getAttribute('height');
} else {
el = document.createElement('canvas');
el.setAttribute('width', width);
el.setAttribute('height', height);
}
this.el = el;
this.ctx = el.getContext('2d');
this.color = {};
this.backgroundColor = {};
this.setBackgroundColor(0,0,0);
this.setColor(255,255,255);
this.setLineCap("butt");
this.setLineWidth(1);
this.setImageSmoothing(true);
this.setAlpha(1);
},clear fills the whole canvas with the background color, effectively clearing
up the screen. See setBackgroundColor for details.
clear : function() {
this.ctx.save();
this.ctx.setTransform(1,0,0,1,0,0);
this.ctx.globalAlpha = 1;
this.ctx.fillStyle = this.backgroundColorStyle;
this.ctx.fillRect(0, 0, this.getWidth(), this.getHeight());
this.ctx.restore();
},print is the function that prints text on the screen. It expects a string
and the two coordinates of the upper-left corner from which the text will
be written.
print : function(str,x,y) {
this.ctx.fillStyle = this.colorStyle;
this.ctx.fillText(str, x, y);
},line draws a line using the currently selected color, line width and line cap
(see setColor, setLineWidth and setLineCap for details about those).
On it simplest form, it expects 4 numbers in the form x1,y1,x2,y2. It draws
a line between the points x1,y1 and x2,y2.
It's possible to add more points (x1,y1,x2,y2,x3,y3 ...), in which case line
will draw a line between x1,y1 and x2,y2, then a line between x2,y2 and x3,y3,
and so on.
The coordinate list must be all numbers, and have an even number of elements. It
can also be passed as a JS array (luv.graphics.line(10,20,30,40) draws the same as
luv.graphics.line([10,20,30,40])
line : function() {
var coords = Array.isArray(arguments[0]) ? arguments[0] : arguments;
this.ctx.beginPath();
drawPolyLine(this, 'luv.graphics.line', 4, coords);
drawPath(this, MODE.STROKE);
},strokeRectangle draws a the perimeter of a rectangle using the specified
coordinates in pixels, using the current color, line width and line cap.
strokeRectangle : function(left, top, width, height) { rectangle(this, MODE.STROKE, left, top, width, height); },fillRectangle draws a rectangle filled with the currently selected color.
fillRectangle : function(left, top, width, height) { rectangle(this, MODE.FILL, left, top, width, height); },strokePolygon draws a the perimeter of a polygon using the specified
coordinates in pixels, using the current color, line width and line cap.
The polygon coordinates must be an even number of numbers, in the form
x1,y1,x2,y2,x3,y3....
var luv = Luv();
luv.strokePolygon(0,0, 10,20, 20,0);
You must specify at least three points (6 coordinates) or else the function will fail.
The point coordinates can be specified as plain arguments (as above) or inside an array. This would print the same as in the previous example:
var luv = Luv();
luv.strokePolygon([0,0, 10,20, 20,0]);
strokePolygon : function() { polygon(this, MODE.STROKE, arguments); },fillPolygon takes the same parameters as strokePolygon, but it fills the
polygon with the current color instead of drawing its perimeter.
fillPolygon : function() { polygon(this, MODE.FILL, arguments); },strokeCircle draws the perimeter of a circle using the current line width and color.
It expects the coordinates of the circle's center in pixels, and its radius.
strokeCircle : function(x,y,radius) { circle(this, MODE.STROKE, x,y, radius); },fillCircle works the same way as strokeCircle, but draws a circle filled with
the current color instead of drawing its perimeter.
fillCircle : function(x,y,radius) { circle(this, MODE.FILL, x,y, radius); },strokeArc draws a section of the perimeter of a circle. Takes the same
parameter as strokeCircle, plus the start and end of the angles of the arc,
in radians.
strokeArc : function(x,y,radius, startAngle, endAngle) { arc(this, MODE.STROKE, x,y, radius, startAngle, endAngle); },fillArc draws an "applepie" or a "pacman" filled with the currently selected
color. Takes the same parameters as strokeArc.
fillArc : function(x,y,radius, startAngle, endAngle) { arc(this, MODE.FILL, x,y, radius, startAngle, endAngle); },draw can be used to draw what Luv calls "drawable" objects.
Currently, the following objects are drawable:
Parameters:
drawable is an object implementing the drawable interface (see below). It is the only required param.x and y are the coordinates of the top-left corner where the drawable will be drawn. They default to 0,0.angle is the angle at which the drawable wil be turned, in radians. Defaults to 0.sx and sy are the horizontal and vertical scales. They default to 1,1 (no scale). If you set them to 2,2, then
the drawable will be drawn "double sized". If you set them to 0.5,1, it will have its default height but its width will
be halved.ox and oy are the coordinates of the point used as center of rotation when an angle is specified. By default they are
0,0. The center of rotation is relative to the top-left corner (specified by the x,y params). So if x,y = 10,10 ,
and ox,oy = 5,5, then the rotation will occur around the point in 15,15.You can implement other drawable objects if you want. Drawable objects must implement a draw method with the following signature:
obj.drawInCanvas(canvas, x, y)
Where context is a js canvas 2d context, and x and y are the coordinates of the object's top left corner.
It is also recommended that your drawable objects implement a getCenter function, so they can be used by drawCentered (see
details below)
Note that javascript canvases try to "minimize the amount of pixellation" when doing transformations in images, so they
apply an "image smoothing" algorithm to rotated/translated images. See
setImageSmoothing for more details.
draw : function(drawable, x, y, angle, sx, sy, ox, oy) {
var ctx = this.ctx;
x = x || 0;
y = y || 0;
sx = sx || 1;
sy = sy || 1;
ox = ox || 0;
oy = oy || 0;
angle = normalizeAngle(angle || 0);
if(angle !== 0 || sx !== 1 || sy !== 1 || ox !== 0 || oy !== 0) {
ctx.save();
ctx.translate(x,y);
ctx.translate(ox, oy);
ctx.rotate(angle);
ctx.scale(sx,sy);
ctx.translate(-ox, -oy);
drawable.drawInCanvas(this, 0, 0);
ctx.restore();
} else {
drawable.drawInCanvas(this, x, y);
}
},drawCentered draws a drawable object (images, sprites, animations, canvases ...
see the draw method for more info) but centering it on its center instead of
using the top-left coordinates.
The drawables must implement a method called getCenter() that should return
a JS object with two properties called x and y, representing the geometrical
center of the object.
var c = obj.getCenter();
console.log(c.x, c.y);
Note that the center must be expressed relatively to the top-left corner of the object, not the origin of coordinates.
All drawable objects in Luv also implement a getCenter function.
drawCentered : function(drawable, x,y, angle, sx, sy) {
var c = drawable.getCenter();
this.draw(drawable, x-c.x,y-c.y, angle, sx, sy, c.x, c.y);
},drawInCanvas makes Canvases drawable - it allows you to be able to draw one canvas inside
another canvas
drawInCanvas: function(canvas, x, y) {
canvas.ctx.drawImage(this.el, x, y);
},translate displaces the origin of coordinates x pixels to the right and y down.
This means that it can be used to simulate things like scrolling or camera.
The origin of coordinates is in 0,0 by default.
translate : function(x,y) {
this.ctx.translate(x,y);
},scale sets the world scale on both the x and y axes. 2,2 will make everything look
bigger, and 0.5,0.5 will make everything look half its size; so it can be used for
zooming in an out.
The default scale is 1 in both axes. That means no scale.
scale : function(sx,sy) {
this.ctx.scale(sx,sy);
},rotate transforms the origin of coordinates with an angle (specified in radians).
rotate : function(angle) {
this.ctx.rotate(angle);
},push inserts the current state of the transformation matrix (things like translate, rotate
and scale configuration) in a stack. This means you can add further transformations later on, and
then "come back to the current state" by invoking pop
push : function() {
this.ctx.save();
},pop is the opposite of push: it removes the current transformation settings from the
graphics canvas and replaces it with the one at the top of the stack, which is "popped" out.
pop can be invoked several times, as long as there are transformations left on the stack.
pop : function() {
this.ctx.restore();
},getDimensions returns a JS object containing two components: width and height,
with the width and height of the canvas in pixels.
var luv = Luv();
var d = luv.getDimensions();
console.log(d.width, d.height);
getDimensions : function(){ return { width: this.getWidth(), height: this.getHeight() }; },
setDimensions : function(width, height) {
this.el.setAttribute('width', width);
this.el.setAttribute('height', height);
},getWidth returns the width of the canvas, in pixels.
getWidth : function(){ return Number(this.el.getAttribute('width')); },getHeight returns the height of the canvas, in pixels.
getHeight : function(){ return Number(this.el.getAttribute('height')); },
getCenter : function(){ return { x: this.getWidth()/2, y: this.getHeight() / 2}; },setColor just sets an internal variable with the color to be used for
during the next graphical operations. If you set the color to 255,0,0
(pure red) and then draw a line or a rectangle, they will be red.
Admits the same parameters as parseColor (see below)
setColor : function(r,g,b) { setColor(this, 'color', r,g,b); },getColor returns the currently selected color. See setColor for details.
The current color is returned like a JS object whith the properties
r, g & b, similar to what parseColor returns.
var luv = Luv();
var c = luv.graphics.getColor();
console.log(c.red, c.green, c.blue, c.alpha);
getColor : function() { return getColor(this.color); },setBackgroundColor changes the color used to clear the screen at the beginning
of each frame. It takes the same parameters as setColor.
The default background color is black (0,0,0)
setBackgroundColor : function(r,g,b) { setColor(this, 'backgroundColor', r,g,b); },getBackgroundColor returns the background color the same way as
getColor returns the foreground color. See setBackgroundColor and getColor
for more info.
getBackgroundColor : function() { return getColor(this.backgroundColor); },setAlpha acceps a number from 0 (full transparency) to 1 (full opaqueness).
Call setAlpha before drawing things to alter how transparent they are.
var luv = Luv();
luv.graphics.setAlpha(0.5);
// draw a semi-transparent line
luv.graphics.line(0,0,20,20);
Alpha defaults to 1 (no transparency).
setAlpha: function(alpha) {
this.alpha = clampNumber(alpha, 0, 1);
this.ctx.globalAlpha = this.alpha;
},getAlpha returns the current alpha. See setAlpha for details
getAlpha: function() { return this.alpha; },setLineWidth changes the width of the lines used for drawing lines with the line method,
as well as the various stroke methods (strokeRectangle, strokePolygon, etc). It expects
a number, in pixels. The number must be positive.
setLineWidth : function(width) {
this.lineWidth = width;
this.ctx.lineWidth = width;
},getLineWidth returns the line width, in pixels.
getLineWidth : function() {
return this.lineWidth;
},setLineCap changes the line "endings" when drawing lines. It expects a string.
It can have three values:
"butt": The lines have "no special ending". Lines behave like small oriented rectangles
connecting two coordinates."butt" line cap. As a result, rectangles and squares' corners look "complete".The default value is "butt".
setLineCap : function(cap) {
if(cap != "butt" && cap != "round" && cap != "square") {
throw new Error("Line cap must be either 'butt', 'round' or 'square' (was: " + cap + ")");
}
this.ctx.lineCap = cap;
this.lineCap = this.ctx.lineCap;
},getLineCap returns the line cap as a string. See setLineCap for details.
getLineCap : function() { return this.lineCap; },setImageSmoothing accepts either true or false. It activates or deactivates the image
smoothing algorithms that browsers use in images, particularly when they are rendered in
non-integer locations or with transformations like scales or rotations.
It is true by default.
setImageSmoothing: function(smoothing) {
this.imageSmoothing = smoothing = !!smoothing;
setImageSmoothing(this.ctx, smoothing);
},getImageSmoothing returns whether the graphics have image smoothing active or not, in a boolean.
See setImageSmoothing for a further explanation.
getImageSmoothing: function() {
return this.imageSmoothing;
}
});var twoPI = Math.PI * 2;Internal function used for setting the foreground and background color
var setColor = function(self, name, r,g,b) {
var color = self[name],
newColor = Luv.Graphics.parseColor(r,g,b);
Luv.extend(color, newColor);
self[name + 'Style'] = "rgb(" + [color.r, color.g, color.b].join() + ")";
};
var getColor = function(color) {
return {r: color.r, g: color.g, b: color.b};
};Strokes a polyline given an array of methods.
var drawPolyLine = function(self, methodName, minLength, coords) {
if(coords.length < minLength) { throw new Error(methodName + " requires at least 4 parameters"); }
if(coords.length % 2 == 1) { throw new Error(methodName + " requires an even number of parameters"); }
self.ctx.moveTo(coords[0], coords[1]);
for(var i=2; i<coords.length; i=i+2) {
self.ctx.lineTo(coords[i], coords[i+1]);
}
self.ctx.stroke();
};Given an angle in radians, return an equivalent angle in the [0 - 2*PI) range.
var normalizeAngle = function(angle) {
angle = angle % twoPI;
return angle < 0 ? angle + twoPI : angle;
};This function makes sure that ctx (a 2d canvas context) is configured to have
the same properties as graphics. This makes sure that the graphics instance is the main
"authority". It's called after each canvas is used with setCanvas.
var resetCanvas = function(self, ctx) {
ctx.setTransform(1,0,0,1,0,0);
setImageSmoothing(ctx, self.getImageSmoothing());
ctx.lineWidth = self.getLineWidth();
ctx.lineCap = self.getLineCap();
ctx.globalAlpha = self.getAlpha();
};Image smoothing helper function
var setImageSmoothing = function(ctx, smoothing) {
ctx.webkitImageSmoothingEnabled = smoothing;
ctx.mozImageSmoothingEnabled = smoothing;
ctx.imageSmoothingEnabled = smoothing;
};Internal function by all the primitive drawing functions. It fills or strokes the current path in the current canvas 2d context.
var drawPath = function(self, mode) {
switch(mode){
case MODE.FILL:
self.ctx.fillStyle = self.colorStyle;
self.ctx.fill();
break;
case MODE.STROKE:
self.ctx.strokeStyle = self.colorStyle;
self.ctx.stroke();
break;
default:
throw new Error('Invalid mode: [' + mode + ']. Should be "fill" or "line"');
}
};Rectangle drawing implementation
var rectangle = function(self, mode, left, top, width, height) {
self.ctx.beginPath();
self.ctx.rect(left, top, width, height);
drawPath(self, mode);
self.ctx.closePath();
};Polygon drawing implementation
var polygon = function(self, mode, args) {
var coordinates = Array.isArray(args[0]) ? args[0] : Array.prototype.slice.call(args, 0);
self.ctx.beginPath();
drawPolyLine(self, 'luv.Graphics.Canvas.polygon', 6, coordinates);
drawPath(self, mode);
self.ctx.closePath();
};Arc drawing implementation
var arc = function(self, mode, x,y,radius, startAngle, endAngle) {
self.ctx.beginPath();
self.ctx.arc(x,y,radius, startAngle, endAngle, false);
drawPath(self, mode);
};Circle implementation (mainly it invokes arc)
var circle = function(self, mode, x,y,radius) {
arc(self, mode, x, y, radius, 0, twoPI);
self.ctx.closePath();
};Private "constant" for magic numbers
var MODE = {
STROKE: 1,
FILL : 2
};Internal function. If x < min, return min. If x > max, return max. Otherwise, return x.
var clampNumber = function(x, min, max) {
return Math.max(min, Math.min(max, Number(x)));
};
}());