@lvl99dotcom/paintcanvas 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -15
- package/dist/lib/brushes/StrokeEngine/index.d.ts +2 -2
- package/dist/lib/brushes/StrokeEngine/index.js +26 -28
- package/dist/lib/brushes/presets/round/index.d.ts +1 -1
- package/dist/lib/brushes/presets/round/index.js +12 -7
- package/dist/lib/brushes/presets/square/index.d.ts +1 -1
- package/dist/lib/brushes/presets/square/index.js +7 -3
- package/dist/lib/brushes/shapeImage/index.d.ts +7 -3
- package/dist/lib/brushes/shapeImage/index.js +15 -9
- package/dist/lib/brushes/shapes/index.d.ts +18 -0
- package/dist/lib/brushes/shapes/index.js +86 -0
- package/dist/lib/classes/BrushSettings/index.d.ts +53 -2
- package/dist/lib/classes/BrushSettings/index.js +85 -14
- package/dist/lib/classes/KeyboardShortcuts/index.d.ts +47 -7
- package/dist/lib/classes/KeyboardShortcuts/index.js +145 -20
- package/dist/lib/classes/PaintCanvas/index.d.ts +123 -27
- package/dist/lib/classes/PaintCanvas/index.js +208 -41
- package/dist/lib/classes/PaintCanvas/tool.d.ts +15 -0
- package/dist/lib/classes/PaintCanvas/tool.js +18 -0
- package/dist/lib/classes/Palette/index.d.ts +58 -0
- package/dist/lib/classes/Palette/index.js +92 -0
- package/dist/lib/classes/SettingsAutosave/index.d.ts +53 -0
- package/dist/lib/classes/SettingsAutosave/index.js +98 -0
- package/dist/lib/classes/ShortcutSettings/index.d.ts +63 -0
- package/dist/lib/classes/ShortcutSettings/index.js +110 -0
- package/dist/lib/classes/UiSettings/index.d.ts +68 -0
- package/dist/lib/classes/UiSettings/index.js +82 -0
- package/dist/lib/palette/index.d.ts +49 -0
- package/dist/lib/palette/index.js +82 -0
- package/dist/lib/palette/schema.d.ts +45 -0
- package/dist/lib/palette/schema.js +41 -0
- package/dist/lib/rendering/cursors/index.d.ts +2 -0
- package/dist/lib/rendering/cursors/index.js +3 -1
- package/dist/lib/rendering/sample/index.js +3 -2
- package/dist/lib/settings/index.d.ts +63 -0
- package/dist/lib/settings/index.js +93 -0
- package/dist/lib/settings/schema.d.ts +168 -0
- package/dist/lib/settings/schema.js +116 -0
- package/dist/lib/storage/index.d.ts +36 -5
- package/dist/lib/storage/index.js +87 -29
- package/dist/lib/utils/colour/index.d.ts +72 -0
- package/dist/lib/utils/colour/index.js +154 -0
- package/dist/lib/utils/shortcuts/index.d.ts +174 -11
- package/dist/lib/utils/shortcuts/index.js +391 -145
- package/dist/lib/validation/validators.generated.d.ts +5 -0
- package/dist/lib/validation/validators.generated.js +1040 -1
- package/dist/locales/en-GB.js +96 -3
- package/dist/locales/en-US.js +96 -3
- package/dist/locales/fr.js +96 -3
- package/dist/locales/ja.js +96 -3
- package/dist/locales/ko.js +96 -3
- package/dist/locales/zh-Hans.js +96 -3
- package/dist/locales/zh-Hant.js +96 -3
- package/dist/package.js +1 -1
- package/dist/testing/recordingCanvas.js +2 -1
- package/dist/ui/base/index.d.ts +2 -2
- package/dist/ui/dismiss/index.d.ts +21 -0
- package/dist/ui/dismiss/index.js +27 -0
- package/dist/ui/drag/index.d.ts +51 -0
- package/dist/ui/drag/index.js +81 -0
- package/dist/ui/elements/PaintCanvas/index.d.ts +57 -50
- package/dist/ui/elements/PaintCanvas/index.js +141 -174
- package/dist/ui/elements/PaintCanvas/style.js +1 -1
- package/dist/ui/elements/PcAppSettings/index.d.ts +59 -0
- package/dist/ui/elements/PcAppSettings/index.js +222 -0
- package/dist/ui/elements/PcBrushConfig/index.d.ts +9 -2
- package/dist/ui/elements/PcBrushConfig/index.js +47 -7
- package/dist/ui/elements/PcBrushConfig/style.js +1 -1
- package/dist/ui/elements/PcBrushSize/index.d.ts +53 -0
- package/dist/ui/elements/PcBrushSize/index.js +66 -0
- package/dist/ui/elements/PcBrushTest/index.d.ts +26 -0
- package/dist/ui/elements/PcBrushTest/index.js +73 -5
- package/dist/ui/elements/PcBrushTest/style.js +1 -1
- package/dist/ui/elements/PcCanvas/index.d.ts +22 -2
- package/dist/ui/elements/PcCanvas/index.js +60 -15
- package/dist/ui/elements/PcCanvasMap/style.js +1 -1
- package/dist/ui/elements/PcClipboardTools/index.d.ts +48 -0
- package/dist/ui/elements/PcClipboardTools/index.js +118 -0
- package/dist/ui/elements/PcColourPair/index.d.ts +43 -0
- package/dist/ui/elements/PcColourPair/index.js +125 -0
- package/dist/ui/elements/PcContextMenu/index.d.ts +71 -34
- package/dist/ui/elements/PcContextMenu/index.js +128 -97
- package/dist/ui/elements/PcInputSettings/index.d.ts +13 -0
- package/dist/ui/elements/PcInputSettings/index.js +16 -1
- package/dist/ui/elements/PcLayerItem/index.d.ts +13 -34
- package/dist/ui/elements/PcLayerItem/index.js +39 -91
- package/dist/ui/elements/PcLayerItem/style.js +1 -1
- package/dist/ui/elements/PcMenu/index.d.ts +138 -0
- package/dist/ui/elements/PcMenu/index.js +296 -0
- package/dist/ui/elements/PcNumberSlider/index.d.ts +137 -0
- package/dist/ui/elements/PcNumberSlider/index.js +247 -0
- package/dist/ui/elements/PcPane/index.d.ts +120 -0
- package/dist/ui/elements/PcPane/index.js +394 -0
- package/dist/ui/elements/PcPanel/index.d.ts +26 -8
- package/dist/ui/elements/PcPanel/index.js +76 -8
- package/dist/ui/elements/{PcBrushMenu → PcPanelBrushes}/index.d.ts +22 -21
- package/dist/ui/elements/{PcBrushMenu → PcPanelBrushes}/index.js +24 -57
- package/dist/ui/elements/PcPanelBrushes/style.js +3 -0
- package/dist/ui/elements/PcPanelColours/index.d.ts +171 -0
- package/dist/ui/elements/PcPanelColours/index.js +650 -0
- package/dist/ui/elements/PcPanelColours/style.js +3 -0
- package/dist/ui/elements/{PcLayersMenu → PcPanelLayers}/index.d.ts +17 -4
- package/dist/ui/elements/{PcLayersMenu → PcPanelLayers}/index.js +17 -10
- package/dist/ui/elements/PcPanelLayers/style.js +3 -0
- package/dist/ui/elements/PcPopover/index.d.ts +39 -1
- package/dist/ui/elements/PcPopover/index.js +72 -4
- package/dist/ui/elements/PcShortcutSettings/index.d.ts +69 -0
- package/dist/ui/elements/PcShortcutSettings/index.js +273 -0
- package/dist/ui/elements/PcThemePicker/index.d.ts +17 -15
- package/dist/ui/elements/PcThemePicker/index.js +22 -21
- package/dist/ui/elements/PcToolSettings/index.d.ts +48 -9
- package/dist/ui/elements/PcToolSettings/index.js +384 -319
- package/dist/ui/elements/PcToolSettings/style.js +1 -1
- package/dist/ui/elements/PcToolbar/index.d.ts +62 -12
- package/dist/ui/elements/PcToolbar/index.js +119 -57
- package/dist/ui/elements/PcToolbar/style.js +1 -1
- package/dist/ui/icons/images/icon-brush@2x.js +3 -0
- package/dist/ui/icons/images/icon-circle-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-clipboard@2x.js +3 -0
- package/dist/ui/icons/images/icon-cog@2x.js +3 -0
- package/dist/ui/icons/images/icon-cursor@2x.js +3 -0
- package/dist/ui/icons/images/icon-diagonal-tlbr-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-diagonal-trbl-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-eraser@2x.js +3 -0
- package/dist/ui/icons/images/icon-eyedropper@2x.js +3 -0
- package/dist/ui/icons/images/icon-flip-horizontal@2x.js +3 -0
- package/dist/ui/icons/images/icon-flip-vertical@2x.js +3 -0
- package/dist/ui/icons/images/icon-focus-pen@2x.js +3 -0
- package/dist/ui/icons/images/icon-freehand-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-hidden@2x.js +3 -0
- package/dist/ui/icons/images/icon-horizontal-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-layers@2x.js +3 -0
- package/dist/ui/icons/images/icon-linked@2x.js +3 -0
- package/dist/ui/icons/images/icon-locked@2x.js +3 -0
- package/dist/ui/icons/images/icon-moon@2x.js +3 -0
- package/dist/ui/icons/images/icon-pan@2x.js +3 -0
- package/dist/ui/icons/images/icon-polygon-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-pressure@2x.js +3 -0
- package/dist/ui/icons/images/icon-randomise@2x.js +3 -0
- package/dist/ui/icons/images/icon-rectangle-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-redo@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate-90-left@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate-90-right@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-bezier-cubic@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-bezier-quad@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-linear@2x.js +3 -0
- package/dist/ui/icons/images/icon-star@2x.js +3 -0
- package/dist/ui/icons/images/icon-sun@2x.js +3 -0
- package/dist/ui/icons/images/icon-tilt@2x.js +3 -0
- package/dist/ui/icons/images/icon-undo@2x.js +3 -0
- package/dist/ui/icons/images/icon-unlinked@2x.js +3 -0
- package/dist/ui/icons/images/icon-unlocked@2x.js +3 -0
- package/dist/ui/icons/images/icon-vertical-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-visible@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom-in@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom-out@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom@2x.js +3 -0
- package/dist/ui/icons/index.d.ts +1 -1
- package/dist/ui/icons/index.js +84 -72
- package/dist/ui/index.d.ts +13 -2
- package/dist/ui/index.js +16 -4
- package/dist/ui/layout/index.d.ts +9 -1
- package/dist/ui/layout/index.js +11 -2
- package/dist/ui/tokens/index.d.ts +5 -4
- package/dist/ui/tokens/index.js +15 -8
- package/locales/en-GB.json +96 -3
- package/locales/en-US.json +96 -3
- package/locales/fr.json +96 -3
- package/locales/ja.json +96 -3
- package/locales/ko.json +96 -3
- package/locales/zh-Hans.json +96 -3
- package/locales/zh-Hant.json +96 -3
- package/package.json +11 -4
- package/dist/ui/elements/PcBrushMenu/style.js +0 -3
- package/dist/ui/elements/PcLayersMenu/style.js +0 -3
- package/dist/ui/icons/images/brush.js +0 -3
- package/dist/ui/icons/images/circle-mask.js +0 -3
- package/dist/ui/icons/images/clipboard.js +0 -3
- package/dist/ui/icons/images/cursor.js +0 -3
- package/dist/ui/icons/images/eraser.js +0 -3
- package/dist/ui/icons/images/eyedropper.js +0 -3
- package/dist/ui/icons/images/flip-horizontal.js +0 -3
- package/dist/ui/icons/images/flip-vertical.js +0 -3
- package/dist/ui/icons/images/focus-pen.js +0 -3
- package/dist/ui/icons/images/freehand-mask.js +0 -3
- package/dist/ui/icons/images/hidden.js +0 -3
- package/dist/ui/icons/images/layers.js +0 -3
- package/dist/ui/icons/images/linked.js +0 -3
- package/dist/ui/icons/images/locked.js +0 -3
- package/dist/ui/icons/images/moon.js +0 -3
- package/dist/ui/icons/images/pan.js +0 -3
- package/dist/ui/icons/images/polygon-mask.js +0 -3
- package/dist/ui/icons/images/pressure.js +0 -3
- package/dist/ui/icons/images/randomise.js +0 -3
- package/dist/ui/icons/images/rectangle-mask.js +0 -3
- package/dist/ui/icons/images/redo.js +0 -3
- package/dist/ui/icons/images/rotate-90-left.js +0 -3
- package/dist/ui/icons/images/rotate-90-right.js +0 -3
- package/dist/ui/icons/images/smoothing-bezier-cubic.js +0 -3
- package/dist/ui/icons/images/smoothing-bezier-quad.js +0 -3
- package/dist/ui/icons/images/smoothing-linear.js +0 -3
- package/dist/ui/icons/images/star.js +0 -3
- package/dist/ui/icons/images/sun.js +0 -3
- package/dist/ui/icons/images/tilt.js +0 -3
- package/dist/ui/icons/images/undo.js +0 -3
- package/dist/ui/icons/images/unlinked.js +0 -3
- package/dist/ui/icons/images/unlocked.js +0 -3
- package/dist/ui/icons/images/visible.js +0 -3
- package/dist/ui/icons/images/zoom-in.js +0 -3
- package/dist/ui/icons/images/zoom-out.js +0 -3
- package/dist/ui/icons/images/zoom.js +0 -3
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import StrokeLog from "../StrokeLog";
|
|
2
|
+
import { Tool } from "./tool";
|
|
3
|
+
import { isHexColour } from "../../utils/colour";
|
|
2
4
|
import BackgroundWork from "../BackgroundWork";
|
|
3
5
|
import type { DrawableLayer } from "../../rendering/compositing";
|
|
4
6
|
import BrushSettings from "../BrushSettings";
|
|
@@ -16,25 +18,14 @@ import type { LayerStoreLike } from "../../storage";
|
|
|
16
18
|
import type { AssetAuthor, BrushConfig } from "../../brushes/types";
|
|
17
19
|
import CustomPresets, { type PresetStoreLike } from "../CustomPresets";
|
|
18
20
|
import InputSettings from "../InputSettings";
|
|
21
|
+
import UiSettings from "../UiSettings";
|
|
22
|
+
import Palette from "../Palette";
|
|
23
|
+
import ShortcutSettings from "../ShortcutSettings";
|
|
19
24
|
import ExternalSheets from "../ExternalSheets";
|
|
20
25
|
import { type CanvasMessage, type PatchMessage, type StrokeMessage } from "../../messages";
|
|
21
26
|
import { type BoundingBox } from "../../image";
|
|
22
27
|
import { type PaintCanvasDocument } from "../../document";
|
|
23
|
-
|
|
24
|
-
export declare enum Tool {
|
|
25
|
-
Brush = "brush",
|
|
26
|
-
Eraser = "eraser",
|
|
27
|
-
/** Move, scale and rotate */
|
|
28
|
-
Move = "move",
|
|
29
|
-
RectangleLasso = "rectangle-lasso",
|
|
30
|
-
CircleLasso = "circle-lasso",
|
|
31
|
-
PolygonLasso = "polygon-lasso",
|
|
32
|
-
FreeformLasso = "freeform-lasso",
|
|
33
|
-
Pan = "pan",
|
|
34
|
-
Zoom = "zoom",
|
|
35
|
-
/** Set the current colour from the canvas (eyedropper) */
|
|
36
|
-
ColourSample = "colour-sample"
|
|
37
|
-
}
|
|
28
|
+
export { Tool };
|
|
38
29
|
/** How the colour sample tool samples: the pixel under the pointer, or the average of a dragged rectangle */
|
|
39
30
|
export type SampleMode = "point" | "area";
|
|
40
31
|
/** Toggleable panels */
|
|
@@ -43,7 +34,9 @@ export declare enum Panel {
|
|
|
43
34
|
Eraser = "eraser",
|
|
44
35
|
Layers = "layers",
|
|
45
36
|
/** The implementer's own app menu */
|
|
46
|
-
Menu = "menu"
|
|
37
|
+
Menu = "menu",
|
|
38
|
+
/** The colour palette: the foreground and background colours, their values and the saved colours */
|
|
39
|
+
Palette = "palette"
|
|
47
40
|
}
|
|
48
41
|
/** Panels opened by tools */
|
|
49
42
|
export declare const TOOL_PANELS: Partial<Record<Tool, Panel>>;
|
|
@@ -51,8 +44,15 @@ export declare const TOOL_PANELS: Partial<Record<Tool, Panel>>;
|
|
|
51
44
|
export declare const DEFAULT_CANVAS_SIZE = 2000;
|
|
52
45
|
/** Largest canvas width or height allowed when the `maxCanvasSize` option isn't given */
|
|
53
46
|
export declare const MAX_CANVAS_SIZE = 2000;
|
|
54
|
-
/**
|
|
47
|
+
/** Foreground colour to start with */
|
|
55
48
|
export declare const DEFAULT_COLOUR = "#000000";
|
|
49
|
+
/** Background colour to start with */
|
|
50
|
+
export declare const DEFAULT_BACKGROUND_COLOUR = "#ffffff";
|
|
51
|
+
/**
|
|
52
|
+
* One of the two colours: the foreground colour (painted by the brush and foreground fills) or the background colour
|
|
53
|
+
* (painted by the eraser and Delete when the layer's alpha lock is on, and by background fills).
|
|
54
|
+
*/
|
|
55
|
+
export type ColourSlot = "foreground" | "background";
|
|
56
56
|
/** Owner of the custom presets when the `ownerId` option isn't given */
|
|
57
57
|
export declare const DEFAULT_OWNER_ID = "local";
|
|
58
58
|
/** Author name of presets made with `newPreset` when the `presetAuthor` option isn't given */
|
|
@@ -65,11 +65,16 @@ export interface PaintCanvasNames {
|
|
|
65
65
|
filePrefix: string;
|
|
66
66
|
/** The `format` written in exported brush preset files */
|
|
67
67
|
presetFileFormat: string;
|
|
68
|
+
/** The `format` written in exported colour palette files */
|
|
69
|
+
paletteFileFormat: string;
|
|
70
|
+
/** The `format` written in exported settings files */
|
|
71
|
+
settingsFileFormat: string;
|
|
68
72
|
/** Author name of presets made by a user without a handle, e.g. from `presetAuthor` */
|
|
69
73
|
unknownAuthor: string;
|
|
70
74
|
}
|
|
71
75
|
/** PaintCanvas's own names */
|
|
72
76
|
export declare const DEFAULT_NAMES: Readonly<PaintCanvasNames>;
|
|
77
|
+
export { isHexColour };
|
|
73
78
|
/** How much the canvas holds, e.g. to show in a stats panel */
|
|
74
79
|
export interface CanvasStats {
|
|
75
80
|
/** Canvas width in pixels */
|
|
@@ -121,9 +126,16 @@ export interface PaintCanvasOptions {
|
|
|
121
126
|
names?: Partial<PaintCanvasNames>;
|
|
122
127
|
/** Where to keep the user's layers so the drawing survives reloading the page (see `useLayerStore`) */
|
|
123
128
|
layerStore?: LayerStoreLike;
|
|
129
|
+
/**
|
|
130
|
+
* The system clipboard, which copying and cutting also put a PNG of the copied image data on so other apps (and brush
|
|
131
|
+
* shapes) can use it (the browser's by default; `null` for none)
|
|
132
|
+
*/
|
|
133
|
+
systemClipboard?: SystemClipboardLike | null;
|
|
124
134
|
}
|
|
135
|
+
/** The part of the system clipboard PaintCanvas writes to (the browser's `navigator.clipboard`) */
|
|
136
|
+
export type SystemClipboardLike = Pick<Clipboard, "write">;
|
|
125
137
|
/**
|
|
126
|
-
* The painting engine: canvas size, layers, brush and eraser settings, custom presets, the selected tool,
|
|
138
|
+
* The painting engine: canvas size, layers, brush and eraser settings, custom presets, the selected tool, foreground and background colours,
|
|
127
139
|
* selection, transforms, clipboard, undo/redo history, viewport and input settings. Where its data is kept and who it
|
|
128
140
|
* is shared with are up to the implementer (see `onMessage` and `receive`).
|
|
129
141
|
*
|
|
@@ -134,6 +146,7 @@ export interface PaintCanvasOptions {
|
|
|
134
146
|
* const canvas = new PaintCanvas({ width: 1000, height: 800 });
|
|
135
147
|
* canvas.selectTool(Tool.Brush, { fromToolbar: true });
|
|
136
148
|
* canvas.setColour("#ff0000");
|
|
149
|
+
* canvas.setColour("#ffffff", "background");
|
|
137
150
|
*/
|
|
138
151
|
export default class PaintCanvas extends EventTarget {
|
|
139
152
|
/** Brush settings */
|
|
@@ -154,6 +167,12 @@ export default class PaintCanvas extends EventTarget {
|
|
|
154
167
|
readonly background: BackgroundWork;
|
|
155
168
|
/** How the user's input devices control the canvas (scrolling, pinching, touch gestures, focus pen mode) */
|
|
156
169
|
readonly input: InputSettings;
|
|
170
|
+
/** The user's interface choices (theme and language), kept with the other app settings */
|
|
171
|
+
readonly ui: UiSettings;
|
|
172
|
+
/** The user's saved colours, shown as swatches */
|
|
173
|
+
readonly palette: Palette;
|
|
174
|
+
/** The user's keyboard shortcuts: each configurable action's key combination */
|
|
175
|
+
readonly shortcuts: ShortcutSettings;
|
|
157
176
|
/** The user's pointer position over the canvas, in canvas pixels (undefined when off the canvas) */
|
|
158
177
|
pointer?: Point;
|
|
159
178
|
/** Selected tool */
|
|
@@ -190,9 +209,14 @@ export default class PaintCanvas extends EventTarget {
|
|
|
190
209
|
private sendingStrokes;
|
|
191
210
|
/** Whether a stroke from elsewhere is being drawn (it isn't sent again) */
|
|
192
211
|
private replaying;
|
|
193
|
-
private
|
|
212
|
+
private currentColours;
|
|
213
|
+
private currentSelectedColour;
|
|
194
214
|
/** Image data copied with cut or copy, and the area it was copied from */
|
|
195
215
|
private clipboard?;
|
|
216
|
+
/** How many times image data has been copied (see `copyCount`) */
|
|
217
|
+
private copies;
|
|
218
|
+
/** The system clipboard copies are also put on, if any */
|
|
219
|
+
private readonly systemClipboard?;
|
|
196
220
|
/** Drawings from elsewhere */
|
|
197
221
|
private externalSheets;
|
|
198
222
|
/** Who receives the drawing messages this canvas sends */
|
|
@@ -416,30 +440,83 @@ export default class PaintCanvas extends EventTarget {
|
|
|
416
440
|
*/
|
|
417
441
|
copy(): Rect | undefined;
|
|
418
442
|
/**
|
|
419
|
-
*
|
|
420
|
-
*
|
|
443
|
+
* How many times image data has been copied or cut, to tell whether it was copied here since something else may have
|
|
444
|
+
* been copied in another app (see `KeyboardShortcuts`).
|
|
445
|
+
*/
|
|
446
|
+
get copyCount(): number;
|
|
447
|
+
/** Whether there is copied image data to paste (see `copy` and `cut`) */
|
|
448
|
+
get hasClipboard(): boolean;
|
|
449
|
+
/**
|
|
450
|
+
* The copied image data as a PNG, if there is any (e.g. to use as a brush shape).
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* const png = await canvas.copiedPng(); // Blob of type "image/png", or undefined
|
|
454
|
+
*/
|
|
455
|
+
copiedPng(): Promise<Blob | undefined>;
|
|
456
|
+
/**
|
|
457
|
+
* Put copied image data on the system clipboard as a PNG, if there is one and the browser can. The item is made
|
|
458
|
+
* straight away (some browsers only allow writing while handling the key press or click) with the PNG to follow.
|
|
459
|
+
* Failing (e.g. the page isn't focused) changes nothing: the image data can still be pasted here.
|
|
460
|
+
*/
|
|
461
|
+
private writeSystemClipboard;
|
|
462
|
+
/**
|
|
463
|
+
* Copy the masked image data of the selected layer to the clipboard, then remove it from the layer as one undoable
|
|
464
|
+
* action (see `deleteMasked`).
|
|
421
465
|
*/
|
|
422
466
|
cut(): void;
|
|
423
467
|
/**
|
|
424
|
-
* Erase the masked image data from the selected layer as one undoable action (Delete or Backspace).
|
|
468
|
+
* Erase the masked image data from the selected layer as one undoable action (Delete or Backspace). When the layer's
|
|
469
|
+
* alpha lock is on, it is painted with the background colour instead, keeping its transparency. Needs a mask with
|
|
425
470
|
* image data inside it; does nothing during a stroke or while a transform is pending.
|
|
426
471
|
*/
|
|
427
472
|
deleteMasked(): void;
|
|
473
|
+
/**
|
|
474
|
+
* Fill the selected layer with the foreground or background colour as one undoable action: only the masked area when
|
|
475
|
+
* there is a mask (partly where it is partly), and only existing pixels when the layer's alpha lock is on. Does
|
|
476
|
+
* nothing during a stroke or while a transform is pending.
|
|
477
|
+
*
|
|
478
|
+
* @example
|
|
479
|
+
* canvas.fill("foreground");
|
|
480
|
+
*/
|
|
481
|
+
fill(slot: ColourSlot): void;
|
|
482
|
+
/**
|
|
483
|
+
* The area a fill changes: the mask's bounds (or the whole canvas without a mask), cut to the selected layer's image
|
|
484
|
+
* data when its alpha lock is on. None if that is empty.
|
|
485
|
+
*/
|
|
486
|
+
private fillArea;
|
|
428
487
|
/**
|
|
429
488
|
* The bounds of the selected layer's image data inside the mask, if there is a mask with image data inside it. None
|
|
430
489
|
* during a stroke or while a transform is pending.
|
|
431
490
|
*/
|
|
432
491
|
private maskedArea;
|
|
433
492
|
/**
|
|
434
|
-
* Erase the mask's part of an area from the selected layer,
|
|
493
|
+
* Erase the mask's part of an area from the selected layer, or paint it with the background colour when the layer's
|
|
494
|
+
* alpha lock is on, as one undoable action.
|
|
495
|
+
*/
|
|
496
|
+
private clearMasked;
|
|
497
|
+
/**
|
|
498
|
+
* Paint a colour over an area of the selected layer, cut to the mask if there is one (partly where it is partly), as
|
|
499
|
+
* one undoable action. Only existing pixels are painted when the layer's alpha lock is on.
|
|
435
500
|
*/
|
|
436
|
-
private
|
|
501
|
+
private paintArea;
|
|
437
502
|
/**
|
|
438
503
|
* Show the clipboard's image data over the selected layer where it was copied from, and select the Move tool to place,
|
|
439
504
|
* scale and rotate it. It is written into the layer (and sent) on Enter or when switching to another tool; Escape
|
|
440
505
|
* discards it. A pending transform is applied first.
|
|
441
506
|
*/
|
|
442
507
|
paste(): void;
|
|
508
|
+
/**
|
|
509
|
+
* Show an image from elsewhere (e.g. pasted from another app) over the selected layer, in the middle of the part of
|
|
510
|
+
* the canvas in view, and select the Move tool to place, scale and rotate it, like `paste`. An image larger than the
|
|
511
|
+
* canvas is shrunk to fit it. Does nothing during a stroke.
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* canvas.pasteImage(await createImageBitmap(file));
|
|
515
|
+
*/
|
|
516
|
+
pasteImage(source: CanvasImageSource & {
|
|
517
|
+
width: number;
|
|
518
|
+
height: number;
|
|
519
|
+
}): void;
|
|
443
520
|
/**
|
|
444
521
|
* Apply a finished lasso action to the mask: a shape is added, subtracted or replaces it; a plain click deselects.
|
|
445
522
|
*/
|
|
@@ -468,12 +545,31 @@ export default class PaintCanvas extends EventTarget {
|
|
|
468
545
|
get activeTool(): Tool;
|
|
469
546
|
/** The user's layers */
|
|
470
547
|
get layers(): LayerStack;
|
|
471
|
-
/** Current
|
|
548
|
+
/** Current foreground colour as a hex string, e.g. `#000000` (the same as `colours.foreground`) */
|
|
472
549
|
get colour(): string;
|
|
550
|
+
/** Current foreground and background colours as hex strings, e.g. `{ foreground: "#000000", background: "#ffffff" }` */
|
|
551
|
+
get colours(): Readonly<Record<ColourSlot, string>>;
|
|
552
|
+
/** Which colour is selected for configuration, e.g. by a colour picker or the colour sample tool ("foreground" by default) */
|
|
553
|
+
get selectedColour(): ColourSlot;
|
|
554
|
+
/**
|
|
555
|
+
* Set the foreground colour, or the background colour. Must be a 6-digit hex colour, e.g. `#ff0000`.
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* canvas.setColour("#ff0000"); // foreground
|
|
559
|
+
* canvas.setColour("#ffffff", "background");
|
|
560
|
+
*/
|
|
561
|
+
setColour(colour: string, slot?: ColourSlot): void;
|
|
562
|
+
/**
|
|
563
|
+
* Select which colour is configured, e.g. by a colour picker or the colour sample tool. One colour is always selected.
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* canvas.selectColour("background");
|
|
567
|
+
*/
|
|
568
|
+
selectColour(slot: ColourSlot): void;
|
|
473
569
|
/**
|
|
474
|
-
*
|
|
570
|
+
* Swap the foreground and background colours. The selected colour stays the same slot.
|
|
475
571
|
*/
|
|
476
|
-
|
|
572
|
+
swapColours(): void;
|
|
477
573
|
/**
|
|
478
574
|
* Change the canvas size, from 1 to the maximum canvas size (`maxCanvasSize` option). Replaces all layers.
|
|
479
575
|
*/
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import StrokeLog from "../StrokeLog/index.js";
|
|
2
|
+
import { Tool } from "./tool.js";
|
|
3
|
+
import { isHexColour } from "../../utils/colour/index.js";
|
|
2
4
|
import BackgroundWork from "../BackgroundWork/index.js";
|
|
3
5
|
import BrushSettings from "../BrushSettings/index.js";
|
|
4
6
|
import LayerStack from "../LayerStack/index.js";
|
|
@@ -17,6 +19,12 @@ import "../../presets/index.js";
|
|
|
17
19
|
import CustomPresets from "../CustomPresets/index.js";
|
|
18
20
|
import { BRUSH_PRESET_FILE_FORMAT } from "../../presets/schema.js";
|
|
19
21
|
import InputSettings from "../InputSettings/index.js";
|
|
22
|
+
import UiSettings from "../UiSettings/index.js";
|
|
23
|
+
import Palette from "../Palette/index.js";
|
|
24
|
+
import ShortcutSettings from "../ShortcutSettings/index.js";
|
|
25
|
+
import { PALETTE_FILE_FORMAT } from "../../palette/schema.js";
|
|
26
|
+
import "../../palette/index.js";
|
|
27
|
+
import { SETTINGS_FILE_FORMAT } from "../../settings/schema.js";
|
|
20
28
|
import ExternalSheets from "../ExternalSheets/index.js";
|
|
21
29
|
import PatchSender from "../../messages/PatchSender/index.js";
|
|
22
30
|
import { createPatchMessage, validateCanvasMessage } from "../../messages/index.js";
|
|
@@ -28,22 +36,6 @@ import { DOCUMENT_FORMAT } from "../../document/schema.js";
|
|
|
28
36
|
import { isValidDocument } from "../../document/index.js";
|
|
29
37
|
import { averageColour, flattenArea, pixelRect, readPixels } from "../../rendering/sample/index.js";
|
|
30
38
|
// src/lib/classes/PaintCanvas/index.ts
|
|
31
|
-
/** Drawing and navigation tools */
|
|
32
|
-
var Tool = /* @__PURE__ */ function(Tool) {
|
|
33
|
-
Tool["Brush"] = "brush";
|
|
34
|
-
Tool["Eraser"] = "eraser";
|
|
35
|
-
/** Move, scale and rotate */
|
|
36
|
-
Tool["Move"] = "move";
|
|
37
|
-
Tool["RectangleLasso"] = "rectangle-lasso";
|
|
38
|
-
Tool["CircleLasso"] = "circle-lasso";
|
|
39
|
-
Tool["PolygonLasso"] = "polygon-lasso";
|
|
40
|
-
Tool["FreeformLasso"] = "freeform-lasso";
|
|
41
|
-
Tool["Pan"] = "pan";
|
|
42
|
-
Tool["Zoom"] = "zoom";
|
|
43
|
-
/** Set the current colour from the canvas (eyedropper) */
|
|
44
|
-
Tool["ColourSample"] = "colour-sample";
|
|
45
|
-
return Tool;
|
|
46
|
-
}({});
|
|
47
39
|
/** Toggleable panels */
|
|
48
40
|
var Panel = /* @__PURE__ */ function(Panel) {
|
|
49
41
|
Panel["Brush"] = "brush";
|
|
@@ -51,19 +43,23 @@ var Panel = /* @__PURE__ */ function(Panel) {
|
|
|
51
43
|
Panel["Layers"] = "layers";
|
|
52
44
|
/** The implementer's own app menu */
|
|
53
45
|
Panel["Menu"] = "menu";
|
|
46
|
+
/** The colour palette: the foreground and background colours, their values and the saved colours */
|
|
47
|
+
Panel["Palette"] = "palette";
|
|
54
48
|
return Panel;
|
|
55
49
|
}({});
|
|
56
50
|
/** Panels opened by tools */
|
|
57
51
|
var TOOL_PANELS = {
|
|
58
|
-
[
|
|
59
|
-
[
|
|
52
|
+
[Tool.Brush]: "brush",
|
|
53
|
+
[Tool.Eraser]: "eraser"
|
|
60
54
|
};
|
|
61
55
|
/** Canvas width and height when none is given */
|
|
62
56
|
var DEFAULT_CANVAS_SIZE = 2e3;
|
|
63
57
|
/** Largest canvas width or height allowed when the `maxCanvasSize` option isn't given */
|
|
64
58
|
var MAX_CANVAS_SIZE = 2e3;
|
|
65
|
-
/**
|
|
59
|
+
/** Foreground colour to start with */
|
|
66
60
|
var DEFAULT_COLOUR = "#000000";
|
|
61
|
+
/** Background colour to start with */
|
|
62
|
+
var DEFAULT_BACKGROUND_COLOUR = "#ffffff";
|
|
67
63
|
/** Owner of the custom presets when the `ownerId` option isn't given */
|
|
68
64
|
var DEFAULT_OWNER_ID = "local";
|
|
69
65
|
/** Author name of presets made with `newPreset` when the `presetAuthor` option isn't given */
|
|
@@ -73,10 +69,10 @@ var DEFAULT_NAMES = {
|
|
|
73
69
|
app: BUILT_IN_AUTHOR,
|
|
74
70
|
filePrefix: "paintcanvas",
|
|
75
71
|
presetFileFormat: BRUSH_PRESET_FILE_FORMAT,
|
|
72
|
+
paletteFileFormat: PALETTE_FILE_FORMAT,
|
|
73
|
+
settingsFileFormat: SETTINGS_FILE_FORMAT,
|
|
76
74
|
unknownAuthor: UNKNOWN_AUTHOR
|
|
77
75
|
};
|
|
78
|
-
/** Pattern for colour input values */
|
|
79
|
-
var COLOUR_PATTERN = /^#[0-9a-f]{6}$/i;
|
|
80
76
|
/**
|
|
81
77
|
* A canvas's pixels as a PNG data URL.
|
|
82
78
|
*/
|
|
@@ -98,7 +94,7 @@ var decodePatch = async (patch) => {
|
|
|
98
94
|
return new ImageData(await decodeRgbaDeflate(patch.data, patch.coords), width, height);
|
|
99
95
|
};
|
|
100
96
|
/**
|
|
101
|
-
* The painting engine: canvas size, layers, brush and eraser settings, custom presets, the selected tool,
|
|
97
|
+
* The painting engine: canvas size, layers, brush and eraser settings, custom presets, the selected tool, foreground and background colours,
|
|
102
98
|
* selection, transforms, clipboard, undo/redo history, viewport and input settings. Where its data is kept and who it
|
|
103
99
|
* is shared with are up to the implementer (see `onMessage` and `receive`).
|
|
104
100
|
*
|
|
@@ -109,6 +105,7 @@ var decodePatch = async (patch) => {
|
|
|
109
105
|
* const canvas = new PaintCanvas({ width: 1000, height: 800 });
|
|
110
106
|
* canvas.selectTool(Tool.Brush, { fromToolbar: true });
|
|
111
107
|
* canvas.setColour("#ff0000");
|
|
108
|
+
* canvas.setColour("#ffffff", "background");
|
|
112
109
|
*/
|
|
113
110
|
var PaintCanvas = class extends EventTarget {
|
|
114
111
|
/** Brush settings */
|
|
@@ -129,10 +126,16 @@ var PaintCanvas = class extends EventTarget {
|
|
|
129
126
|
background = new BackgroundWork(this.strokeLog);
|
|
130
127
|
/** How the user's input devices control the canvas (scrolling, pinching, touch gestures, focus pen mode) */
|
|
131
128
|
input = new InputSettings();
|
|
129
|
+
/** The user's interface choices (theme and language), kept with the other app settings */
|
|
130
|
+
ui = new UiSettings();
|
|
131
|
+
/** The user's saved colours, shown as swatches */
|
|
132
|
+
palette = new Palette();
|
|
133
|
+
/** The user's keyboard shortcuts: each configurable action's key combination */
|
|
134
|
+
shortcuts = new ShortcutSettings();
|
|
132
135
|
/** The user's pointer position over the canvas, in canvas pixels (undefined when off the canvas) */
|
|
133
136
|
pointer;
|
|
134
137
|
/** Selected tool */
|
|
135
|
-
tool =
|
|
138
|
+
tool = Tool.Brush;
|
|
136
139
|
/** Tool used temporarily while a key is held (e.g. Space for pan), overriding the selected tool */
|
|
137
140
|
heldTool = null;
|
|
138
141
|
/** Open panel, if any */
|
|
@@ -186,9 +189,17 @@ var PaintCanvas = class extends EventTarget {
|
|
|
186
189
|
sendingStrokes = false;
|
|
187
190
|
/** Whether a stroke from elsewhere is being drawn (it isn't sent again) */
|
|
188
191
|
replaying = false;
|
|
189
|
-
|
|
192
|
+
currentColours = {
|
|
193
|
+
foreground: DEFAULT_COLOUR,
|
|
194
|
+
background: DEFAULT_BACKGROUND_COLOUR
|
|
195
|
+
};
|
|
196
|
+
currentSelectedColour = "foreground";
|
|
190
197
|
/** Image data copied with cut or copy, and the area it was copied from */
|
|
191
198
|
clipboard;
|
|
199
|
+
/** How many times image data has been copied (see `copyCount`) */
|
|
200
|
+
copies = 0;
|
|
201
|
+
/** The system clipboard copies are also put on, if any */
|
|
202
|
+
systemClipboard;
|
|
192
203
|
/** Drawings from elsewhere */
|
|
193
204
|
externalSheets;
|
|
194
205
|
/** Who receives the drawing messages this canvas sends */
|
|
@@ -229,6 +240,7 @@ var PaintCanvas = class extends EventTarget {
|
|
|
229
240
|
this.customPresets.addEventListener("change", () => this.listCustomPresets());
|
|
230
241
|
this.customPresets.load().catch(() => void 0);
|
|
231
242
|
this.createCanvas = options.createCanvas ?? createOffscreenCanvas;
|
|
243
|
+
this.systemClipboard = options.systemClipboard === null ? void 0 : options.systemClipboard ?? globalThis.navigator?.clipboard;
|
|
232
244
|
this.canvasWidth = DEFAULT_CANVAS_SIZE;
|
|
233
245
|
this.canvasHeight = DEFAULT_CANVAS_SIZE;
|
|
234
246
|
this.layerStack = new LayerStack(this.canvasWidth, this.canvasHeight, this.createCanvas, this.maxLayers);
|
|
@@ -642,7 +654,7 @@ var PaintCanvas = class extends EventTarget {
|
|
|
642
654
|
*/
|
|
643
655
|
syncTransform() {
|
|
644
656
|
const transformer = this.transformer;
|
|
645
|
-
if (this.tool !==
|
|
657
|
+
if (this.tool !== Tool.Move) {
|
|
646
658
|
if (transformer.pending) this.commitTransform();
|
|
647
659
|
else if (transformer.active) {
|
|
648
660
|
transformer.reset();
|
|
@@ -713,23 +725,87 @@ var PaintCanvas = class extends EventTarget {
|
|
|
713
725
|
image,
|
|
714
726
|
area
|
|
715
727
|
};
|
|
728
|
+
this.copies++;
|
|
729
|
+
this.writeSystemClipboard(image);
|
|
730
|
+
this.notify();
|
|
716
731
|
return area;
|
|
717
732
|
}
|
|
718
733
|
/**
|
|
719
|
-
*
|
|
720
|
-
*
|
|
734
|
+
* How many times image data has been copied or cut, to tell whether it was copied here since something else may have
|
|
735
|
+
* been copied in another app (see `KeyboardShortcuts`).
|
|
736
|
+
*/
|
|
737
|
+
get copyCount() {
|
|
738
|
+
return this.copies;
|
|
739
|
+
}
|
|
740
|
+
/** Whether there is copied image data to paste (see `copy` and `cut`) */
|
|
741
|
+
get hasClipboard() {
|
|
742
|
+
return this.clipboard !== void 0;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* The copied image data as a PNG, if there is any (e.g. to use as a brush shape).
|
|
746
|
+
*
|
|
747
|
+
* @example
|
|
748
|
+
* const png = await canvas.copiedPng(); // Blob of type "image/png", or undefined
|
|
749
|
+
*/
|
|
750
|
+
async copiedPng() {
|
|
751
|
+
return this.clipboard?.image.convertToBlob({ type: "image/png" });
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Put copied image data on the system clipboard as a PNG, if there is one and the browser can. The item is made
|
|
755
|
+
* straight away (some browsers only allow writing while handling the key press or click) with the PNG to follow.
|
|
756
|
+
* Failing (e.g. the page isn't focused) changes nothing: the image data can still be pasted here.
|
|
757
|
+
*/
|
|
758
|
+
writeSystemClipboard(image) {
|
|
759
|
+
if (!this.systemClipboard || typeof ClipboardItem === "undefined") return;
|
|
760
|
+
const item = new ClipboardItem({ "image/png": image.convertToBlob({ type: "image/png" }) });
|
|
761
|
+
this.systemClipboard.write([item]).catch(() => void 0);
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Copy the masked image data of the selected layer to the clipboard, then remove it from the layer as one undoable
|
|
765
|
+
* action (see `deleteMasked`).
|
|
721
766
|
*/
|
|
722
767
|
cut() {
|
|
723
768
|
const area = this.copy();
|
|
724
|
-
if (area) this.
|
|
769
|
+
if (area) this.clearMasked(area);
|
|
725
770
|
}
|
|
726
771
|
/**
|
|
727
|
-
* Erase the masked image data from the selected layer as one undoable action (Delete or Backspace).
|
|
772
|
+
* Erase the masked image data from the selected layer as one undoable action (Delete or Backspace). When the layer's
|
|
773
|
+
* alpha lock is on, it is painted with the background colour instead, keeping its transparency. Needs a mask with
|
|
728
774
|
* image data inside it; does nothing during a stroke or while a transform is pending.
|
|
729
775
|
*/
|
|
730
776
|
deleteMasked() {
|
|
731
777
|
const area = this.maskedArea();
|
|
732
|
-
if (area) this.
|
|
778
|
+
if (area) this.clearMasked(area);
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Fill the selected layer with the foreground or background colour as one undoable action: only the masked area when
|
|
782
|
+
* there is a mask (partly where it is partly), and only existing pixels when the layer's alpha lock is on. Does
|
|
783
|
+
* nothing during a stroke or while a transform is pending.
|
|
784
|
+
*
|
|
785
|
+
* @example
|
|
786
|
+
* canvas.fill("foreground");
|
|
787
|
+
*/
|
|
788
|
+
fill(slot) {
|
|
789
|
+
if (this.strokeEngine.drawing || this.transformer.pending) return;
|
|
790
|
+
const area = this.fillArea();
|
|
791
|
+
if (area) this.paintArea(area, this.currentColours[slot]);
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* The area a fill changes: the mask's bounds (or the whole canvas without a mask), cut to the selected layer's image
|
|
795
|
+
* data when its alpha lock is on. None if that is empty.
|
|
796
|
+
*/
|
|
797
|
+
fillArea() {
|
|
798
|
+
const { width, height } = this;
|
|
799
|
+
const layer = this.layerStack.active;
|
|
800
|
+
const mask = this.currentSelection.mask;
|
|
801
|
+
const maskData = mask ? mask.getContext("2d").getImageData(0, 0, width, height).data : void 0;
|
|
802
|
+
if (layer.alphaLock) return contentBounds(layer.context.getImageData(0, 0, width, height).data, width, height, maskData);
|
|
803
|
+
return maskData ? contentBounds(maskData, width, height) : {
|
|
804
|
+
x: 0,
|
|
805
|
+
y: 0,
|
|
806
|
+
width,
|
|
807
|
+
height
|
|
808
|
+
};
|
|
733
809
|
}
|
|
734
810
|
/**
|
|
735
811
|
* The bounds of the selected layer's image data inside the mask, if there is a mask with image data inside it. None
|
|
@@ -742,12 +818,18 @@ var PaintCanvas = class extends EventTarget {
|
|
|
742
818
|
return contentBounds(this.layerStack.active.context.getImageData(0, 0, width, height).data, width, height, mask.getContext("2d").getImageData(0, 0, width, height).data);
|
|
743
819
|
}
|
|
744
820
|
/**
|
|
745
|
-
* Erase the mask's part of an area from the selected layer,
|
|
821
|
+
* Erase the mask's part of an area from the selected layer, or paint it with the background colour when the layer's
|
|
822
|
+
* alpha lock is on, as one undoable action.
|
|
746
823
|
*/
|
|
747
|
-
|
|
824
|
+
clearMasked(area) {
|
|
825
|
+
const layer = this.layerStack.active;
|
|
826
|
+
if (layer.alphaLock) {
|
|
827
|
+
this.paintArea(area, this.currentColours.background);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
748
830
|
const mask = this.currentSelection.mask;
|
|
749
831
|
const maskPiece = copyArea(this.createCanvas, mask, area);
|
|
750
|
-
this.layerStack.changeImage(
|
|
832
|
+
this.layerStack.changeImage(layer.id, area, (ctx) => {
|
|
751
833
|
ctx.save();
|
|
752
834
|
ctx.globalCompositeOperation = "destination-out";
|
|
753
835
|
ctx.drawImage(maskPiece, area.x, area.y);
|
|
@@ -755,6 +837,28 @@ var PaintCanvas = class extends EventTarget {
|
|
|
755
837
|
});
|
|
756
838
|
}
|
|
757
839
|
/**
|
|
840
|
+
* Paint a colour over an area of the selected layer, cut to the mask if there is one (partly where it is partly), as
|
|
841
|
+
* one undoable action. Only existing pixels are painted when the layer's alpha lock is on.
|
|
842
|
+
*/
|
|
843
|
+
paintArea(area, colour) {
|
|
844
|
+
const layer = this.layerStack.active;
|
|
845
|
+
const mask = this.currentSelection.mask;
|
|
846
|
+
const paint = this.createCanvas(area.width, area.height);
|
|
847
|
+
const paintCtx = paint.getContext("2d");
|
|
848
|
+
if (mask) {
|
|
849
|
+
paintCtx.drawImage(copyArea(this.createCanvas, mask, area), 0, 0);
|
|
850
|
+
paintCtx.globalCompositeOperation = "source-in";
|
|
851
|
+
}
|
|
852
|
+
paintCtx.fillStyle = colour;
|
|
853
|
+
paintCtx.fillRect(0, 0, area.width, area.height);
|
|
854
|
+
this.layerStack.changeImage(layer.id, area, (ctx) => {
|
|
855
|
+
ctx.save();
|
|
856
|
+
ctx.globalCompositeOperation = layer.alphaLock ? "source-atop" : "source-over";
|
|
857
|
+
ctx.drawImage(paint, area.x, area.y);
|
|
858
|
+
ctx.restore();
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
758
862
|
* Show the clipboard's image data over the selected layer where it was copied from, and select the Move tool to place,
|
|
759
863
|
* scale and rotate it. It is written into the layer (and sent) on Enter or when switching to another tool; Escape
|
|
760
864
|
* discards it. A pending transform is applied first.
|
|
@@ -763,10 +867,36 @@ var PaintCanvas = class extends EventTarget {
|
|
|
763
867
|
const clipboard = this.clipboard;
|
|
764
868
|
if (!clipboard || this.strokeEngine.drawing) return;
|
|
765
869
|
if (this.transformer.pending) this.commitTransform();
|
|
766
|
-
this.selectTool(
|
|
870
|
+
this.selectTool(Tool.Move);
|
|
767
871
|
this.transformer.float(this.layerStack.active, clipboard.image, clipboard.area);
|
|
768
872
|
}
|
|
769
873
|
/**
|
|
874
|
+
* Show an image from elsewhere (e.g. pasted from another app) over the selected layer, in the middle of the part of
|
|
875
|
+
* the canvas in view, and select the Move tool to place, scale and rotate it, like `paste`. An image larger than the
|
|
876
|
+
* canvas is shrunk to fit it. Does nothing during a stroke.
|
|
877
|
+
*
|
|
878
|
+
* @example
|
|
879
|
+
* canvas.pasteImage(await createImageBitmap(file));
|
|
880
|
+
*/
|
|
881
|
+
pasteImage(source) {
|
|
882
|
+
if (this.strokeEngine.drawing) return;
|
|
883
|
+
const scale = Math.min(1, this.canvasWidth / source.width, this.canvasHeight / source.height);
|
|
884
|
+
const width = Math.max(1, Math.round(source.width * scale));
|
|
885
|
+
const height = Math.max(1, Math.round(source.height * scale));
|
|
886
|
+
const image = this.createCanvas(width, height);
|
|
887
|
+
image.getContext("2d").drawImage(source, 0, 0, width, height);
|
|
888
|
+
const view = this.view.visibleRect();
|
|
889
|
+
const area = {
|
|
890
|
+
x: Math.round(view.x + (view.width - width) / 2),
|
|
891
|
+
y: Math.round(view.y + (view.height - height) / 2),
|
|
892
|
+
width,
|
|
893
|
+
height
|
|
894
|
+
};
|
|
895
|
+
if (this.transformer.pending) this.commitTransform();
|
|
896
|
+
this.selectTool(Tool.Move);
|
|
897
|
+
this.transformer.float(this.layerStack.active, image, area);
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
770
900
|
* Apply a finished lasso action to the mask: a shape is added, subtracted or replaces it; a plain click deselects.
|
|
771
901
|
*/
|
|
772
902
|
applyLasso(result) {
|
|
@@ -827,16 +957,53 @@ var PaintCanvas = class extends EventTarget {
|
|
|
827
957
|
get layers() {
|
|
828
958
|
return this.layerStack;
|
|
829
959
|
}
|
|
830
|
-
/** Current
|
|
960
|
+
/** Current foreground colour as a hex string, e.g. `#000000` (the same as `colours.foreground`) */
|
|
831
961
|
get colour() {
|
|
832
|
-
return this.
|
|
962
|
+
return this.currentColours.foreground;
|
|
963
|
+
}
|
|
964
|
+
/** Current foreground and background colours as hex strings, e.g. `{ foreground: "#000000", background: "#ffffff" }` */
|
|
965
|
+
get colours() {
|
|
966
|
+
return this.currentColours;
|
|
967
|
+
}
|
|
968
|
+
/** Which colour is selected for configuration, e.g. by a colour picker or the colour sample tool ("foreground" by default) */
|
|
969
|
+
get selectedColour() {
|
|
970
|
+
return this.currentSelectedColour;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Set the foreground colour, or the background colour. Must be a 6-digit hex colour, e.g. `#ff0000`.
|
|
974
|
+
*
|
|
975
|
+
* @example
|
|
976
|
+
* canvas.setColour("#ff0000"); // foreground
|
|
977
|
+
* canvas.setColour("#ffffff", "background");
|
|
978
|
+
*/
|
|
979
|
+
setColour(colour, slot = "foreground") {
|
|
980
|
+
if (!isHexColour(colour)) throw new Error(`Invalid colour ${colour}`);
|
|
981
|
+
this.currentColours = {
|
|
982
|
+
...this.currentColours,
|
|
983
|
+
[slot]: colour.toLowerCase()
|
|
984
|
+
};
|
|
985
|
+
this.notify();
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Select which colour is configured, e.g. by a colour picker or the colour sample tool. One colour is always selected.
|
|
989
|
+
*
|
|
990
|
+
* @example
|
|
991
|
+
* canvas.selectColour("background");
|
|
992
|
+
*/
|
|
993
|
+
selectColour(slot) {
|
|
994
|
+
if (slot === this.currentSelectedColour) return;
|
|
995
|
+
this.currentSelectedColour = slot;
|
|
996
|
+
this.notify();
|
|
833
997
|
}
|
|
834
998
|
/**
|
|
835
|
-
*
|
|
999
|
+
* Swap the foreground and background colours. The selected colour stays the same slot.
|
|
836
1000
|
*/
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
this.
|
|
1001
|
+
swapColours() {
|
|
1002
|
+
const { foreground, background } = this.currentColours;
|
|
1003
|
+
this.currentColours = {
|
|
1004
|
+
foreground: background,
|
|
1005
|
+
background: foreground
|
|
1006
|
+
};
|
|
840
1007
|
this.notify();
|
|
841
1008
|
}
|
|
842
1009
|
/**
|
|
@@ -963,4 +1130,4 @@ var PaintCanvas = class extends EventTarget {
|
|
|
963
1130
|
this.dispatchEvent(new Event("change"));
|
|
964
1131
|
}
|
|
965
1132
|
};
|
|
966
|
-
export { DEFAULT_AUTHOR, DEFAULT_CANVAS_SIZE, DEFAULT_COLOUR, DEFAULT_NAMES, DEFAULT_OWNER_ID, MAX_CANVAS_SIZE, Panel, TOOL_PANELS, Tool, PaintCanvas as default };
|
|
1133
|
+
export { DEFAULT_AUTHOR, DEFAULT_BACKGROUND_COLOUR, DEFAULT_CANVAS_SIZE, DEFAULT_COLOUR, DEFAULT_NAMES, DEFAULT_OWNER_ID, MAX_CANVAS_SIZE, Panel, TOOL_PANELS, Tool, PaintCanvas as default, isHexColour };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Drawing and navigation tools */
|
|
2
|
+
export declare enum Tool {
|
|
3
|
+
Brush = "brush",
|
|
4
|
+
Eraser = "eraser",
|
|
5
|
+
/** Move, scale and rotate */
|
|
6
|
+
Move = "move",
|
|
7
|
+
RectangleLasso = "rectangle-lasso",
|
|
8
|
+
CircleLasso = "circle-lasso",
|
|
9
|
+
PolygonLasso = "polygon-lasso",
|
|
10
|
+
FreeformLasso = "freeform-lasso",
|
|
11
|
+
Pan = "pan",
|
|
12
|
+
Zoom = "zoom",
|
|
13
|
+
/** Set the current colour from the canvas (eyedropper) */
|
|
14
|
+
ColourSample = "colour-sample"
|
|
15
|
+
}
|