| @@ -17,11 +17,11 @@ type Layer = { | |||||
| image?: ImageItem } | image?: ImageItem } | ||||
| type Line = | type Line = | ||||
| | { mode: Mode.Pen | Mode.Rubber | |||||
| | { mode: typeof Mode.Pen | typeof Mode.Rubber | |||||
| points: number[] | points: number[] | ||||
| stroke: string | stroke: string | ||||
| strokeWidth: number } | strokeWidth: number } | ||||
| | { mode: Mode.Paint | |||||
| | { mode: typeof Mode.Paint | |||||
| points: number[] | points: number[] | ||||
| stroke: string, | stroke: string, | ||||
| imageSrc: string } | imageSrc: string } | ||||
| @@ -29,7 +29,6 @@ type Line = | |||||
| const Mode = { | const Mode = { | ||||
| Pen: 'Pen', | Pen: 'Pen', | ||||
| Rubber: 'Rubber', | Rubber: 'Rubber', | ||||
| Image: 'Image', | |||||
| Paint: 'Paint' } as const | Paint: 'Paint' } as const | ||||
| type Mode = (typeof Mode)[keyof typeof Mode] | type Mode = (typeof Mode)[keyof typeof Mode] | ||||
| @@ -51,7 +50,7 @@ const floodFill = ( | |||||
| const start = getRGBA (data, sx, sy, W) | const start = getRGBA (data, sx, sy, W) | ||||
| const visited = new Uint8Array (W * H) | const visited = new Uint8Array (W * H) | ||||
| if (colourDiffMax (start, [fill.r, flil.g, fill.b, fill.a]) <= tolerance) | |||||
| if (colourDiffMax (start, [fill.r, fill.g, fill.b, fill.a]) <= tolerance) | |||||
| return | return | ||||
| const stack: [number, number][] = [[sx, sy]] | const stack: [number, number][] = [[sx, sy]] | ||||
| @@ -99,7 +98,7 @@ const getRGBA = (data: Uint8ClampedArray, x: number, y: number, w: number) => { | |||||
| const hexToRgba = (hex: string, alpha = 255): [number, number, number, number] => { | const hexToRgba = (hex: string, alpha = 255): [number, number, number, number] => { | ||||
| const m = /^#?([0-9a-f]{6})$/i.exec (hex) | const m = /^#?([0-9a-f]{6})$/i.exec (hex) | ||||
| const n = parseInt (m[1], 16) | |||||
| const n = parseInt (m![1], 16) | |||||
| return [(n >> 16) & 255, (n >> 8) & 255, n & 255, alpha] | return [(n >> 16) & 255, (n >> 8) & 255, n & 255, alpha] | ||||
| } | } | ||||
| @@ -110,21 +109,10 @@ const isLayer = (obj: unknown): obj is Layer => ( | |||||
| && typeof (obj as Layer).id === 'string' | && typeof (obj as Layer).id === 'string' | ||||
| && typeof (obj as Layer).name === 'string' | && typeof (obj as Layer).name === 'string' | ||||
| && Array.isArray ((obj as Layer).lines) | && Array.isArray ((obj as Layer).lines) | ||||
| && (obj as Layer).lines.every (isLine) | |||||
| && Array.isArray ((obj as Layer).history) | && Array.isArray ((obj as Layer).history) | ||||
| && Array.isArray ((obj as Layer).future)) | && Array.isArray ((obj as Layer).future)) | ||||
| const isLine = (obj: unknown): obj is Line => ( | |||||
| typeof obj === 'object' | |||||
| && obj !== null | |||||
| && Array.isArray ((obj as Line).points) | |||||
| && (obj as Line).points.every (n => typeof n === 'number') | |||||
| && typeof (obj as Line).stroke === 'string' | |||||
| && typeof (obj as Line).strokeWidth === 'number' | |||||
| && Object.values (Mode).includes ((obj as Line).mode)) | |||||
| export default () => { | export default () => { | ||||
| const drawingRef = useRef (false) | const drawingRef = useRef (false) | ||||
| const fileInputRef = useRef<HTMLInputElement> (null) | const fileInputRef = useRef<HTMLInputElement> (null) | ||||
| @@ -143,7 +131,7 @@ export default () => { | |||||
| const activeLayer = layers.find (l => l.id === activeLayerId) | const activeLayer = layers.find (l => l.id === activeLayerId) | ||||
| const handleMouseDown = (ev: Konva.KonvaEventObject<MouseEvent | TouchEvent>) => { | const handleMouseDown = (ev: Konva.KonvaEventObject<MouseEvent | TouchEvent>) => { | ||||
| if (!(activeLayer)) | |||||
| if (!(activeLayer) || (mode !== Mode.Pen && mode !== Mode.Rubber)) | |||||
| return | return | ||||
| drawingRef.current = true | drawingRef.current = true | ||||
| @@ -222,7 +210,7 @@ export default () => { | |||||
| fileInputRef.current.value = '' | fileInputRef.current.value = '' | ||||
| } | } | ||||
| const handlePaint = async (ev: Konva.KonvaEventObject<MouseEvent | TouchEvent>) => { | |||||
| const handlePaint = async () => { | |||||
| const layer = activeLayer | const layer = activeLayer | ||||
| if (!(layer) || !(stageRef.current)) | if (!(layer) || !(stageRef.current)) | ||||
| return | return | ||||
| @@ -245,14 +233,13 @@ export default () => { | |||||
| $off.width = stageWidth | $off.width = stageWidth | ||||
| $off.height = stageHeight | $off.height = stageHeight | ||||
| const ctx = $off.getContext ('2d') | const ctx = $off.getContext ('2d') | ||||
| ctx.drawImage (img, 0, 0, stageWidth, stageHeight) | |||||
| ctx!.drawImage (img, 0, 0, stageWidth, stageHeight) | |||||
| const imgData = ctx.getImageData (0, 0, stageWidth, stageHeight) | |||||
| 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 prevImageSrc = layer.image?.src | |||||
| const nextSrc = $off.toDataURL ('image/png') | const nextSrc = $off.toDataURL ('image/png') | ||||
| updateActiveLayerHistory ([...layer.history, layer.lines]) | updateActiveLayerHistory ([...layer.history, layer.lines]) | ||||
| @@ -347,11 +334,7 @@ export default () => { | |||||
| if (!(Array.isArray (parsed))) | if (!(Array.isArray (parsed))) | ||||
| throw new Error | throw new Error | ||||
| const restored: Layer[] = parsed.filter (isLayer).map (l => ({ | |||||
| ...l, lines: l.lines.map (s => ({ | |||||
| ...s, mode: (Object.values (Mode).includes (s.mode) | |||||
| ? s.mode | |||||
| : Mode.Pen) })) })) | |||||
| const restored: Layer[] = parsed.filter (isLayer) | |||||
| if (restored.length === 0) | if (restored.length === 0) | ||||
| throw new Error | throw new Error | ||||