I’m implementing a color picker. There is problem with the rendering. When I call c.fillRect(0, 0, 100, 80); the size of that rectangle is 103×42 px instead of 100×80. What is wrong here?
Also, rectangles are antialiased. Do I need offset the position by (0.5, 0.5) to avoid AA? I didn’t use any kind of the coordinate system transformations.
colorSlider = function($e, color) {
this._$canvas = $('<canvas></canvas>');
this._c = this._$canvas[0].getContext('2d');
this._color = color || { r: 0, g: 0, b: 0 };
this._$canvas.width('310px');
this._$canvas.height('80px');
$e.append(this._$canvas);
this._render();
var me = this;
this._$canvas.mousedown(function(e) { me._mouseDown.call(me, e) });
this._$canvas.mouseup(function(e) { me._mouseUp.call(me, e) });
this._$canvas.mousemove(function(e) { me._mouseMove.call(me, e) });
this._dragChannel = 0;
}
colorSlider.prototype._pointInRect = function(x, y, rect) {
return x >= rect.x && x <= rect.x + rect.w && y >= rect.y && y <= rect.y + rect.h;
}
colorSlider.prototype._findTarget = function(event) {
var x = event.offsetX;
var y = event.offsetY;
console.log(x, y, this._rectR);
if (this._pointInRect(x, y, this._rectRThumb)) {
return { target: 1, value: x - this._rectR.x };
}
if (this._pointInRect(x, y, this._rectGThumb)) {
return { target: 2, value: x - this._rectG.x };
}
if (this._pointInRect(x, y, this._rectBThumb)) {
return { target: 3, value: x - this._rectB.x };
}
if (this._pointInRect(x, y, this._rectR)) {
return { target: 4, value: x - this._rectR.x };
}
if (this._pointInRect(x, y, this._rectG)) {
return { target: 5, value: x - this._rectG.x };
}
if (this._pointInRect(x, y, this._rectB)) {
return { target: 6, value: x - this._rectB.x };
}
return null;
}
colorSlider.prototype._mouseDown = function(event) {
this._dragChannel = 0;
var target = this._findTarget(event);
if (target) {
switch (target.target) {
case 1:
this._dragChannel = 1;
break;
case 2:
this._dragChannel = 2;
break;
case 3:
this._dragChannel = 3;
break;
case 4:
this._color.r = target.value;
break;
case 5:
this._color.g = target.value;
break;
case 6:
this._color.b = target.value;
break;
}
this._render();
}
};
colorSlider.prototype._mouseUp = function(event) {
//console.log('mouseUp');
};
colorSlider.prototype._mouseMove = function(event) {
//console.log('mouseMove', event);
};
colorSlider.prototype.padding = 4;
colorSlider.prototype._render = function() {
var padding = this.padding;
var thickness = 16;
var c = this._c;
var w = 255;
var h = this._$canvas.height();
c.clearRect(0, 0, this._$canvas.width(), this._$canvas.height());
var gradient = c.createLinearGradient(padding, 0, w, 0);
c.fillStyle = gradient;
gradient.addColorStop(0, this.colorToHex({ r: 0, g: this._color.g, b: this._color.b }));
gradient.addColorStop(1, this.colorToHex({ r: 255, g: this._color.g, b: this._color.b }));
c.fillRect(padding, padding, w, thickness);
c.lineWidth = 0;
c.fillRect(0, 0, 100, 80);
this._rectR = { x: padding, y: padding, w: w, h: thickness };
gradient = c.createLinearGradient(padding, 0, w, 0);
c.fillStyle = gradient;
gradient.addColorStop(0, this.colorToHex({ r: this._color.r, g: 0, b: this._color.b }));
gradient.addColorStop(1, this.colorToHex({ r: this._color.r, g: 255, b: this._color.b }));
c.fillRect(padding, padding + thickness + 2 * padding, w, thickness);
this._rectG = { x: padding, y: padding + thickness + 2 * padding, w: w, h: thickness };
gradient = c.createLinearGradient(padding, 0, w, 0);
c.fillStyle = gradient;
gradient.addColorStop(0, this.colorToHex({ r: this._color.r, g: this._color.g, b: 0 }));
gradient.addColorStop(1, this.colorToHex({ r: this._color.r, g: this._color.g, b: 255 }));
c.fillRect(padding, padding + 2 * (thickness + 2 * padding), w, thickness);
this._rectB = { x: padding, y: padding + 2 * (thickness + 2 * padding), w: w, h: thickness };
c.lineWidth = 2;
c.fillStyle = "white";
c.strokeStyle = "#888888";
this._rectRThumb = { x: padding + this._color.r - 2, y: padding / 2, w: 8, h: 20, r: 2 };
this.drawRoundedRectangle(c, this._rectRThumb);
this._rectGThumb = { x: padding + this._color.g - 2, y: padding / 2 + 2 * padding + thickness, w: 8, h: 20, r: 2 };
this.drawRoundedRectangle(c, this._rectGThumb);
this._rectBThumb = { x: padding + this._color.b - 2, y: padding / 2 + 2 * (2 * padding + thickness), w: 8, h: 20, r: 2 };
this.drawRoundedRectangle(c, this._rectBThumb);
};
colorSlider.prototype.colorToHex = function(color) {
var c = '#'
+ (color.r + 256).toString(16).substr(1, 2)
+ (color.g + 256).toString(16).substr(1, 2)
+ (color.b + 256).toString(16).substr(1, 2);
console.log(c);
return c;
};
// http://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas
colorSlider.prototype.drawRoundedRectangle = function(c, rect) {
var x = rect.x;
var y = rect.y;
var width = rect.w;
var height = rect.h;
var radius = rect.r;
c.beginPath();
c.moveTo(x + radius, y);
c.lineTo(x + width - radius, y);
c.quadraticCurveTo(x + width, y, x + width, y + radius);
c.lineTo(x + width, y + height - radius);
c.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
c.lineTo(x + radius, y + height);
c.quadraticCurveTo(x, y + height, x, y + height - radius);
c.lineTo(x, y + radius);
c.quadraticCurveTo(x, y, x + radius, y);
c.closePath();
c.stroke();
c.fill();
};
index.html
<script>
$(function() {
$("#directionalLight,#ambientLight").each(function() {
new colorSlider($(this));
});
});
</script>
<body>
<div>Directional light</div>
<div id="directionalLight"></div>
<div>Ambient light</div>
<div id="ambientLight"></div>
</body>
The first thing to know is that a
canvaselement has intrinsic dimensions = number of pixels in the inside coordinate space (set by thewidthandheightattributes and properties). It also has extrinsic dimensions (style.widthandstyle.height) which is the number of pixels that the image takes within the webpage. The intrinsic pixels are scaled to fit the extrinsic space.It’s confusing because an
imgalso has intrinsic and extrinsic dimensions, but the names of the properties are completely different fromcanvas. If you setwidthandheighton an image, it’s basically the same as settingstyle.widthorstyle.height; they both set the extrinsic dimensions to scale the image within the page. Meanwhile, you can only get the intrinsic dimensions of animgusing the newnaturalWidthandnaturalHeight(HTML5 browsers only) properties.If the extrinsic dimensions are not set on both
imgandcanvas, the image will be laid out at the same size as the intrinsic dimensions (i.e., scale factor would be 1).Now, when you use jQuery,
$(canvas).width('310px')is the same as$(canvas).css('310px'), which sets the extrinsic dimensions. You have to call$(canvas).prop('width', 310)or simply setcanvas.width = 310to set the intrinsic width.