JavaScript PNG drawing function with inline styling not working - javascript

I need your help. I'm currently trying to apply a part of this answered SO question to a library I'm using to generate a PNG:
How can I colour different words in the same line with HTML5 Canvas?
This is how I call my function:
let fs = require('fs');
let text2png = require('text2png');
let text = '{#ff0000ES}KISTAFÜNF\nZEHNZWANZIG\nDREIVIERTEL\nVORFUNKNACH\nHALBEALFÜNF\nEINSXAMZWEI\nDREIPMJVIER\nSECHSNLACHT\nSIEBENZWÖLF\nZEHNEUNKUHR';
text = text.split('').join(' '); // Fake letter spacing
fs.writeFileSync('test2.png', text2png(text,
{
color: 'gray',
textAlign: 'center',
lineSpacing: 30,
font: '100px sans-serif'
}
));
This is the text2png function which I modified:
const text2png = (text, options = {}) => {
// Options
options = parseOptions(options);
// Register a custom font
if (options.localFontPath && options.localFontName) {
registerFont(options.localFontPath, {family: options.localFontName});
}
const canvas = createCanvas(0, 0);
const ctx = canvas.getContext("2d");
const max = {
left: 0,
right: 0,
ascent: 0,
descent: 0
};
let lastDescent;
const lineProps = text.split("\n").map(line => {
ctx.font = options.font;
const metrics = ctx.measureText(line);
const left = -1 * metrics.actualBoundingBoxLeft;
const right = metrics.actualBoundingBoxRight;
const ascent = metrics.actualBoundingBoxAscent;
const descent = metrics.actualBoundingBoxDescent;
max.left = Math.max(max.left, left);
max.right = Math.max(max.right, right);
max.ascent = Math.max(max.ascent, ascent);
max.descent = Math.max(max.descent, descent);
lastDescent = descent;
return {line, left, right, ascent, descent};
});
const lineHeight = max.ascent + max.descent + options.lineSpacing;
const contentWidth = max.left + max.right;
const contentHeight =
lineHeight * lineProps.length -
options.lineSpacing -
(max.descent - lastDescent);
canvas.width =
contentWidth +
options.borderLeftWidth +
options.borderRightWidth +
options.paddingLeft +
options.paddingRight;
canvas.height =
contentHeight +
options.borderTopWidth +
options.borderBottomWidth +
options.paddingTop +
options.paddingBottom;
const hasBorder =
false ||
options.borderLeftWidth ||
options.borderTopWidth ||
options.borderRightWidth ||
options.borderBottomWidth;
if (hasBorder) {
ctx.fillStyle = options.borderColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
if (options.backgroundColor) {
ctx.fillStyle = options.backgroundColor;
ctx.fillRect(
options.borderLeftWidth,
options.borderTopWidth,
canvas.width - (options.borderLeftWidth + options.borderRightWidth),
canvas.height - (options.borderTopWidth + options.borderBottomWidth)
);
} else if (hasBorder) {
ctx.clearRect(
options.borderLeftWidth,
options.borderTopWidth,
canvas.width - (options.borderLeftWidth + options.borderRightWidth),
canvas.height - (options.borderTopWidth + options.borderBottomWidth)
);
}
ctx.font = options.font;
ctx.fillStyle = options.textColor;
ctx.antialias = "gray";
ctx.textAlign = options.textAlign;
ctx.lineWidth = options.strokeWidth;
ctx.strokeStyle = options.strokeColor;
let offsetY = options.borderTopWidth + options.paddingTop;
lineProps.forEach(lineProp => {
// Calculate Y
let x = 0;
let y = max.ascent + offsetY;
// Calculate X
switch (options.textAlign) {
case "start":
case "left":
x = lineProp.left + options.borderLeftWidth + options.paddingLeft;
break;
case "end":
case "right":
x =
canvas.width -
lineProp.left -
options.borderRightWidth -
options.paddingRight;
break;
case "center":
x = contentWidth / 2 + options.borderLeftWidth + options.paddingLeft;
break;
}
let i = 0;
let inlineColorMarker = '{}';
let state = [];
let subText = '';
while (i < lineProp.line.length) {
let letter = lineProp.line[i];
let wordCharCode = lineProp.line.charCodeAt(i);
if (wordCharCode < 256) {
if (inlineColorMarker.indexOf(letter) > -1) {
if (subText !== '') {
renderText(subText);
subText = '';
}
if (letter === '{') { // Word color delimiter
state.push({
textColor: options.textColor,
x: x,
y: y,
});
var t = text[i];
console.log(t);
if (t === '#') {
console.log(text.substr(i, 7));
options.textColor = text.substr(i, 7);
i += 6;
}
} else if (letter === '}') {
var s = state.pop();
y = s.y;
options.textColor = s.textColor;
}
} else {
subText += letter;
}
}
i++;
}
function renderText(text) {
ctx.save();
ctx.fillStyle = options.textColor;
ctx.translate(x, y)
// ctx.scale(scale, scale)
ctx.fillText(text, 0, 0);
if (options.strokeWidth > 0) {
ctx.strokeText(lineProp.line, x, y);
}
ctx.restore();
}
// ctx.fillText(lineProp.line, x, y);
// if (options.strokeWidth > 0) {
// ctx.strokeText(lineProp.line, x, y);
// }
offsetY += lineHeight;
});
switch (options.output) {
case "buffer":
return canvas.toBuffer();
case "stream":
return canvas.createPNGStream();
case "dataURL":
return canvas.toDataURL("image/png");
case "canvas":
return canvas;
default:
throw new Error(`output type:${options.output} is not supported.`);
}
};
function parseOptions(options) {
return {
font: or(options.font, "30px sans-serif"),
textAlign: or(options.textAlign, "left"),
textColor: or(options.textColor, options.color, "black"),
backgroundColor: or(options.bgColor, options.backgroundColor, null),
lineSpacing: or(options.lineSpacing, 0),
strokeWidth: or(options.strokeWidth, 0),
strokeColor: or(options.strokeColor, "white"),
paddingLeft: or(options.paddingLeft, options.padding, 0),
paddingTop: or(options.paddingTop, options.padding, 0),
paddingRight: or(options.paddingRight, options.padding, 0),
paddingBottom: or(options.paddingBottom, options.padding, 0),
borderLeftWidth: or(options.borderLeftWidth, options.borderWidth, 0),
borderTopWidth: or(options.borderTopWidth, options.borderWidth, 0),
borderBottomWidth: or(options.borderBottomWidth, options.borderWidth, 0),
borderRightWidth: or(options.borderRightWidth, options.borderWidth, 0),
borderColor: or(options.borderColor, "black"),
localFontName: or(options.localFontName, null),
localFontPath: or(options.localFontPath, null),
output: or(options.output, "buffer")
};
}
function or() {
for (let arg of arguments) {
if (typeof arg !== "undefined") {
return arg;
}
}
return arguments[arguments.length - 1];
}
module.exports = text2png;
Somehow I'm missing something because it's just drawing the first line and also without any colors in the first 2 letters:

Make the following changes.
At top of code (very first line) add the directive "use strict"; This would have throw an error where the problem is.
In the while loop you have
var t = text[i]; // no such thing as text
// i pointing to wrong position
console.log(t);
if (t === '#') {
console.log(text.substr(i, 7)); // text is undefined
options.textColor = text.substr(i, 7); // text is undefined
i += 6; // wrong step as i has wrong value
}
Change to
if (lineProp.line[i + 1] === '#') {
options.textColor = lineProp.line.substr(++i, 7); // must be ++i DO NOT use i++
i += 6;
}

Related

How to extract the background color in the desired order when I click the part of the shape drawn on the canvas

In React I drew using canvas. And I got these paintings on canvas.(The numbers have been edited by me.)
And using the floodfill algorithm, I colored as follows.
I want to process the colors corresponding to the numbered areas in the image above as an array.
I want to get an array of the same shape as
[Background of ①, Background of ②, Background of ③].
For example, the image below wants to be output like ['blue','yellow','red'].
If ①=yellow,②=blue,③=red, an array of ['yellow','blue','red'] will be created.
Is it possible to implement these functions via canvas? Here is my code.
import { useEffect, useRef, useState } from "react";
const allowColor = [
'{"r":255,"g":255,"b":255,"a":255}',
'{"r":255,"g":0,"b":0,"a":255}',
'{"r":255,"g":255,"b":0,"a":255}',
'{"r":0,"g":0,"b":255,"a":255}',
];
function TimeColor() {
const [nowColor, setNowColor] = useState({ r: 255, g: 255, b: 255, a: 255 });
const angles = [Math.PI * 1.5, Math.PI * 0.5, Math.PI * 0.8, Math.PI * 1.5];
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
let beginAngle = 0;
let endAngle = 0;
const ctx = canvas.getContext("2d");
for (let i = 0; i < angles.length - 1; i++) {
// Begin where we left off
beginAngle = angles[i];
// End Angle
endAngle = angles[i + 1];
// ctx.fillStyle = colors[i % colors.length];
ctx.fillStyle = "white";
ctx.beginPath();
ctx.lineWidth = 3;
// Same code as before
ctx.moveTo(200, 200);
ctx.arc(200, 200, 120, beginAngle, endAngle);
ctx.lineTo(200, 200);
ctx.stroke();
// Fill
ctx.fill();
}
}, []);
/* start flood fill functions */
const matchStartColor = function (data, pos, startColor) {
return (
data[pos] === startColor.r &&
data[pos + 1] === startColor.g &&
data[pos + 2] === startColor.b &&
data[pos + 3] === startColor.a
);
};
const colorPixel = function (data, pos, color) {
data[pos] = color.r || 0;
data[pos + 1] = color.g || 0;
data[pos + 2] = color.b || 0;
data[pos + 3] = color.hasOwnProperty("a") ? color.a : 255;
};
const floodFill = function (startX, startY, fillColor) {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
const dstImg = ctx.getImageData(0, 0, canvas.width, canvas.height);
const dstData = dstImg.data;
const startPos = (startY * canvas.width + startX) * 4;
const startColor = {
r: dstData[startPos],
g: dstData[startPos + 1],
b: dstData[startPos + 2],
a: dstData[startPos + 3],
};
if (allowColor.indexOf(JSON.stringify(startColor)) === -1) return;
if (JSON.stringify(fillColor) === JSON.stringify(startColor)) return;
const todo = [[startX, startY]];
while (todo.length) {
const pos = todo.pop();
let x = pos[0];
let y = pos[1];
let currentPos = (y * canvas.width + x) * 4;
while (y-- >= 0 && matchStartColor(dstData, currentPos, startColor)) {
currentPos -= canvas.width * 4;
}
currentPos += canvas.width * 4;
++y;
let reachLeft = false;
let reachRight = false;
while (
y++ < canvas.height - 1 &&
matchStartColor(dstData, currentPos, startColor)
) {
colorPixel(dstData, currentPos, fillColor);
if (x > 0) {
if (matchStartColor(dstData, currentPos - 4, startColor)) {
if (!reachLeft) {
todo.push([x - 1, y]);
reachLeft = true;
}
} else if (reachLeft) {
reachLeft = false;
}
}
if (x < canvas.width - 1) {
if (matchStartColor(dstData, currentPos + 4, startColor)) {
if (!reachRight) {
todo.push([x + 1, y]);
reachRight = true;
}
} else if (reachRight) {
reachRight = false;
}
}
currentPos += canvas.width * 4;
}
}
ctx.putImageData(dstImg, 0, 0);
};
/* end flood fill funtions */
const changeColor = (e) => {
floodFill(e.clientX, e.clientY, nowColor);
};
return (
<>
<canvas ref={canvasRef} width={400} height={400} onClick={changeColor} />
<div
style={{ display: "flex", justifyContent: "space-around", width: 400 }}
>
<div
style={{
background: "red",
width: 50,
height: 50,
borderRadius: 10,
cursor: "pointer",
}}
onClick={() => setNowColor({ r: 255, g: 0, b: 0, a: 255 })}
/>
<div
style={{
background: "blue",
width: 50,
height: 50,
borderRadius: 10,
cursor: "pointer",
}}
onClick={() => setNowColor({ r: 0, g: 0, b: 255, a: 255 })}
/>
<div
style={{
background: "yellow",
width: 50,
height: 50,
borderRadius: 10,
cursor: "pointer",
}}
onClick={() => setNowColor({ r: 255, g: 255, b: 0, a: 255 })}
/>
</div>
</>
);
}
export default TimeColor;

