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