Reputation: 2282
I have some code that takes a array of colors and creates a grid of rectangles on a canvas based on the data: https://jsfiddle.net/yzjeLm5r/2/
Html:
<canvas id="canvas" width="100" height="100">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
Js:
var my_canvas = document.getElementById('canvas'),
context = my_canvas.getContext("2d");
const cellSize = 20;
const arr = ['#FF0000', '#FF0000', null, null, '#0000FF',
'#FF0000', '#FF0000', '#FF0000', null, '#0000FF',
'#FF0000', null, '#FF0000', null, '#0000FF',
'#FF0000', '#FF0000', '#FF0000', null, '#0000FF',
'#FF0000', '#FF0000', '#0000FF', '#0000FF', '#0000FF'
]
let col = 0;
let row = 0;
arr.forEach((item) => {
if (item) {
context.fillStyle = item;
context.fillRect(
col * cellSize,
row * cellSize,
cellSize,
cellSize
);
}
if (col === 4) {
col = 0;
row += 1;
} else {
col += 1;
}
});
Outcome:
I now need to find a way to create a stroke around the entire shape, as well as any 'island' pieces on the inside, to separate it from the blank regions (it does not separate the red from blue, just the outer borders). So it would look like this:
However, I can't find any viable way to do this. Some methods suggest creating the stroke, change the composition to context.globalCompositeOperation = "destination-out";
and then adding the fill which would 'merge' any overlapping pieces, but these don't actually overlap they just touch. Other solutions recommend just tracing around the edges of the complete shape with stoke ctx.moveTo()
and then applying stroke, but since I'm filling data using fillRect() I dont have the coordinates to trace around it.
Is there anyway to get the stroke I need?
Upvotes: 2
Views: 384
Reputation: 54039
Assuming its the inside edges the following as some solutions.
The simplest way is to render the borders by checking the 4 sides of each cell and drawing the border if the edge has a neighboring cell.
The next example adds the border and cell color in one pass, it is drawn twice, with the second (right) with only 1 pixel border.
const ctx = canvas.getContext("2d");
const cells =
"AAA B" +
"A A B" +
"AAA B" +
"AA BB" +
"ABB B" +
"AABBB";
const cols = {
A: "#F00",
B: "#00F",
};
const cols1 = {
A: "#FDD",
B: "#DDF",
};
const stride = 5;
const size = canvas.height / (cells.length / stride | 0);
const borderCol = "#000";
function drawCells(x, y, border, cols, cells) {
var i = 0;
ctx.setTransform(1,0,0,1,x,y);
while (i < cells.length) {
const col = cols[cells[i]];
if (col) {
const x = i % stride, y = i / stride | 0;
ctx.fillStyle = col;
ctx.fillRect(x * size, y * size, size, size);
ctx.fillStyle = borderCol;
const left = x ? cells[i - 1] : undefined;
const right = x < stride - 1 ? cells[i + 1] : undefined;
const s = size, b = border;
!cols[cells[i - stride]] && ctx.fillRect(x * s, y * s, s, b); // above
!cols[cells[i + stride]] && ctx.fillRect(x * s, (y + 1) * s - b, s, b); // below
!cols[left] && ctx.fillRect(x * s, y * s, b, s);
!cols[right] && ctx.fillRect((x + 1) * s - b, y * s, b, s);
}
i++;
}
}
drawCells(0, 0, 6, cols, cells);
drawCells(300, 0, 1, cols1, cells);
<canvas id="canvas" width="500" height="240"></canvas>
However it does not do a good job on the inside corners.
To do that you can use a similar double pass. First pass draws the cells, then the second pass extends the borders for the top and bottom edges while using composite operation "source-atop" to ensure it only draws where there are existing pixels.
const ctx = canvas.getContext("2d");
const cells =
"AAA BB" +
"A AAAB" +
"AAA BB" +
"A BBB" +
"ABBB B" +
"AA BBB";
const cols = {
A: "#0C0",
B: "#FF0",
};
const stride = 6;
const size = canvas.height / (cells.length / stride | 0);
const border = 6; // border width in pixels
const borderCol = "#000";
function drawCells(cells) {
var i = 0;
while (i < cells.length) {
const col = cols[cells[i]];
if (col) {
const x = i % stride, y = i / stride | 0;
ctx.fillStyle = col;
ctx.fillRect(x * size, y * size, size, size);
}
i++;
}
ctx.fillStyle = borderCol;
ctx.globalCompositeOperation = "source-atop";
i = 0;
while (i < cells.length) {
const col = cols[cells[i]];
if (col) {
const x = i % stride, y = i / stride | 0;
const left = x ? cells[i - 1] : undefined;
const right = x < stride - 1 ? cells[i + 1] : undefined;
const s = size, b = border, sb = s + b + b;
!cols[cells[i - stride]] && ctx.fillRect(x * s - b, y * s, sb, b);
!cols[cells[i + stride]] && ctx.fillRect(x * s - b, (y + 1) * s - b, sb, b);
!cols[left] && ctx.fillRect(x * s, y * s, b, s);
!cols[right] && ctx.fillRect((x + 1) * s - b, y * s, b, s);
}
i++;
}
}
drawCells(cells);
<canvas id="canvas" width="240" height="240"></canvas>
Or same again but using beveled corners
const ctx = canvas.getContext("2d");
const cells =
"AAA BB" +
"A AAAB" +
"AAA BB" +
"A BBB" +
"ABBB B" +
"AA BBB";
const cols = {
A: "#F00",
B: "#FF0",
};
const stride = 6;
const size = canvas.height / (cells.length / stride | 0);
const border = 6; // border width in pixels
const borderCol = "#000";
function bevelRectBottom(x, y, w, h) {
ctx.beginPath();
ctx.lineTo(x, y + h);
ctx.lineTo(x + h, y);
ctx.lineTo(x + w - h, y);
ctx.lineTo(x + w, y + h);
ctx.fill();
}
function bevelRectTop(x, y, w, h) {
ctx.beginPath();
ctx.lineTo(x, y);
ctx.lineTo(x + h, y + h);
ctx.lineTo(x + w - h, y + h);
ctx.lineTo(x + w, y);
ctx.fill();
}
function drawCells(cells) {
var i = 0;
while (i < cells.length) {
const col = cols[cells[i]];
if (col) {
const x = i % stride, y = i / stride | 0;
ctx.fillStyle = col;
ctx.fillRect(x * size, y * size, size, size);
}
i++;
}
ctx.fillStyle = borderCol;
ctx.globalCompositeOperation = "source-atop";
i = 0;
while (i < cells.length) {
const col = cols[cells[i]];
if (col) {
const x = i % stride, y = i / stride | 0;
const left = x ? cells[i - 1] : undefined;
const right = x < stride - 1 ? cells[i + 1] : undefined;
const s = size, b = border, sb = s + b + b;
!cols[cells[i - stride]] && bevelRectTop(x * s - b, y * s, sb, b);
!cols[cells[i + stride]] && bevelRectBottom(x * s - b, (y + 1) * s - b, sb, b);
!cols[left] && ctx.fillRect(x * s, y * s, b, s);
!cols[right] && ctx.fillRect((x + 1) * s - b, y * s, b, s);
}
i++;
}
}
drawCells(cells);
<canvas id="canvas" width="240" height="240"></canvas>
Upvotes: 1