This commit is contained in:
2025-08-10 17:19:56 +09:00
parent 235814aff7
commit e6eeb88c14
@@ -116,6 +116,7 @@ const isLayer = (obj: unknown): obj is Layer => (
export default () => { export default () => {
const drawingRef = useRef (false) const drawingRef = useRef (false)
const fileInputRef = useRef<HTMLInputElement> (null) const fileInputRef = useRef<HTMLInputElement> (null)
const layersRef = useRef<Record<string, Konva.Layer>> ({ })
const stageRef = useRef<Konva.Stage | null> (null) const stageRef = useRef<Konva.Stage | null> (null)
const [activeLayerId, setActiveLayerId] = useState<string | null> (null) const [activeLayerId, setActiveLayerId] = useState<string | null> (null)
@@ -125,6 +126,7 @@ export default () => {
const [layers, setLayers] = useState<Layer[]> ([]) const [layers, setLayers] = useState<Layer[]> ([])
const [mode, setMode] = useState<Mode> (Mode.Pen) const [mode, setMode] = useState<Mode> (Mode.Pen)
const [pointSize, setPointSize] = useState (3) const [pointSize, setPointSize] = useState (3)
const [singleLayer, setSingleLayer] = useState (false)
const [stageHeight, setStageHeight] = useState (480) const [stageHeight, setStageHeight] = useState (480)
const [stageWidth, setStageWidth] = useState (480) const [stageWidth, setStageWidth] = useState (480)
@@ -210,37 +212,31 @@ export default () => {
fileInputRef.current.value = '' fileInputRef.current.value = ''
} }
const handlePaint = async () => { const handlePaint = (ev: Konva.KonvaEventObject<PointerEvent | MouseEvent | TouchEvent>) => {
const layer = activeLayer const layer = activeLayer
if (!(layer) || !(stageRef.current)) if (!(layer) || !(layersRef.current?.[layer.id]))
return return
const stage = stageRef.current const pos = ev.target.getStage ()?.getPointerPosition ()
const pos = stage.getPointerPosition ()
if (!(pos)) if (!(pos))
return return
const dataURL = stage.toDataURL ({ mimeType: 'image/png', pixelRatio: 1 }) const stage = layersRef.current[layer.id]
if (!(stage))
return
const img = new window.Image if (drawingRef.current)
img.crossOrigin = 'anonymous' return
await new Promise<void> (res => {
img.onload = () => res ()
img.src = dataURL
})
const $off = document.createElement ('canvas') drawingRef.current = true
$off.width = stageWidth
$off.height = stageHeight const off = stage.toCanvas ({ pixelRatio: 1 })
const ctx = $off.getContext ('2d') const ctx = off.getContext ('2d')!
ctx!.drawImage (img, 0, 0, stageWidth, stageHeight) const imgData = ctx.getImageData (0, 0, off.width, off.height)
const imgData = ctx!.getImageData (0, 0, stageWidth, stageHeight)
const [r, g, b, a] = hexToRgba (colour, 255) const [r, g, b, a] = hexToRgba (colour, 255)
floodFill (imgData, Math.floor (pos.x), Math.floor (pos.y), { r, g, b, a }) floodFill (imgData, Math.floor (pos.x), Math.floor (pos.y), { r, g, b, a })
ctx!.putImageData (imgData, 0, 0) ctx.putImageData (imgData, 0, 0)
const nextSrc = $off.toDataURL ('image/png')
updateActiveLayerHistory ([...layer.history, layer.lines]) updateActiveLayerHistory ([...layer.history, layer.lines])
updateActiveLayerFuture ([]) updateActiveLayerFuture ([])
@@ -251,7 +247,9 @@ export default () => {
{ mode: Mode.Paint, { mode: Mode.Paint,
points: [pos.x, pos.y], points: [pos.x, pos.y],
stroke: colour, stroke: colour,
imageSrc: nextSrc }] }) : l)) imageSrc: off.toDataURL ('image/png') }] }) : l))
drawingRef.current = false
} }
const updateActiveLayerLines = (lines: Line[]) => { const updateActiveLayerLines = (lines: Line[]) => {
@@ -537,11 +535,19 @@ export default () => {
</button> </button>
</div> </div>
<label>
<input type="checkbox"
checked={singleLayer}
onChange={ev => setSingleLayer (ev.target.checked)} />
</label>
</div> </div>
<div className="w-full flex items-center justify-center mb-16"> <div className="w-full flex items-center justify-center mb-16">
<div className="border border-black dark:border-white"> <div className="border border-black dark:border-white">
<Stage ref={node => { <Stage className="touch-none"
ref={node => {
stageRef.current = node stageRef.current = node
}} }}
width={stageWidth} width={stageWidth}
@@ -561,8 +567,11 @@ export default () => {
listening={false} /> listening={false} />
</Layer> </Layer>
{layers.map (layer => ( {layers.map (layer => (!(singleLayer) || layer.id === activeLayerId) && (
<Layer key={layer.id}> <Layer key={layer.id} ref={node => {
if (node)
layersRef.current = { ...layersRef.current, [layer.id]: node }
}}>
{images[layer.id] && ( {images[layer.id] && (
<Image image={images[layer.id]} <Image image={images[layer.id]}
x={0} x={0}
@@ -587,7 +596,8 @@ export default () => {
: 'source-over'} />) : 'source-over'} />)
case Mode.Paint: case Mode.Paint:
return ( return (
<Image image={images[line.imageSrc]} <Image key={i}
image={images[line.imageSrc]}
x={0} x={0}
y={0} y={0}
scaleX={1} scaleX={1}