From 5eb5a036edd164cefa8f42c223a9415ca48e1909 Mon Sep 17 00:00:00 2001 From: Neil Graham Date: Mon, 11 Sep 2023 13:07:52 +1200 Subject: [PATCH] Moved functions to support.js Added layer reordering. --- web/page/bspaint.js | 286 +++++++++++++++++--------------------------- web/page/index.html | 1 + web/page/style.css | 10 +- web/page/support.js | 165 +++++++++++++++++++++++++ 4 files changed, 282 insertions(+), 180 deletions(-) create mode 100644 web/page/support.js diff --git a/web/page/bspaint.js b/web/page/bspaint.js index a1ec70b..73b528c 100644 --- a/web/page/bspaint.js +++ b/web/page/bspaint.js @@ -7,91 +7,20 @@ if (location.pathname.includes("/page/")) { $(initPaint); } -function cells(x=1,y=1) { - let width = 1/x; - let height = 1/y; - return strokes => { - let result = []; - for (let tx = -1; txa.map( - function ({x,y}) { - return { - x:(x + tx*width), - y:(y + ty*height), - } - })); - result.push(...cell); - } - } - return result; - } -} - -function mirrorX(strokes) { - return [...strokes, ...strokes.map( a=>a.map(({x,y})=>({x:1-x,y})))]; -} -function mirrorY(strokes) { - return [...strokes, ...strokes.map( a=>a.map(({x,y})=>({x,y:1-y})))]; -} - -function mirrorDiagonalXY(strokes) { - return [...strokes, ...strokes.map( a => a.map(({x, y}) => ({x: y, y: x})))]; -} - -function mirrorDiagonalY1MinusX(strokes) { - return [...strokes, ...strokes.map( a => a.map(({x, y}) => ({x: 1 - y, y: 1 - x})))]; -} - -function rotationalSymmetry(ways) { - const angleIncrement = (Math.PI * 2) / ways; - const originX = 0.5, originY = 0.5; - - return function(strokes) { - let newStrokes = [...strokes]; - - for(let i = 1; i < ways; i++) { - const angle = i * angleIncrement; - const cosAngle = Math.cos(angle); - const sinAngle = Math.sin(angle); - - const rotatedStrokes = strokes.map( a => a.map(({x, y}) => ({ - x: cosAngle * (x - originX) - sinAngle * (y - originY) + originX, - y: sinAngle * (x - originX) + cosAngle * (y - originY) + originY - }))); - - newStrokes = [...newStrokes, ...rotatedStrokes]; - } - - return newStrokes; - }; -} - - -function composeFunction(fnA,fnB) { - return (...args)=>(fnB(fnA(...args))) -} - -const mirrorXY = composeFunction(mirrorX,mirrorY); - -CanvasRenderingContext2D.prototype.getAllImageData = function() { - return this.getImageData(0,0,this.canvas.width,this.canvas.height) -} - -CanvasRenderingContext2D.prototype.setAllImageData = function(imageData) { - return this.putImageData(imageData,0,0) -} - var initialPalette = [ "#000000","#ffffff", + "#202020","#404040", + "#606060","#a0a0a0", + "#808080","#a0a0a0", + "#c0c060","#e0e0e0", + "#ff0000","#ff8000", "#ffff00","#008000", "#00ff00","#00ff80", "#0080ff","#50C0ff", "#0000ff","#8080ff", "#a00060","#ff00ff", - "#ffa0a0","#a06040", - "#606060","#a0a0a0"]; + "#ffa0a0","#a06040"]; var tip = { lastX : 0, @@ -121,11 +50,11 @@ function setActivePic(newValue) { } var dragging = false; - - var dragStartX; var dragStartY; +var draggingElement = null; + function setExportPic(pic) { selectedExport=pic; @@ -182,68 +111,6 @@ class Layer { } } -function blankCanvas(width=512, height=width, filled=true) { - const canvas = document.createElement("canvas"); - canvas.width=width; - canvas.height=height; - const ctx=canvas.getContext("2d"); - if (filled) { - ctx.fillStyle="white"; - ctx.fillRect(0,0,width,height); - } - canvas.ctx=ctx; - return canvas; -} - -function circleImage(diameter) { - - const imageSize=(diameter+4)|0; - let result = blankCanvas(imageSize,imageSize,false); - result.ctx.arc(imageSize/2,imageSize/2,diameter/2,0,Math.PI*2); - result.ctx.lineWidth=3; - result.ctx.strokeStyle="#000"; - result.ctx.stroke(); - result.ctx.lineWidth=1; - result.ctx.strokeStyle="#fff"; - result.ctx.stroke(); - return result; -} - -function gridImage(rows,columns,size=24) { - const result = blankCanvas(size,size,false); - const ctx = result.ctx; - const rowHeight = size/rows; - const columnWidth = size/columns; - for (let tx=0; txa; function updateMirrors() { let result = a=>a; + if($("#mirror_rotational.mirror.button").hasClass("down")) { + const ways = $(".rotational_mirrors input").val(); + const rotationFunction = rotationalSymmetry(ways); + result = composeFunction(result,rotationFunction); + } + if($("#mirror_grid.mirror.button").hasClass("down")) { const columns = $(".grid_mirrors #repeat_x").val(); const rows = $(".grid_mirrors #repeat_y").val(); @@ -272,11 +145,6 @@ function updateMirrors() { if($("#mirror_y.mirror.button").hasClass("down")) result = composeFunction(result,mirrorY); if($("#mirror_tlbr.mirror.button").hasClass("down")) result = composeFunction(result,mirrorDiagonalXY); if($("#mirror_trbl.mirror.button").hasClass("down")) result = composeFunction(result,mirrorDiagonalY1MinusX); - if($("#mirror_rotational.mirror.button").hasClass("down")) { - const ways = $(".rotational_mirrors input").val(); - const rotationFunction = rotationalSymmetry(ways); - result = composeFunction(result,rotationFunction); - } mirrorFunction=result; } @@ -308,8 +176,8 @@ function createDrawArea(canvas = blankCanvas()) { const image = canvas.ctx.getAllImageData(); - const undo = []; - const redo = []; + const undoStack = []; + const redoStack = []; const layers = []; layers.push( new Layer("base", canvas)); @@ -381,9 +249,9 @@ function createDrawArea(canvas = blankCanvas()) { layer: this.activeLayer, data: this.activeLayer.ctx.getAllImageData() } - undo.push(undoRecord); - if (undo.length > undoDepth) undo.shift(); - redo.length=0; + undoStack.push(undoRecord); + if (undoStack.length > undoDepth) undoStack.shift(); + redoStack.length=0; this.activeLayer.ctx.putImageData(data,0,0); this.isDrawing = false; @@ -469,11 +337,25 @@ function createDrawArea(canvas = blankCanvas()) { this.activeLayer = layers[Math.min(layers.length-1,i)]; } } + this.updateVisualRepresentation(true); }, addEmptyLayer() { let result = new Layer("new layer", canvas) - layers.push(result); + layers.push(result); return result; + }, + insertLayer(layer, below = null) { + const indexExisting = layers.indexOf(layer); + if (indexExisting !== -1) { + layers.splice(indexExisting, 1); + } + + const indexBelow = layers.indexOf(below); + if (indexBelow !== -1) { + layers.splice(indexBelow, 0, layer); + } else { + layers.push(layer); + } } } eventOverlay.pic = result; @@ -723,14 +605,6 @@ function handleMouseMove(e) { -function canvasPngAsBytes (canvas,callback) { - function handleBlob(blob) { - var reader = new FileReader(); - reader.onload = function () { callback(this.result); }; - reader.readAsArrayBuffer(blob); - } - canvas.toBlob(handleBlob); -} function handleMouseWheel(e) { let direction = -Math.sign(e.deltaY); @@ -744,10 +618,6 @@ function scalefactorToSize(n) { return (Math.pow(2,(n/2))); } -function insideBounds(point,bounds) { - if (!point) return false; - return (point.x>bounds.x && point.y>bounds.y && point.x -
+
@@ -996,12 +855,73 @@ function poulateLayerControl() { activePic.removeLayer(activePic.activeLayer) updateLayerList(); }); + + let dropzone =$(".layer_list.layer_dropzone"); + dropzone.on("dragover",e=>{ + dropzone.children().removeClass("insert_after"); + dropzone.removeClass("insert_top") + const insertPoint=findInsertPoint(dropzone[0],e); + if (insertPoint !== draggingElement) { + if (insertPoint) { + insertPoint.classList.add("insert_after"); + } else { + dropzone.addClass("insert_top") + } + e.preventDefault(); + } + }); + dropzone.on("drop", e=>{ + dropzone.removeClass("insert_top") + const insertPoint=findInsertPoint(dropzone[0],e); + if (insertPoint !== draggingElement) { + if (draggingElement) { + const layer = draggingElement.layer; + const below = insertPoint?.layer; + activePic.insertLayer(layer,below); + updateLayerList(); + activePic.updateVisualRepresentation(); + } else { + //handle file drop maybe? + } + + } + }) +} + +function findInsertPoint(dropzone, event) { + const dropzoneRect = dropzone.getBoundingClientRect(); + const children = Array.from(dropzone.children); + + // Normalize the event clientY coordinate to the dropzone's coordinate space + const eventClientY = event.clientY - dropzoneRect.top; + + for (let i = 0; i < children.length; i++) { + const child = children[i]; + const childRect = child.getBoundingClientRect(); + const childTop = childRect.top - dropzoneRect.top; + const childBottom = childTop + childRect.height; + + if (eventClientY >= childTop && eventClientY <= childBottom) { + if (child === draggingElement) { + return draggingElement; + } + if (eventClientY < childTop + childRect.height / 2) { + // Event is in the top half of the child element + return i === 0 ? null : children[i - 1]; + } else { + // Event is in the bottom half of the child element + return child; + } + } + } + + return children.length > 0 ? children[children.length - 1] : null; } function updateLayerList() { function makeLayerWidget(layer) { const pic=activePic; - const result = $(`
+ const result = $(`
${layer.title}
@@ -1031,10 +951,18 @@ function updateLayerList() { } result.onmousedown = e=> { if (e.button==0) { - pic.activeLayer=layer; - updateLayerList() + if (pic.activeLayer !== layer) { + pic.activeLayer=layer; + updateLayerList() + } } } + result.ondrag = e=>{ + draggingElement=e.currentTarget; + } + result.ondragend = e=> { + if (draggingElement === e.currentTarget ) draggingElement=null; + } return result; }; diff --git a/web/page/index.html b/web/page/index.html index e2981d9..49487fd 100644 --- a/web/page/index.html +++ b/web/page/index.html @@ -6,6 +6,7 @@ ComfyUI Canvas Editor + diff --git a/web/page/style.css b/web/page/style.css index 898d805..690d8ba 100644 --- a/web/page/style.css +++ b/web/page/style.css @@ -405,6 +405,15 @@ input[type="color"] { background: radial-gradient(ellipse at center, #ffffff 0%,#e0e6f9 11%,#5b9aff 43%,#3d8cf4 70%,#007fef 100%); } + +#layer_control .layer_list.insert_top { + border-top:3px solid red; +} + +#layer_control .layer_widget.insert_after { + border-bottom:3px solid red; +} + .layer_widget.active { background: cornsilk; } @@ -482,4 +491,3 @@ input.maskColor { width: 100%; text-align: center; } - diff --git a/web/page/support.js b/web/page/support.js new file mode 100644 index 0000000..be66695 --- /dev/null +++ b/web/page/support.js @@ -0,0 +1,165 @@ +// these are support functions for bspaint, +// Nothing in here should rely on global state + +function cells(x=1,y=1) { + let width = 1/x; + let height = 1/y; + return strokes => { + let result = []; + //this is ia bit inefficient, but handles the case where strokes go from one cell to another. + //could be optimized to measure the first point of a stroke set and to do operations relative to + //the cell that the first point occurs in. + for (let tx = -x; txa.map( + function ({x,y}) { + return { + x:(x + tx*width), + y:(y + ty*height), + } + })); + result.push(...cell); + } + } + return result; + } +} + +function mirrorX(strokes) { + return [...strokes, ...strokes.map( a=>a.map(({x,y})=>({x:1-x,y})))]; +} +function mirrorY(strokes) { + return [...strokes, ...strokes.map( a=>a.map(({x,y})=>({x,y:1-y})))]; +} + +function mirrorDiagonalXY(strokes) { + return [...strokes, ...strokes.map( a => a.map(({x, y}) => ({x: y, y: x})))]; +} + +function mirrorDiagonalY1MinusX(strokes) { + return [...strokes, ...strokes.map( a => a.map(({x, y}) => ({x: 1 - y, y: 1 - x})))]; +} + +function rotationalSymmetry(ways) { + const angleIncrement = (Math.PI * 2) / ways; + const originX = 0.5, originY = 0.5; + + return function(strokes) { + let newStrokes = [...strokes]; + + for(let i = 1; i < ways; i++) { + const angle = i * angleIncrement; + const cosAngle = Math.cos(angle); + const sinAngle = Math.sin(angle); + + const rotatedStrokes = strokes.map( a => a.map(({x, y}) => ({ + x: cosAngle * (x - originX) - sinAngle * (y - originY) + originX, + y: sinAngle * (x - originX) + cosAngle * (y - originY) + originY + }))); + + newStrokes = [...newStrokes, ...rotatedStrokes]; + } + + return newStrokes; + }; +} + + +function composeFunction(fnA,fnB) { + return (...args)=>(fnB(fnA(...args))) +} + +const mirrorXY = composeFunction(mirrorX,mirrorY); + +CanvasRenderingContext2D.prototype.getAllImageData = function() { + return this.getImageData(0,0,this.canvas.width,this.canvas.height) +} + +CanvasRenderingContext2D.prototype.setAllImageData = function(imageData) { + return this.putImageData(imageData,0,0) +} + +function blankCanvas(width=512, height=width, filled=true) { + const canvas = document.createElement("canvas"); + canvas.width=width; + canvas.height=height; + const ctx=canvas.getContext("2d"); + if (filled) { + ctx.fillStyle="white"; + ctx.fillRect(0,0,width,height); + } + canvas.ctx=ctx; + return canvas; +} + +function circleImage(diameter) { + + const imageSize=(diameter+4)|0; + let result = blankCanvas(imageSize,imageSize,false); + result.ctx.arc(imageSize/2,imageSize/2,diameter/2,0,Math.PI*2); + result.ctx.lineWidth=3; + result.ctx.strokeStyle="#000"; + result.ctx.stroke(); + result.ctx.lineWidth=1; + result.ctx.strokeStyle="#fff"; + result.ctx.stroke(); + return result; +} + +function gridImage(rows,columns,size=24) { + const result = blankCanvas(size,size,false); + const ctx = result.ctx; + const rowHeight = size/rows; + const columnWidth = size/columns; + for (let tx=0; txbounds.x && point.y>bounds.y && point.x