I'm trying to figure out how to reproduce the behavior of the "border-radius" css property into an HTML canvas.
So i've already done something in Javascript in order to compute the correct borders of a given shape, using a specific radius (for each corner).
Here is the previous question if needed : Gecko - CSS layout border radius - Javascript conversion
I've managed to get close from the browser adaptation but there's still an issue, and it seems that it's the last, and the hard part!
Let's take an example in order to explain you the problem
Take a shape of 100px
width & 100px
height. Now apply the following radius to each corner :
- Top left : 37px
- Top right : 100px
- Bottom right : 1px
- Bottom left : 100px
So the css style for this will be border-radius : 37px 100px 1px 100px
Now let's consider using the code below in order to draw this shape in a canvas.
By using the function correctRadius(r, w, h)
in order to avoid bad shapes, each corners will be computed like that :
=> {tl: 18.5, tr: 81.5, br: 0.5, bl: 81.5}
Here is a visual of this :
You will be able to test it in the following snippet
As you can see, the browser shape (green) is overlapped with the canvas shape (brown + 'pink' due to opacity). I've put some opacity on it in order to check the bad corners (pink).
The brown shape do not fit properly into the green shape, the top left corner is getting out of the base, and the bottom left and top right corners don't fit the green shape.
I've already tried to fix that but with no success, and also looked the sources of the Gecko layout engine (https://github.com/mozilla/gecko-dev) in this file : layout/painting/nsCSSRenderingBorders.cpp
, but haven't found anything + i have no skills in C++
If anyone could help me to figure this out, or give me some advice, i'll be able to fix this and get the borders working
// Ctx
var ctx = document.getElementById("rounded-rect").getContext("2d");
ctx.translate(0, 0);
function correctRadius(r, w, h) {
var tl = r.tl;
var tr = r.tr;
var br = r.br;
var bl = r.bl;
r.tl -= Math.max(Math.max((tl + tr - w) / 2, 0),
Math.max((tl + bl - h) / 2, 0));
r.tr -= Math.max(Math.max((tr + tl - w) / 2, 0),
Math.max((tr + br - h) / 2, 0));
r.br -= Math.max(Math.max((br + bl - w) / 2, 0),
Math.max((br + tr - h) / 2, 0));
r.bl -= Math.max(Math.max((bl + br - w) / 2, 0),
Math.max((bl + tl - h) / 2, 0));
}
//Round rect func
ctx.constructor.prototype.fillRoundedRect =
function(xx, yy, ww, hh, rad, fill, stroke) {
correctRadius(rad, ww, hh);
if (typeof(rad) === "undefined") rad = 5;
this.beginPath();
this.moveTo(xx, yy);
this.arcTo(xx + ww, yy, xx + ww, yy + hh, rad.tr);
this.arcTo(xx + ww, yy + hh, xx, yy + hh, rad.br);
this.arcTo(xx, yy + hh, xx, yy, rad.bl);
this.arcTo(xx, yy, xx + ww, yy, rad.tl);
if (stroke) this.stroke(); // Default to no stroke
if (fill || typeof(fill) === "undefined") this.fill(); // Default to fill
};
ctx.fillStyle = "red";
ctx.strokeStyle = "#ddf";
var copy = document.getElementById('copy');
var tl = document.getElementById('tl');
var tr = document.getElementById('tr');
var bl = document.getElementById('bl');
var br = document.getElementById('br');
var last = [];
setInterval(function() {
/* 1.Top left */
/* 2. Top right */
/* 3. Bottom right */
/* 4. Bottom left */
var bordersCSSProps = [
"border-top-left-radius",
"border-top-right-radius",
"border-bottom-right-radius",
"border-bottom-left-radius"
],
elementBorders = [],
elementStyle = getComputedStyle(copy);
var changed = false;
for (var i = 0; i < 4; ++i) {
elementBorders[i] = elementStyle.getPropertyValue(bordersCSSProps[i]);
if (elementBorders[i] !== last[i]) {
changed = true;
last[i] = elementBorders[i];
}
}
if (changed) {
var borders = [].concat(elementBorders).map(function(a) {
return parseInt(a)
});
var rad = {
tl: borders[0],
tr: borders[1],
br: borders[2],
bl: borders[3]
};
ctx.clearRect(0, 0, 600, 500);
ctx.fillRoundedRect(120, 120, 100, 100, rad);
}
}, 1E3 / 60);
function elemBordersSet() {
var borders = [tl.value, tr.value, br.value, bl.value].join('px ') + 'px';
copy.style.borderRadius = borders;
}
tl.oninput = elemBordersSet;
tr.oninput = elemBordersSet;
bl.oninput = elemBordersSet;
br.oninput = elemBordersSet;
html,
body {
margin: 0;
padding: 0;
}
<div style="display:inline-block; position: absolute;
left:120px;top:120px; width: 100px; height: 100px; background:green;
border-radius: 100px 49px 1px 1px;" id="copy">
</div>
<canvas style="opacity:0.5; z-index:1; display: inline-block; position: absolute; left:0; top:0;" id="rounded-rect" width="600" height="500">
</canvas>
<div style="margin-top:250px; position:absolute; z-index:5">
<label>
Top left
<input type="range" min="1" max="100" value="0" class="slider" id="tl"></label><br/>
<label>
Top right
<input type="range" min="1" max="100" value="0" class="slider" id="tr"></label><br/>
<label>
Bottom left
<input type="range" min="1" max="100" value="0" class="slider" id="bl"></label><br/>
<label>
Bottom right
<input type="range" min="1" max="100" value="0" class="slider" id="br"></label><br/>
</div>