|
@@ -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 = stage.getPointerPosition () |
|
|
|
|
|
|
|
|
const pos = ev.target.getStage ()?.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 |
|
|
|
|
|
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) |
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
const nextSrc = $off.toDataURL ('image/png') |
|
|
|
|
|
|
|
|
ctx.putImageData (imgData, 0, 0) |
|
|
|
|
|
|
|
|
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 => ( |
|
|
|
|
|
<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] && ( |
|
|
{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} |
|
|