How to draw a rubberband or lasso to make a selection in word selection game

I am trying to put a word search game on my web page.
I see here is a beautiful example: Embed a Word Search in Your Site
I want to make similar gui, so that when the user selects the word letters from the grid by dragging mouse, it will draw a red elastic rubber band around the letters selected.
How do they do it on their web page? I tried to "inspect" the html after the band is drawn, however did not see any element added.
Can somebody give me any clue?
Here is a JS example
class App {
constructor(div) {
this.div = div;
this.canvas = document.getElementById("canvas");
this.ctx = canvas.getContext('2d');
this.dirty = true;
this.prev = +new Date();
this.resize();
window.addEventListener('resize', () => this.resize(), false);
this.canvas.addEventListener("mousedown", (event) => {
this.touchdown(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("mousemove", (event) => {
this.touchmove(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("mouseup", (event) => {
this.touchup(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("touchstart", (event) => {
this.touchdown(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("touchmove", (event) => {
this.touchmove(App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("touchend", (event) => {
this.touchup(App.getmousePos(event));
event.preventDefault();
});
}
resize() {
this.canvas.width = this.div.clientWidth;
this.canvas.height = this.div.clientWidth;
this.draw();
}
loop() {
let now = +new Date();
let dt = now - this.prev;
this.prev = now;
this.update()
if (this.dirty)
this.draw();
window.requestAnimationFrame(() => this.loop());
}
update(dt) {}
draw() {
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
this.ctx.rect(0, 0, 100, 100);
this.ctx.fill();
}
touchdown(x, y) { console.log("down", x, y); }
touchmove(x, y) {}
touchup(x, y) {}
static getmousePos(event) {
if (event.changedTouches) {
return [event.changedTouches[0].pageX, event.changedTouches[0].pageY];
}
else {
var rect = event.target.getBoundingClientRect();
return [event.clientX- rect.left, event.clientY - rect.top];
}
}
}
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
add(v_x, y) {
if (y != undefined) {
return new Vector(this.x + v_x, this.y + y);
}
else {
return new Vector(this.x + v_x.x, this.y + v_x.y);
}
}
sub(v_x, y) {
if (y != undefined) {
return new Vector(this.x - v_x, this.y - y);
}
else {
return new Vector(this.x - v_x.x, this.y - v_x.y);
}
}
mul(s) {
return new Vector(this.x * s, this.y * s);
}
get slength() {
return this.x * this.x + this.y * this.y;
}
get length() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
get angle() {
return Math.atan2(this.y, this.x);
}
}
class Grid {
constructor(width, height, data) {
this.width = width;
this.height = height;
this.data = data || new Array(width*height).fill("_");
}
get length() {
return this.width*this.height;
}
get(x_pos, y) {
if (y != undefined)
return this.data[x_pos+y*this.width];
else
return this.data[x_pos];
}
set(value, x_pos, y) {
if (y != undefined)
this.data[x_pos+y*this.width] = value;
else
this.data[x_pos] = value;
}
clone() {
return new Grid(this.width, this.height, this.data.slice());
}
finalize() {
this.data = this.data.map(v => v == "_" ? String.fromCharCode(Math.floor(Math.random() * 26) + 97) : v);
}
print() {
for (let i = 0; i < this.height; i++)
console.log(this.data.slice(i*this.width, (i+1)*this.width).join(","));
}
}
class Puzzle {
constructor(width, height, words, directions) {
this.grid = new Grid(width, height);
this.words = words;
this.placeWords(words, directions);
}
placeWords(words, directions) {
const range = (N) => Array.from({length: N}, (v, k) => k);
const shuffle = (array) => {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
words = words.slice();
let positions = range(this.grid.length);
let stack = [ {
grid: this.grid,
word: words.shift(),
directions: shuffle(directions.slice()),
positions: shuffle(positions.slice()) } ];
while (true) {
let current = stack[stack.length-1];
if (!current)
throw Error("impossible");
let dir = current.directions.pop();
if (!dir) {
current.positions.pop();
current.directions = shuffle(directions.slice());
dir = current.directions.pop();
}
if (current.positions.length <= 0) {
words.unshift(current.word);
stack.pop();
}
else {
let pos = current.positions[current.positions.length-1];
let grid = this.placeWord(current.grid, current.word, pos, dir);
if (grid) {
if (words.length > 0) {
stack.push({grid: grid,
word: words.shift(),
directions: shuffle(directions.slice()),
positions: shuffle(positions.slice())});
}
else {
grid.finalize();
this.grid = grid;
break;
}
}
}
}
}
placeWord(grid, word, position, direction) {
let copy = grid.clone();
position = new Vector(position % grid.width, Math.floor(position / grid.width));
let letters = [...word];
while (0 <= position.x && position.x < grid.width && 0 <= position.y && position.y < grid.height) {
if (letters.length <= 0)
break;
let letter = letters.shift();
if (copy.get(position.x, position.y) == "_" || copy.get(position.x, position.y) == letter) {
copy.set(letter, position.x, position.y);
position = position.add(direction);
}
else {
return null;
}
}
if (letters.length > 0)
return null;
else
return copy;
}
wordAt(position, direction, length) {
let word = new Array(length);
for (let i = 0; i < length; i++) {
word[i] = this.grid.get(position.x, position.y);
position = position.add(direction);
}
return word.join("");
}
print() {
this.grid.print();
}
}
class Selection {
constructor(position, fill=false) {
this.position = position;
this._fill = fill;
this.direction = new Vector(1, 0);
this.length = 0;
this.flength = 0;
}
clone() {
return new Selection(this.position).to(this.position.add(this.direction.mul(this.length)));
}
to(position) {
let direction = position.sub(this.position);
if (Math.abs(direction.y) == 0) {
this.direction = new Vector(direction.x >= 0 ? 1 : -1, 0);
this.length = direction.x * this.direction.x;
}
else if (Math.abs(direction.x) == 0) {
this.direction = new Vector(0, direction.y >= 0 ? 1 : -1);
this.length = direction.y * this.direction.y;
}
else {
this.direction = new Vector(direction.x >= 0 ? 1 : -1, direction.y >= 0 ? 1 : -1);
this.length = direction.x * this.direction.x;
}
this.flength = direction.length;
return this;
}
fill(fill=true) {
this._fill = fill;
return this;
}
draw(ctx, wsize, hsize) {
let pos = this.position.mul(wsize);
let length = wsize * (this.flength+1);
let angle = this.direction.angle;
//console.log(this.x, this.y, x, y, length, angle, this.dx, this.dy);
ctx.save();
ctx.strokeStyle = "black";
ctx.fillStyle = "yellow";
ctx.translate(pos.x+hsize*0.5, pos.y+hsize*0.5);
ctx.rotate(angle);
this.drawHighlight(ctx, -hsize*0.5, -hsize*0.5, length, hsize, hsize*0.5, this._fill, true);
ctx.restore();
}
drawHighlight(ctx, x, y, width, height, radius=20, fill=true, stroke=false) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
if (fill) ctx.fill();
if (stroke) ctx.stroke();
}
}
class PuzzleApp extends App {
constructor(div, puzzle) {
super(div);
this.puzzle = puzzle;
this.renderList(document.getElementById("list"));
this.selections = new Array();
this.loop();
}
renderList(parent) {
this.puzzle.words.forEach(word => {
let li = document.createElement("li");
let text = document.createTextNode(word);
li.appendChild(text);
parent.appendChild(li);
});
}
gridSize() {
let wsize = Math.floor(this.canvas.width/this.puzzle.grid.width);
let hsize = Math.floor(this.canvas.width/this.puzzle.grid.height);
return [wsize, hsize];
}
clientToGrid(x, y) {
let [wsize, hsize] = this.gridSize();
x = Math.floor(x / wsize);
y = Math.floor(y / hsize);
return [x, y];
}
draw() {
if (!this.puzzle)
return;
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
let [wsize, hsize] = this.gridSize();
this.selections.forEach(s => s.draw(this.ctx, wsize, hsize));
if (this.selection)
this.selection.draw(this.ctx, wsize, hsize);
let x = 0;
let y = 0;
this.ctx.fillStyle = "black";
this.ctx.font = (wsize * 0.5) + 'px sans-serif';
this.ctx.textAlign = "center";
this.ctx.textBaseline = "middle";
for (let j = 0; j < this.puzzle.grid.height; j++) {
for (let i = 0; i < this.puzzle.grid.width; i++) {
let letter = this.puzzle.grid.get(i, j);
this.ctx.fillText(letter, x+wsize * 0.5, y+wsize * 0.5);
x += wsize;
}
x = 0;
y += wsize;
}
}
touchdown(x, y) {
[x, y] = this.clientToGrid(x, y);
this.selection = new Selection(new Vector(x, y));
this.dirty = true;
}
touchmove(x, y) {
if (!this.selection)
return;
[x, y] = this.clientToGrid(x, y);
this.selection.to(new Vector(x, y));
}
touchup(x, y) {
if (!this.selection)
return;
let word = this.puzzle.wordAt(this.selection.position, this.selection.direction, this.selection.length+1);
console.log(word);
if (word) {
let list = document.getElementById("list");
let elements = list.getElementsByTagName("li");
Array.prototype.some.call(elements, li => {
if (li.innerText == word) {
li.classList.add("found");
this.selections.push(this.selection.clone().fill());
return true;
}
return false;
});
}
this.selection = null;
}
}
let app = null;
document.addEventListener("DOMContentLoaded", function(event) {
const wordLists = [
["pear", "apple", "banana", "peach", "kiwi", "prune", "persimon"]
]
const pick = (array) => array[Math.floor(Math.random() * (array.length))];
let params = (new URL(document.location)).searchParams;
let directions = [new Vector(1,0), new Vector(0,1)];
if (params.get("diagonal")) {
directions.push(new Vector(1,1));
}
if (params.get("backwards")) {
directions.push(new Vector(-1,0));
directions.push(new Vector(0,-1));
if (params.get("diagonal")) {
directions.push(new Vector(-1,-1));
}
}
let puzzle = new Puzzle(8, 8, pick(wordLists), directions);
puzzle.print();
app = new PuzzleApp(document.getElementById("canvasContainer"), puzzle);
});
html, body {
width: 100%;
height: 100%;
margin: 0px;
}
ul {
display: inline-block;
font-size: 7vmin;
padding-left: 14vmin;
}
li.found {
text-decoration: line-through;
}
div#canvasContainer {
width: 100%;
display: inline-block;
}
#media (min-aspect-ratio: 1/1) {
ul {
font-size: 4vmin;
padding-left: 7vmin;
}
div#canvasContainer {
width: 50%;
}
}
<div id="canvasContainer"><canvas id="canvas" width="320" height="320"></canvas></div>
<ul id="list"></ul>
I'll let you figure out how to change the thickness and color of the rubber band.
The red band is an svg. It doesn't get added when you click, but rather it gets redrawn.
I have inspected the referenced website and it seems like they draw rubber band thing in SVG. If you inspect this SVG tag while drawing:
you will notice it shows some updates.
You can use same SVG methodology. Or Another way is to use an absolute positioned div and update top, left css values based on mousedown, width based on mousemove event.
I have seen "what" they have done.
They have created an svg element. This svg sits exactly above and covers the entire table that contains the grid with letters. When the user clicks any letter and starts drag, they add a rectangle to the svg. Depending on how the cursor will move they do the angle transform to the rectangle. If the word is selected correctly, the rectangle survives; else, it is removed.
I need to find out now, "how" part of it.
Thanks to all for a very quick response.

JS: Locate image/canvas inside of another canvas, and then rectangle

How can I do something like this?
Image one:
Image two:
And how can I do like this:
const image1 = document.querySelector("#imgone");
const image2 = document.querySelector("#imgtwo");
let ctx1 = image1.getContext("2d");
let ctx2 = image2.getContext("2d");
function locate(context1, context2) {
#??????
return "..."
}
function rectangle(context, corner1,corner2,corner3,corner4) {
#??????
return "..."
}
[corner1,corner2,corner3,corner4] = locate(ctx,ctx2);
rectangle(ctx2, corner1,corner2,corner3,corner4);
Basically a computer-vision task.
Is it possible? How can you do it?
it is me sld on a different account. I have figured it out, and would like to show you.
My solution is very inneficient, but I have this code.
function canvas(w = 300, h = 150) {
let r = document.createElement("canvas")
r.height = h
r.width = w
return r
}
function points(startx, starty, w, h) {
return [
[startx, starty],
[startx + w, starty],
[startx + w, starty + h],
[startx, starty + h]
]
}
function getPixelColor(canvas, x, y) {
return Array.from(canvas.getContext("2d").getImageData(x, y, 1, 1).data)
}
function gpc(canvas, x, y, sx, sy) {
return Array.from(canvas.getContext("2d").getImageData(x, y, sx, sy).data)
}
function gap(canvas) {
return gpc(canvas, 0, 0, canvas.width, canvas.height)
}
function gx(index, w = 300, h = 150, nx = 5, ny = 5) {
let fpd = []
for (let i = 0; i < w; i += nx) {
for (let ii = 0; ii < h; ii += ny) {
fpd.push([i, ii])
}
}
return fpd[index]
}
// function genPixels(canvas,nx=5,ny=5) {
// let ctx = canvas.getContext("2d")
// let fpd = []
// for (let i = 0; i < canvas.width; i+=nx) {
// for (let ii = 0; ii < canvas.height; ii+=ny) {
// fpd.push(Array.from(ctx.getImageData(i,ii,nx,ny).data))
// }
// }
// return fpd
// }
function chunk(n, c) {
return n.reduce((n, r, t) => {
const o = Math.floor(t / c);
return n[o] = [].concat(n[o] || [], r), n
}, [])
}
function findImg(parent, canvas, ih, iw) {
if (parent.tagName !== "canvas") {
console.log("parent argument must be canvas. use canvas.content.drawImage on a new canvas to convert img to canvas.")
}
if (canvas.tagName === "img") {
console.log("canvas argument must be type canvas. use canvas.context.drawImage on a new canvas to convert img to canvas.")
}
let allPixels = gap(parent)
let cp = gap(canvas)
let chunks = chunk(allPixels, ih * iw * 4)
for (let i = 0; i < chunks.length; i += 1) {
if (String(chunks[i]) === String(cp)) {
let start = gx(i, parent.width, parent.height, iw, ih)
return points(start[0], start[1], iw, ih)
}
}
return false
}
let pointsc = findImg(canvas(), canvas(5, 5), 5, 5);
let dim = pointsc[2].reverse()
this.strokeStyle = "black"
let yours = document.querySelector("#yours")
yours.getContext("2d").strokeRect(0, 0, dim[0], dim[1])
<canvas id="yours"></canvas>
Minified:
function canvas(t=300,n=150){let e=document.createElement("canvas");return e.height=n,e.width=t,e}function points(t,n,e,a){return[[t,n],[t+e,n],[t+e,n+a],[t,n+a]]}function getPixelColor(t,n,e){return Array.from(t.getContext("2d").getImageData(n,e,1,1).data)}function gpc(t,n,e,a,r){return Array.from(t.getContext("2d").getImageData(n,e,a,r).data)}function gap(t){return gpc(t,0,0,t.width,t.height)}function gx(t,n=300,e=150,a=5,r=5){let o=[];for(let t=0;t<n;t+=a)for(let n=0;n<e;n+=r)o.push([t,n]);return o[t]}function chunk(t,n){return t.reduce((t,e,a)=>{const r=Math.floor(a/n);return t[r]=[].concat(t[r]||[],e),t},[])}function findImg(t,n,e,a){"CANVAS"!==t.tagName&&console.log("parent argument must be canvas. use canvas.content.drawImage on a new canvas to convert img to canvas."),"IMG"===n.tagName&&console.log("canvas argument must be type canvas. use canvas.context.drawImage on a new canvas to convert img to canvas.");let r=gap(t),o=gap(n),c=chunk(r,e*a*4);for(let n=0;n<c.length;n+=1)if(String(c[n])===String(o)){let r=gx(n,t.width,t.height,a,e);return points(r[0],r[1],a,e)}return!1}
I will be doing an example with just two white images.
let pointsc = findImg(canvas(), canvas(5,5), 5, 5);
let dim = pointsc[2].reverse()
this.strokeStyle="black"
document.querySelector("#yours").getContext("2d").strokeRect(0,0,dim[0],dim[1])
This is extremely inefficient, and I am up for suggestions, for example how to not use as many loops.

Javascript bin-packing problem is leaving empty spaces

My friend and I are working on a small project, but are struggling with bin-packing problem using canvas drawing, to let the customers imagine, what they're ordering.
It's basically a PCB order form. What we are struggling with is that the canvas is somehow drawing "stairs" in the middle of the image, when adding patterns.
I can find no good reason why this is happening.
/*-- CLASSES --*/
let GrowingPacker = function() { };
GrowingPacker.prototype = {
fit: function(blocks) {
var n, node, block, len = blocks.length;
var w = len > 0 ? blocks[0].w : 0;
var h = len > 0 ? blocks[0].h : 0;
this.root = { x: 0, y: 0, w: w, h: h };
for (n = 0; n < len ; n++) {
block = blocks[n];
if (node = this.findNode(this.root, block.w, block.h))
block.fit = this.splitNode(node, block.w, block.h);
else
block.fit = this.growNode(block.w, block.h);
}
},
findNode: function(root, w, h) {
if (root.used)
return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
else if ((w <= root.w) && (h <= root.h))
return root;
else
return null;
},
splitNode: function(node, w, h) {
node.used = true;
node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h };
node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h };
return node;
},
growNode: function(w, h) {
//var possibleGrowDown = (limit.area.max.height >= this.root.h + h);
var possibleGrowRight = (limit.area.max.width >= this.root.w + w);
var canGrowDown = (w <= this.root.w);
var canGrowRight = (h <= this.root.h);
var shouldGrowDown = possibleGrowRight && canGrowDown && (this.root.w >= (this.root.h + h)); // attempt to keep square-ish by growing down when width is much greater than height
var shouldGrowRight = possibleGrowRight && canGrowRight && (this.root.h >= (this.root.w + w)); // attempt to keep square-ish by growing right when height is much greater than width
if (shouldGrowDown)
return this.growDown(w, h);
else if (shouldGrowRight)
return this.growRight(w, h);
else if (canGrowDown)
return this.growDown(w, h);
else if (canGrowRight)
return this.growRight(w, h);
else
return null; // need to ensure sensible root starting size to avoid this happening
},
growRight: function(w, h) {
this.root = {
used: true,
x: 0,
y: 0,
w: this.root.w + w,
h: this.root.h,
down: this.root,
right: { x: this.root.w, y: 0, w: w, h: this.root.h }
};
var node;
if (node = this.findNode(this.root, w, h))
return this.splitNode(node, w, h);
else
return null;
},
growDown: function(w, h) {
this.root = {
used: true,
x: 0,
y: 0,
w: this.root.w,
h: this.root.h + h,
down: { x: 0, y: this.root.h, w: this.root.w, h: h },
right: this.root
};
var node;
if (node = this.findNode(this.root, w, h))
return this.splitNode(node, w, h);
else
return null;
}
}
class Line {
strokeStyle = '#ddd';
constructor(fX, fY, tX, tY) {
this.fX = fX;
this.fY = fY;
this.tX = tX;
this.tY = tY;
}
get length() {
const hL = Math.pow(this.tX - this.fX, 2);
const vL = Math.pow(this.tY - this.fY, 2);
const l = Math.sqrt(hL + vL);
return l;
}
draw(ctx) {
ctx.beginPath();
ctx.moveTo(this.fX, this.fY);
ctx.lineTo(this.tX, this.tY);
ctx.strokeStyle = this.strokeStyle;
ctx.stroke();
}
}
class Base {
x = 0;
y = 0;
w = 0;
h = 0;
fillStyle = '#e0ede0';
strokeStyle = '#0d0';
constructor() {}
resize(canvas, motifs) {
let x = canvas.width;
let y = canvas.height;
let w = 0;
let h = 0;
if(motifs && motifs.length > 0) {
motifs.forEach((motif) => {
if(motif.x < x) { x = motif.x; }
if(motif.y < y) { y = motif.y; }
if(motif.x + motif.w > x + w) { w = motif.x - x + motif.w; }
if(motif.y + motif.h > y + h) { h = motif.y - y + motif.h; }
});
}
else {
x = 0;
y = 0;
}
this.x = x - form.option.panelization[1].padding;
this.y = y - form.option.panelization[1].padding;
this.w = w + form.option.panelization[1].padding * 2;
this.h = h + form.option.panelization[1].padding * 2;
}
draw(ctx) {
ctx.beginPath();
ctx.rect(this.x, this.y, this.w, this.h);
ctx.fillStyle = this.fillStyle;
ctx.fill();
ctx.strokeStyle = this.strokeStyle;
ctx.stroke();
}
}
class Motif {
name = 'A';
subname = 1;
quantity = 1;
x = 0;
y = 0;
w = 0;
h = 0;
fillStyle = '#ede0e0';
strokeStyle = '#d00';
constructor(id, subname='1') {
this.id = id;
this.subname = subname;
}
get fullName() {
return this.name + this.subname;
}
get id() {
return this.number;
}
set id(id) {
this.number = id;
this.generateName();
}
get area() {
return this.w * this.h;
}
generateName() {
let num = this.number;
let s = '', t;
while(num > 0) {
t = (num - 1) % 26;
s = String.fromCharCode(65 + t) + s;
num = (num - t) / 26 | 0;
}
this.name = s;
}
rotate() {
const w = this.w;
this.w = this.h;
this.h = w;
}
draw(ctx) {
// Rectangle
ctx.beginPath();
ctx.rect(this.x, this.y, this.w, this.h);
ctx.fillStyle = this.fillStyle;
ctx.fill();
ctx.strokeStyle = this.strokeStyle;
ctx.stroke();
// Text
ctx.fillStyle = this.strokeStyle;
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(this.fullName, this.x + this.w / 2, this.y + this.h / 2);
}
}
class Canvas {
canvas;
ctx;
gridCellSize = 10;
gridLines = [];
base = new Base();
motifs = [];
millingLines = [];
constructor(canvas) {
this.canvas = canvas;
this.ctx = this.canvas.getContext('2d');
// Create grid lines
for(let i = 0; i < this.canvas.width / this.gridCellSize + 1; i++) {
this.gridLines.push(new Line(i * this.gridCellSize, 0, i * this.gridCellSize, this.canvas.height));
}
for(let i = 0; i < this.canvas.height / this.gridCellSize + 1; i++) {
this.gridLines.push(new Line(0, i * this.gridCellSize, this.canvas.width, i * this.gridCellSize));
}
this.update();
}
update(motifs) {
this.motifs = motifs;
this.arrangeMotifs();
this.base.resize(this.canvas, this.motifs);
this.draw();
}
arrangeMotifs() {
if(form.option.panelization[1].motifs.length > 0) {
// Copy motifs
let motifs = [];
form.option.panelization[1].motifs.forEach((motif) => {
if(motif.w > 0 && motif.h > 0) {
for(let i = 0; i < motif.quantity; i++) {
const motifCopy = JSON.parse(JSON.stringify(motif));
const motifCopyKeys = Object.keys(motifCopy);
const motifCopyValues = Object.values(motifCopy);
let newMotif = new Motif(motifCopy.id);
for(let i = 0; i < motifCopyKeys.length; i++) {
newMotif[motifCopyKeys[i]] = motifCopyValues[i];
}
newMotif.subname = i+1;
newMotif.x = 0;
newMotif.y = 0;
if(newMotif.w > newMotif.h) {
newMotif.rotate();
}
// Add milling padding
newMotif.w += limit.milling;
newMotif.h += limit.milling;
motifs.push(newMotif);
}
}
});
// Place motifs
const baseW = limit.area.max.width - limit.milling - form.option.panelization[1].padding * 2;
const baseH = limit.area.max.height - limit.milling - form.option.panelization[1].padding * 2;
this.motifs = this.placeMotifs(motifs, baseW, baseH);
}
}
placeMotifs(motifs, baseW, baseH) {
// Sort by area
motifs.sort((a, b) => b.area - a.area);
// Sort by max(width, height)
motifs.sort((a, b) => Math.max(b.w, b.h) - Math.max(a.w, a.h));
// Packing motifs
const packer = new GrowingPacker();
packer.fit(motifs);
const finalMotifs = [];
motifs.forEach((motif) => {
const motifCopy = JSON.parse(JSON.stringify(motif));
const motifCopyKeys = Object.keys(motifCopy);
const motifCopyValues = Object.values(motifCopy);
let newMotif = new Motif(motifCopy.id);
for(let i = 0; i < motifCopyKeys.length; i++) {
newMotif[motifCopyKeys[i]] = motifCopyValues[i];
}
// Fix x & y
if(motif.fit) {
newMotif.x = motif.fit.x;
newMotif.y = motif.fit.y;
}
// Add padding
newMotif.x += form.option.panelization[1].padding;
newMotif.y += form.option.panelization[1].padding;
// Milling
newMotif.w -= limit.milling;
newMotif.h -= limit.milling;
finalMotifs.push(newMotif);
});
return finalMotifs;
}
draw() {
// Clear
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Grid lines
if(this.gridLines && this.gridLines.length > 0) {
this.gridLines.forEach((line) => {
line.draw(this.ctx);
});
}
// Text
this.ctx.fillStyle = '#888';
this.ctx.font = '12px Arial';
this.ctx.textAlign = 'right';
this.ctx.textBaseline = 'baseline';
this.ctx.fillText(this.canvas.width + 'x' + this.canvas.height + 'mm', this.canvas.width - 10, this.canvas.height - 10);
// Base
this.base.draw(this.ctx);
// Motifs
if(this.motifs && this.motifs.length > 0) {
this.motifs.forEach((motif) => {
motif.draw(this.ctx);
});
}
// Milling lines
if(this.millingLines && this.millingLines.length > 0) {
this.millingLines.forEach((millingLine) => {
millingLine.draw(this.ctx);
});
}
}
}
Live preview:
http://jadvo.eu/
Panelized pattern -> Add pattern (Keep clicking it to add more patterns and you will see what's the problem)

Keep getting TypeError when using collision function

I keep getting this error when using points[i+2], it doesn't return an error, but doesn't work as supposed to either when using points[i+1] (line 60). I can't seem to figure out why this error is happening. I looked in the console and don't know what undefined should be.
var canvas = document.getElementById('canvas');
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
var ctx = canvas.getContext('2d');
var GLOBAL = {};
var point1 = [300, 300];
var point2 = [245, 55];
var point3 = [23, 143];
var point4 = [40, 200];
/*
* y = mx + b
* b = y - mx
* m = y - b/x
* x = y - b/m
*/
function lineSide(a, b, c) {
var m = (b[1] - a[1]) / (b[0] - a[0]);
var b = a[1] - (m * a[0]);
var result = function(x, y) {
if (y < m * x + b) return 0;
else if (y > m * x + b) return 1;
else if (y == m * x + b) return 2;
//TODO implement error handling
}(c[0], c[1]);
switch (result) {
case 0:
return 'lt';
break;
case 1:
return 'gt';
break;
case 2:
return 'eq';
break;
default:
return 'asdf';
break;
}
}
function inBounds(p, points) {
var bcheck = [],
pcheck = [];
for (var i = 0; i < points.length; i++) {
var pointa, pointb;
if (i + 1 >= points.length) {
pointa = points[i];
pointb = points[0];
bcheck.push(lineSide(pointa, pointb, points[1]));
} else {
pointa = points[i];
pointb = points[i + 1];
console.log(i + 2);
console.log(points[i + 2]);
bcheck.push(lineSide(pointa, pointb, points[i + 2])); // <-- THE PROBLEM AREA.
}
}
// console.log(bcheck);
for (var i = 0; i < points.length; i++) {
var pointa, pointb;
if (i + 1 >= points.length) {
pointa = points[i];
pointb = points[0];
pcheck.push(lineSide(pointa, pointb, p));
} else {
pointa = points[i];
pointb = points[i + 1];
pcheck.push(lineSide(pointa, pointb, p));
}
}
console.log(pcheck)
for (var i in bcheck) {
if (bcheck[i] != pcheck[i]) return false;
}
return true;
}
canvas.onmousemove = function(e) {
GLOBAL.mouseX = e.clientX;
GLOBAL.mouseY = e.clientY;
}
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (inBounds([GLOBAL.mouseX, GLOBAL.mouseY], [point1, point2, point3, point4])) {
ctx.fillStyle = 'red';
} else {
ctx.fillStyle = 'black';
}
ctx.beginPath();
ctx.moveTo(point1[0], point1[1]);
ctx.lineTo(point2[0], point2[1]);
ctx.lineTo(point3[0], point3[1]);
ctx.lineTo(point4[0], point4[1]);
ctx.closePath();
ctx.fill();
window.requestAnimationFrame(render);
}
render();
The loop inside inBounds() needs to be adjusted.
Either line 60 needs to only add by one:
bcheck.push(lineSide(pointa, pointb, points[i + 1])); // <-- THE PROBLEM AREA.
Or the if block on line 51 needs to add by two:
if (i + 2 >= points.length) {
I'm not entirely sure what you're trying to accomplish with the loop, so adjust as necessary.
jsFiddle of option #2.
The shape highlighting seems to break when the cursor exits through the bottom border. That's because the onmousemove event is only on the canvas element. You can change that event to be on the entire document, or add an onmouseleave event to the canvas element.
canvas.onmouseleave = function(e) {
GLOBAL.mouseX = undefined;
GLOBAL.mouseY = undefined;
};

Categories