Paint bucket!
This commit is contained in:
parent
c27b349668
commit
4d1e42a38b
244
src/main.js
244
src/main.js
|
|
@ -3,7 +3,7 @@ import * as fitCurve from '/fit-curve.js';
|
|||
import { Bezier } from "/bezier.js";
|
||||
import { Quadtree } from './quadtree.js';
|
||||
import { createNewFileDialog, showNewFileDialog, closeDialog } from './newfile.js';
|
||||
import { titleCase, getMousePositionFraction, getKeyframesSurrounding, invertPixels, lerpColor, lerp, camelToWords, generateWaveform } from './utils.js';
|
||||
import { titleCase, getMousePositionFraction, getKeyframesSurrounding, invertPixels, lerpColor, lerp, camelToWords, generateWaveform, floodFillRegion, getShapeAtPoint } from './utils.js';
|
||||
const { writeTextFile: writeTextFile, readTextFile: readTextFile, writeFile: writeFile, readFile: readFile }= window.__TAURI__.fs;
|
||||
const {
|
||||
open: openFileDialog,
|
||||
|
|
@ -31,6 +31,10 @@ forwardConsole('info', info);
|
|||
forwardConsole('warn', warn);
|
||||
forwardConsole('error', error);
|
||||
|
||||
// Debug flags
|
||||
const debugQuadtree =false
|
||||
const debugPaintbucket = false
|
||||
|
||||
const macOS = navigator.userAgent.includes('Macintosh')
|
||||
|
||||
let simplifyPolyline = simplify
|
||||
|
|
@ -41,6 +45,9 @@ let rootPane;
|
|||
|
||||
let canvases = [];
|
||||
|
||||
let debugCurves = [];
|
||||
let debugPoints = [];
|
||||
|
||||
let mode = "select"
|
||||
|
||||
let minSegmentSize = 5;
|
||||
|
|
@ -113,6 +120,7 @@ let mouseEvent;
|
|||
|
||||
let context = {
|
||||
mouseDown: false,
|
||||
mousePos: {x: 0, y: 0},
|
||||
swatches: [
|
||||
"#000000",
|
||||
"#FFFFFF",
|
||||
|
|
@ -162,18 +170,27 @@ let startProps = {}
|
|||
|
||||
let actions = {
|
||||
addShape: {
|
||||
create: (parent, shape) => {
|
||||
create: (parent, shape, ctx) => {
|
||||
if (shape.curves.length==0) return;
|
||||
redoStack.length = 0; // Clear redo stack
|
||||
let serializableCurves = []
|
||||
for (let curve of shape.curves) {
|
||||
serializableCurves.push({ points: curve.points, color: curve.color })
|
||||
}
|
||||
let c = {
|
||||
...context,
|
||||
...ctx
|
||||
}
|
||||
let action = {
|
||||
parent: parent.idx,
|
||||
curves: serializableCurves,
|
||||
startx: shape.startx,
|
||||
starty: shape.starty,
|
||||
context: {
|
||||
fillShape: c.fillShape,
|
||||
strokeShape: c.strokeShape,
|
||||
fillStyle: c.fillStyle
|
||||
},
|
||||
uuid: uuidv4()
|
||||
}
|
||||
undoStack.push({name: "addShape", action: action})
|
||||
|
|
@ -183,7 +200,11 @@ let actions = {
|
|||
execute: (action) => {
|
||||
let object = pointerList[action.parent]
|
||||
let curvesList = action.curves
|
||||
let shape = new Shape(action.startx, action.starty, context, action.uuid)
|
||||
let cxt = {
|
||||
...context,
|
||||
...action.context
|
||||
}
|
||||
let shape = new Shape(action.startx, action.starty, cxt, action.uuid)
|
||||
for (let curve of curvesList) {
|
||||
shape.addCurve(new Bezier(
|
||||
curve.points[0].x, curve.points[0].y,
|
||||
|
|
@ -1363,6 +1384,9 @@ class BaseShape {
|
|||
} else {
|
||||
ctx.fillStyle = this.fillStyle
|
||||
}
|
||||
if (context.debugColor) {
|
||||
ctx.fillStyle = context.debugColor
|
||||
}
|
||||
if (this.curves.length > 0) {
|
||||
ctx.moveTo(this.curves[0].points[0].x, this.curves[0].points[0].y)
|
||||
for (let curve of this.curves) {
|
||||
|
|
@ -1373,7 +1397,7 @@ class BaseShape {
|
|||
}
|
||||
ctx.fill()
|
||||
}
|
||||
if (this.stroked) {
|
||||
if (this.stroked && !context.debugColor) {
|
||||
for (let curve of this.curves) {
|
||||
ctx.strokeStyle = curve.color
|
||||
ctx.beginPath()
|
||||
|
|
@ -1392,7 +1416,9 @@ class BaseShape {
|
|||
}
|
||||
}
|
||||
// Debug, show quadtree
|
||||
// this.quadtree.draw(ctx)
|
||||
if (debugQuadtree && this.quadtree && !context.debugColor) {
|
||||
this.quadtree.draw(ctx)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1458,6 +1484,7 @@ class Shape extends BaseShape {
|
|||
midpoint.x, midpoint.y,
|
||||
x, y)
|
||||
curve.color = context.strokeStyle
|
||||
this.quadtree.insert(curve, this.curves.length - 1)
|
||||
this.curves.push(curve)
|
||||
}
|
||||
bbox() {
|
||||
|
|
@ -1494,6 +1521,20 @@ class Shape extends BaseShape {
|
|||
|
||||
return newShape
|
||||
}
|
||||
fromPoints(points, error=30) {
|
||||
console.log(error)
|
||||
this.curves = []
|
||||
let curves = fitCurve.fitCurve(points, error)
|
||||
for (let curve of curves) {
|
||||
let bezier = new Bezier(curve[0][0], curve[0][1],
|
||||
curve[1][0],curve[1][1],
|
||||
curve[2][0], curve[2][1],
|
||||
curve[3][0], curve[3][1])
|
||||
this.curves.push(bezier)
|
||||
this.quadtree.insert(bezier, this.curves.length - 1)
|
||||
}
|
||||
return this
|
||||
}
|
||||
simplify(mode="corners") {
|
||||
this.quadtree.clear()
|
||||
this.inProgress = false
|
||||
|
|
@ -1524,17 +1565,7 @@ class Shape extends BaseShape {
|
|||
for (let curve of this.curves) {
|
||||
points.push([curve.points[3].x, curve.points[3].y])
|
||||
}
|
||||
this.curves = []
|
||||
let curves = fitCurve.fitCurve(points, error)
|
||||
for (let curve of curves) {
|
||||
let bezier = new Bezier(curve[0][0], curve[0][1],
|
||||
curve[1][0],curve[1][1],
|
||||
curve[2][0], curve[2][1],
|
||||
curve[3][0], curve[3][1])
|
||||
this.curves.push(bezier)
|
||||
this.quadtree.insert(bezier, this.curves.length - 1)
|
||||
|
||||
}
|
||||
this.fromPoints(points, error)
|
||||
}
|
||||
let epsilon = 0.01
|
||||
let newCurves = []
|
||||
|
|
@ -2184,6 +2215,8 @@ function _newFile(width, height, fps) {
|
|||
fileWidth = width
|
||||
fileHeight = height
|
||||
fileFps = fps
|
||||
undoStack = []
|
||||
redoStack = []
|
||||
for (let stage of document.querySelectorAll(".stage")) {
|
||||
stage.width = width
|
||||
stage.height = height
|
||||
|
|
@ -2695,6 +2728,139 @@ function stage() {
|
|||
break;
|
||||
case "paint_bucket":
|
||||
let line = {p1: mouse, p2: {x: mouse.x + 3000, y: mouse.y}}
|
||||
debugCurves = []
|
||||
debugPoints = []
|
||||
let epsilon = 5;
|
||||
let min_x = Infinity;
|
||||
let curveB = undefined
|
||||
let point = undefined
|
||||
let regionPoints
|
||||
|
||||
// First, see if there's an existing shape to change the color of
|
||||
const startTime = performance.now()
|
||||
let pointShape = getShapeAtPoint(mouse, context.activeObject.currentFrame.shapes)
|
||||
const endTime = performance.now()
|
||||
|
||||
console.log(pointShape)
|
||||
console.log(`getShapeAtPoint took ${endTime - startTime} milliseconds.`)
|
||||
|
||||
if (pointShape) {
|
||||
actions.colorShape.create(pointShape, context.fillStyle);
|
||||
break;
|
||||
}
|
||||
|
||||
// We didn't find an existing region to paintbucket, see if we can make one
|
||||
try {
|
||||
regionPoints = floodFillRegion(mouse,epsilon,fileWidth,fileHeight,context, debugPoints, debugPaintbucket)
|
||||
} catch (e) {
|
||||
updateUI()
|
||||
throw e;
|
||||
|
||||
}
|
||||
console.log(regionPoints.length)
|
||||
if (regionPoints.length>0 && regionPoints.length < 10) {
|
||||
// probably a very small area, rerun with minimum epsilon
|
||||
regionPoints = floodFillRegion(mouse,1,fileWidth,fileHeight,context, debugPoints)
|
||||
}
|
||||
let points = []
|
||||
for (let point of regionPoints) {
|
||||
points.push([point.x, point.y])
|
||||
}
|
||||
let cxt = {
|
||||
...context,
|
||||
fillShape: true,
|
||||
strokeShape: false,
|
||||
}
|
||||
let shape = new Shape(regionPoints[0].x, regionPoints[0].y, cxt)
|
||||
shape.fromPoints(points, 1)
|
||||
actions.addShape.create(context.activeObject, shape, cxt)
|
||||
/*
|
||||
for (let i in context.activeObject.currentFrame.shapes) {
|
||||
let shape = context.activeObject.currentFrame.shapes[i]
|
||||
for (let curve of shape.curves) {
|
||||
let intersects = curve.intersects(line)
|
||||
for (let intersect of intersects) {
|
||||
let pos = curve.compute(intersect)
|
||||
if (pos.x < min_x) {
|
||||
curveB = curve
|
||||
min_x = pos.x
|
||||
point = intersect
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let previousCurves = [];
|
||||
let previousCurveIds = [];
|
||||
if (curveB) {
|
||||
previousCurves.push(curveB.toString())
|
||||
previousCurveIds.push(curveB.toString())
|
||||
let derivative = curveB.derivative(point)
|
||||
// if curve is moving downward at intersection
|
||||
let splitCurve = curveB.split(point)
|
||||
let curveA;
|
||||
if (derivative.y > 0) {
|
||||
curveA = splitCurve.right
|
||||
} else {
|
||||
curveA = splitCurve.left.reverse()
|
||||
}
|
||||
// debugCurves.push(curveA)
|
||||
for (let i=0; i<2; i++) {
|
||||
|
||||
let min_intersect = Infinity
|
||||
let nextCurve = undefined
|
||||
for (let i in context.activeObject.currentFrame.shapes) {
|
||||
let shape = context.activeObject.currentFrame.shapes[i]
|
||||
for (let j of shape.quadtree.query(curveA.bbox())) {
|
||||
let curve = shape.curves[j]
|
||||
console.log(previousCurveIds)
|
||||
console.log(curve.toString())
|
||||
console.log(curve==(previousCurves.length?previousCurves[0]:undefined))
|
||||
console.log(curve.toString()==(previousCurves.length?previousCurves[0].toString():undefined))
|
||||
if (previousCurves.indexOf(curve.toString()) != -1) {
|
||||
console.log("skipping")
|
||||
continue;
|
||||
}
|
||||
let intersects = curveA.intersects(curve)
|
||||
// if (intersects.length > 4) continue;
|
||||
console.log(intersects)
|
||||
console.log(curve)
|
||||
for (let intersect of intersects) {
|
||||
intersect = intersect.split('/')
|
||||
let intersect_a = parseFloat(intersect[0])
|
||||
let intersect_b = parseFloat(intersect[1])
|
||||
if (intersect_a < min_intersect) {
|
||||
// console.log(curve)
|
||||
min_intersect = intersect_a
|
||||
nextCurve = curve
|
||||
point = intersect_b
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
curveB = nextCurve
|
||||
if (curveB) {
|
||||
// debugCurves.push(curveB)
|
||||
console.log(min_intersect)
|
||||
let splitCurve = curveB.split(point)
|
||||
let d_A = curveA.derivative(min_intersect)
|
||||
let d_B = curveB.derivative(point)
|
||||
curveA = curveA.split(min_intersect).left
|
||||
debugCurves.push(curveA)
|
||||
if ((d_A.x * d_B.y - d_A.y * d_B.x) < 0) {
|
||||
curveA = splitCurve.left.reverse()
|
||||
} else {
|
||||
curveA = splitCurve.right
|
||||
}
|
||||
// debugCurves.push(curveB)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// for (let)
|
||||
}
|
||||
*/
|
||||
break
|
||||
// Loop labels in JS!
|
||||
shapeLoop:
|
||||
// Iterate in reverse so we paintbucket the frontmost shape
|
||||
|
|
@ -3286,6 +3452,52 @@ function updateUI() {
|
|||
context.activeShape.draw(context)
|
||||
}
|
||||
|
||||
// Debug rendering
|
||||
if (debugQuadtree) {
|
||||
|
||||
ctx.fillStyle = "rgba(255,255,255,0.5)"
|
||||
ctx.fillRect(0,0,fileWidth,fileHeight)
|
||||
const ep = 2.5
|
||||
const bbox = {
|
||||
x: { min: context.mousePos.x - ep, max: context.mousePos.x + ep },
|
||||
y: { min: context.mousePos.y - ep, max: context.mousePos.y + ep }
|
||||
};
|
||||
debugCurves = []
|
||||
for (let shape of context.activeObject.currentFrame.shapes) {
|
||||
for (let i of shape.quadtree.query(bbox)) {
|
||||
debugCurves.push(shape.curves[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
// let i=4;
|
||||
for (let curve of debugCurves) {
|
||||
ctx.beginPath()
|
||||
// ctx.strokeStyle = `#ff${i}${i}${i}${i}`
|
||||
// i = (i+3)%10
|
||||
ctx.strokeStyle = '#'+(Math.random()*0xFFFFFF<<0).toString(16);
|
||||
ctx.moveTo(curve.points[0].x, curve.points[0].y)
|
||||
ctx.bezierCurveTo(
|
||||
curve.points[1].x, curve.points[1].y,
|
||||
curve.points[2].x, curve.points[2].y,
|
||||
curve.points[3].x, curve.points[3].y
|
||||
)
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
let bbox = curve.bbox()
|
||||
ctx.rect(bbox.x.min,bbox.y.min,bbox.x.max-bbox.x.min,bbox.y.max-bbox.y.min)
|
||||
ctx.stroke()
|
||||
}
|
||||
let i=0;
|
||||
for (let point of debugPoints) {
|
||||
ctx.beginPath()
|
||||
let j = i.toString(16).padStart(2, '0');
|
||||
ctx.fillStyle = `#${j}ff${j}`
|
||||
i+=1
|
||||
i %= 255
|
||||
ctx.arc(point.x, point.y, 3, 0, 2*Math.PI)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
}
|
||||
for (let selectionRect of document.querySelectorAll(".selectionRect")) {
|
||||
selectionRect.style.display = "none"
|
||||
|
|
|
|||
|
|
@ -41,7 +41,11 @@ class Quadtree {
|
|||
|
||||
insert (curve, curveIdx) {
|
||||
const bbox = curve.bbox()
|
||||
if (!this.intersects(curve.bbox())) {
|
||||
const isOutside = bbox.x.min < this.boundary.x.min ||
|
||||
bbox.x.max > this.boundary.x.max ||
|
||||
bbox.y.min < this.boundary.y.min ||
|
||||
bbox.y.max > this.boundary.y.max;
|
||||
if (isOutside) {
|
||||
let newNode = new Quadtree(this.boundary, this.capacity)
|
||||
newNode.curveIndexes = this.curveIndexes;
|
||||
newNode.curves = this.curves;
|
||||
|
|
@ -84,6 +88,10 @@ class Quadtree {
|
|||
|
||||
// Insert a curve into the quadtree, subdividing if necessary
|
||||
_insert(curve, curveIdx) {
|
||||
if (curve.points[0].x==381.703125) {
|
||||
console.log(this.intersects(curve.bbox()))
|
||||
console.log(this.boundary)
|
||||
}
|
||||
// If the curve's bounding box doesn't intersect this node's boundary, do nothing
|
||||
if (!this.intersects(curve.bbox())) {
|
||||
return false;
|
||||
|
|
@ -91,22 +99,31 @@ class Quadtree {
|
|||
|
||||
// If the node has space, insert the curve here
|
||||
if (this.curves.length < this.capacity) {
|
||||
|
||||
if (curve.points[0].x==381.703125) {
|
||||
"inserting"
|
||||
}
|
||||
this.curves.push(curve);
|
||||
this.curveIndexes.push(curveIdx)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (curve.points[0].x==381.703125) {
|
||||
console.log("going down a level")
|
||||
}
|
||||
|
||||
// Otherwise, subdivide and insert the curve into the appropriate quadrant
|
||||
if (!this.divided) {
|
||||
this.subdivide();
|
||||
}
|
||||
|
||||
return (
|
||||
this.nw._insert(curve, curveIdx) ||
|
||||
this.ne._insert(curve, curveIdx) ||
|
||||
this.sw._insert(curve, curveIdx) ||
|
||||
this.se._insert(curve, curveIdx)
|
||||
);
|
||||
const resultNw = this.nw._insert(curve, curveIdx);
|
||||
const resultNe = this.ne._insert(curve, curveIdx);
|
||||
const resultSw = this.sw._insert(curve, curveIdx);
|
||||
const resultSe = this.se._insert(curve, curveIdx);
|
||||
|
||||
// Return true if any of the insert operations returned true
|
||||
return resultNw || resultNe || resultSw || resultSe;
|
||||
}
|
||||
|
||||
// Query all curves that intersect with a given bounding box
|
||||
|
|
|
|||
165
src/utils.js
165
src/utils.js
|
|
@ -200,6 +200,167 @@ function generateWaveform(img, buffer, imgHeight, frameWidth, framesPerSecond) {
|
|||
img.src = dataUrl;
|
||||
}
|
||||
|
||||
function floodFillRegion(startPoint, epsilon, fileWidth, fileHeight, context, debugPoints, debugPaintbucket) {
|
||||
// Helper function to check if the point is at the boundary of the region
|
||||
function isBoundaryPoint(point) {
|
||||
return point.x <= 0 || point.x >= fileWidth || point.y <= 0 || point.y >= fileHeight;
|
||||
}
|
||||
let halfEpsilon = epsilon/2
|
||||
|
||||
// Helper function to check if a point is near any curve in the shape
|
||||
function isNearCurve(point, shape) {
|
||||
// Generate bounding box around the point for quadtree query
|
||||
const bbox = {
|
||||
x: { min: point.x - halfEpsilon, max: point.x + halfEpsilon },
|
||||
y: { min: point.y - halfEpsilon, max: point.y + halfEpsilon }
|
||||
};
|
||||
// Get the list of curve indices that are near the point
|
||||
const nearbyCurveIndices = shape.quadtree.query(bbox);
|
||||
// const nearbyCurveIndices = shape.curves.keys()
|
||||
// Check if any of the curves are close enough to the point
|
||||
for (const idx of nearbyCurveIndices) {
|
||||
const curve = shape.curves[idx];
|
||||
const projection = curve.project(point);
|
||||
if (projection.d < epsilon) {
|
||||
return projection;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const shapes = context.activeObject.currentFrame.shapes;
|
||||
const visited = new Set();
|
||||
const stack = [startPoint];
|
||||
const regionPoints = [];
|
||||
|
||||
// Begin the flood fill process
|
||||
while (stack.length > 0) {
|
||||
const currentPoint = stack.pop();
|
||||
|
||||
// If we reach the boundary of the region, throw an exception
|
||||
if (isBoundaryPoint(currentPoint)) {
|
||||
throw new Error("Flood fill reached the boundary of the area.");
|
||||
}
|
||||
|
||||
// If the current point is already visited, skip it
|
||||
const pointKey = `${currentPoint.x},${currentPoint.y}`;
|
||||
if (visited.has(pointKey)) {
|
||||
continue;
|
||||
}
|
||||
visited.add(pointKey);
|
||||
if (debugPaintbucket) {
|
||||
debugPoints.push(currentPoint)
|
||||
}
|
||||
|
||||
let isNearAnyCurve = false;
|
||||
for (const shape of shapes) {
|
||||
let projection = isNearCurve(currentPoint, shape)
|
||||
if (projection) {
|
||||
isNearAnyCurve = true;
|
||||
regionPoints.push(projection)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip the points that are near curves, to prevent jumping past them
|
||||
if (!isNearAnyCurve) {
|
||||
const neighbors = [
|
||||
{ x: currentPoint.x - epsilon, y: currentPoint.y },
|
||||
{ x: currentPoint.x + epsilon, y: currentPoint.y },
|
||||
{ x: currentPoint.x, y: currentPoint.y - epsilon },
|
||||
{ x: currentPoint.x, y: currentPoint.y + epsilon }
|
||||
];
|
||||
// Add unvisited neighbors to the stack
|
||||
for (const neighbor of neighbors) {
|
||||
const neighborKey = `${neighbor.x},${neighbor.y}`;
|
||||
if (!visited.has(neighborKey)) {
|
||||
stack.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the region points in connected order
|
||||
return sortPointsByProximity(regionPoints)
|
||||
}
|
||||
|
||||
function sortPointsByProximity(points) {
|
||||
if (points.length <= 1) return points;
|
||||
|
||||
// Start with the first point as the initial sorted point
|
||||
const sortedPoints = [points[0]];
|
||||
points.splice(0, 1); // Remove the first point from the original list
|
||||
|
||||
// Iterate through the remaining points and find the nearest neighbor
|
||||
while (points.length > 0) {
|
||||
const lastPoint = sortedPoints[sortedPoints.length - 1];
|
||||
|
||||
// Find the closest point to the last point
|
||||
let closestIndex = -1;
|
||||
let closestDistance = Infinity;
|
||||
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const currentPoint = points[i];
|
||||
const distance = Math.sqrt(Math.pow(currentPoint.x - lastPoint.x, 2) + Math.pow(currentPoint.y - lastPoint.y, 2));
|
||||
|
||||
if (distance < closestDistance) {
|
||||
closestDistance = distance;
|
||||
closestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the closest point to the sorted points
|
||||
sortedPoints.push(points[closestIndex]);
|
||||
points.splice(closestIndex, 1); // Remove the closest point from the original list
|
||||
}
|
||||
|
||||
return sortedPoints;
|
||||
}
|
||||
|
||||
function getShapeAtPoint(point, shapes) {
|
||||
// Create a 1x1 off-screen canvas and translate so it is in the first pixel
|
||||
const offscreenCanvas = document.createElement('canvas');
|
||||
offscreenCanvas.width = 1;
|
||||
offscreenCanvas.height = 1;
|
||||
const ctx = offscreenCanvas.getContext('2d');
|
||||
|
||||
ctx.translate(-point.x, -point.y);
|
||||
const colorToShapeMap = {};
|
||||
|
||||
// Generate a unique color for each shape (start from #000001 and increment)
|
||||
let colorIndex = 1;
|
||||
|
||||
// Draw all shapes to the off-screen canvas with their unique colors
|
||||
shapes.forEach(shape => {
|
||||
// Generate a unique color for this shape
|
||||
const debugColor = intToHexColor(colorIndex);
|
||||
colorToShapeMap[debugColor] = shape;
|
||||
|
||||
const context = {
|
||||
ctx: ctx,
|
||||
debugColor: debugColor
|
||||
};
|
||||
shape.draw(context);
|
||||
colorIndex++;
|
||||
});
|
||||
|
||||
const pixel = ctx.getImageData(0, 0, 1, 1).data;
|
||||
const sampledColor = rgbToHex(pixel[0], pixel[1], pixel[2]);
|
||||
return colorToShapeMap[sampledColor] || null;
|
||||
}
|
||||
|
||||
// Helper function to convert a number (0-16777215) to a hex color code
|
||||
function intToHexColor(value) {
|
||||
// Ensure the value is between 0 and 16777215 (0xFFFFFF)
|
||||
value = value & 0xFFFFFF;
|
||||
return '#' + value.toString(16).padStart(6, '0').toUpperCase();
|
||||
}
|
||||
|
||||
// Helper function to convert RGB to hex (for sampling)
|
||||
function rgbToHex(r, g, b) {
|
||||
return '#' + (1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1).toUpperCase();
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
titleCase,
|
||||
|
|
@ -209,5 +370,7 @@ export {
|
|||
lerp,
|
||||
lerpColor,
|
||||
camelToWords,
|
||||
generateWaveform
|
||||
generateWaveform,
|
||||
floodFillRegion,
|
||||
getShapeAtPoint
|
||||
};
|
||||
Loading…
Reference in New Issue