@lvl99dotcom/paintcanvas 0.0.0-stage → 0.2.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/LICENSE +201 -0
- package/README.md +131 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/lib/brushes/BrushTipCache/index.d.ts +32 -0
- package/dist/lib/brushes/BrushTipCache/index.js +60 -0
- package/dist/lib/brushes/StrokeEngine/index.d.ts +171 -0
- package/dist/lib/brushes/StrokeEngine/index.js +324 -0
- package/dist/lib/brushes/StrokePath/index.d.ts +53 -0
- package/dist/lib/brushes/StrokePath/index.js +161 -0
- package/dist/lib/brushes/StrokeSampler/index.d.ts +55 -0
- package/dist/lib/brushes/StrokeSampler/index.js +75 -0
- package/dist/lib/brushes/authors/index.d.ts +55 -0
- package/dist/lib/brushes/authors/index.js +60 -0
- package/dist/lib/brushes/configRanges/index.d.ts +52 -0
- package/dist/lib/brushes/configRanges/index.js +206 -0
- package/dist/lib/brushes/index.d.ts +5 -0
- package/dist/lib/brushes/index.js +19 -0
- package/dist/lib/brushes/params/index.d.ts +108 -0
- package/dist/lib/brushes/params/index.js +139 -0
- package/dist/lib/brushes/presetFile/index.d.ts +32 -0
- package/dist/lib/brushes/presetFile/index.js +55 -0
- package/dist/lib/brushes/presets/drawing/brush-pen.js +3 -0
- package/dist/lib/brushes/presets/drawing/felt-pen.js +3 -0
- package/dist/lib/brushes/presets/drawing/index.d.ts +13 -0
- package/dist/lib/brushes/presets/drawing/index.js +88 -0
- package/dist/lib/brushes/presets/drawing/pencil-2b.js +3 -0
- package/dist/lib/brushes/presets/drawing/pencil-hb.js +3 -0
- package/dist/lib/brushes/presets/painting/grass.js +3 -0
- package/dist/lib/brushes/presets/painting/hair.js +3 -0
- package/dist/lib/brushes/presets/painting/index.d.ts +18 -0
- package/dist/lib/brushes/presets/painting/index.js +118 -0
- package/dist/lib/brushes/presets/painting/splatter.js +3 -0
- package/dist/lib/brushes/presets/painting/textured-paint.js +3 -0
- package/dist/lib/brushes/presets/painting/watery-paint.js +3 -0
- package/dist/lib/brushes/presets/round/index.d.ts +9 -0
- package/dist/lib/brushes/presets/round/index.js +44 -0
- package/dist/lib/brushes/presets/round/round.js +3 -0
- package/dist/lib/brushes/presets/round/soft-round.js +3 -0
- package/dist/lib/brushes/presets/sized/index.d.ts +20 -0
- package/dist/lib/brushes/presets/sized/index.js +30 -0
- package/dist/lib/brushes/presets/sketching/index.d.ts +14 -0
- package/dist/lib/brushes/presets/sketching/index.js +107 -0
- package/dist/lib/brushes/presets/square/index.d.ts +7 -0
- package/dist/lib/brushes/presets/square/index.js +22 -0
- package/dist/lib/brushes/presets/square/square.js +3 -0
- package/dist/lib/brushes/shapeImage/index.d.ts +40 -0
- package/dist/lib/brushes/shapeImage/index.js +61 -0
- package/dist/lib/brushes/types/index.d.ts +243 -0
- package/dist/lib/brushes/types/index.js +20 -0
- package/dist/lib/classes/BackgroundWork/index.d.ts +55 -0
- package/dist/lib/classes/BackgroundWork/index.js +97 -0
- package/dist/lib/classes/BrushSettings/index.d.ts +91 -0
- package/dist/lib/classes/BrushSettings/index.js +152 -0
- package/dist/lib/classes/CustomPresets/index.d.ts +53 -0
- package/dist/lib/classes/CustomPresets/index.js +71 -0
- package/dist/lib/classes/ExternalSheets/index.d.ts +64 -0
- package/dist/lib/classes/ExternalSheets/index.js +112 -0
- package/dist/lib/classes/GestureTracker/index.d.ts +62 -0
- package/dist/lib/classes/GestureTracker/index.js +122 -0
- package/dist/lib/classes/History/index.d.ts +83 -0
- package/dist/lib/classes/History/index.js +126 -0
- package/dist/lib/classes/HistoryGuard/index.d.ts +58 -0
- package/dist/lib/classes/HistoryGuard/index.js +97 -0
- package/dist/lib/classes/InputSettings/index.d.ts +74 -0
- package/dist/lib/classes/InputSettings/index.js +101 -0
- package/dist/lib/classes/KeyboardShortcuts/index.d.ts +48 -0
- package/dist/lib/classes/KeyboardShortcuts/index.js +137 -0
- package/dist/lib/classes/Layer/index.d.ts +86 -0
- package/dist/lib/classes/Layer/index.js +111 -0
- package/dist/lib/classes/LayerAutosave/index.d.ts +180 -0
- package/dist/lib/classes/LayerAutosave/index.js +346 -0
- package/dist/lib/classes/LayerStack/index.d.ts +177 -0
- package/dist/lib/classes/LayerStack/index.js +394 -0
- package/dist/lib/classes/PaintCanvas/index.d.ts +537 -0
- package/dist/lib/classes/PaintCanvas/index.js +965 -0
- package/dist/lib/classes/StrokeLog/index.d.ts +149 -0
- package/dist/lib/classes/StrokeLog/index.js +257 -0
- package/dist/lib/classes/Viewport/index.d.ts +108 -0
- package/dist/lib/classes/Viewport/index.js +194 -0
- package/dist/lib/document/index.d.ts +17 -0
- package/dist/lib/document/index.js +25 -0
- package/dist/lib/document/schema.d.ts +132 -0
- package/dist/lib/document/schema.js +110 -0
- package/dist/lib/i18n/index.d.ts +94 -0
- package/dist/lib/i18n/index.js +182 -0
- package/dist/lib/image/index.d.ts +90 -0
- package/dist/lib/image/index.js +147 -0
- package/dist/lib/messages/PatchSender/index.d.ts +75 -0
- package/dist/lib/messages/PatchSender/index.js +125 -0
- package/dist/lib/messages/index.d.ts +26 -0
- package/dist/lib/messages/index.js +59 -0
- package/dist/lib/messages/schema.d.ts +1775 -0
- package/dist/lib/messages/schema.js +192 -0
- package/dist/lib/presets/index.d.ts +32 -0
- package/dist/lib/presets/index.js +35 -0
- package/dist/lib/presets/schema.d.ts +753 -0
- package/dist/lib/presets/schema.js +181 -0
- package/dist/lib/rendering/BrushTip/index.d.ts +63 -0
- package/dist/lib/rendering/BrushTip/index.js +112 -0
- package/dist/lib/rendering/checkerboard/index.d.ts +22 -0
- package/dist/lib/rendering/checkerboard/index.js +31 -0
- package/dist/lib/rendering/compositing/index.d.ts +64 -0
- package/dist/lib/rendering/compositing/index.js +124 -0
- package/dist/lib/rendering/cursor/index.d.ts +14 -0
- package/dist/lib/rendering/cursor/index.js +39 -0
- package/dist/lib/rendering/cursors/index.d.ts +18 -0
- package/dist/lib/rendering/cursors/index.js +23 -0
- package/dist/lib/rendering/maskOutline/index.d.ts +22 -0
- package/dist/lib/rendering/maskOutline/index.js +78 -0
- package/dist/lib/rendering/outline/index.d.ts +36 -0
- package/dist/lib/rendering/outline/index.js +120 -0
- package/dist/lib/rendering/sample/index.d.ts +55 -0
- package/dist/lib/rendering/sample/index.js +83 -0
- package/dist/lib/rendering/screenshot/index.d.ts +67 -0
- package/dist/lib/rendering/screenshot/index.js +96 -0
- package/dist/lib/rendering/transformBox/index.d.ts +10 -0
- package/dist/lib/rendering/transformBox/index.js +57 -0
- package/dist/lib/selection/Lasso/index.d.ts +70 -0
- package/dist/lib/selection/Lasso/index.js +156 -0
- package/dist/lib/selection/Selection/index.d.ts +86 -0
- package/dist/lib/selection/Selection/index.js +228 -0
- package/dist/lib/selection/geometry/index.d.ts +37 -0
- package/dist/lib/selection/geometry/index.js +57 -0
- package/dist/lib/storage/index.d.ts +63 -0
- package/dist/lib/storage/index.js +114 -0
- package/dist/lib/transform/Transformer/index.d.ts +124 -0
- package/dist/lib/transform/Transformer/index.js +286 -0
- package/dist/lib/transform/geometry/index.d.ts +95 -0
- package/dist/lib/transform/geometry/index.js +258 -0
- package/dist/lib/utils/array/index.d.ts +10 -0
- package/dist/lib/utils/array/index.js +21 -0
- package/dist/lib/utils/gestures/index.d.ts +35 -0
- package/dist/lib/utils/gestures/index.js +81 -0
- package/dist/lib/utils/navigation/index.d.ts +51 -0
- package/dist/lib/utils/navigation/index.js +59 -0
- package/dist/lib/utils/random/index.d.ts +18 -0
- package/dist/lib/utils/random/index.js +27 -0
- package/dist/lib/utils/shortcuts/index.d.ts +74 -0
- package/dist/lib/utils/shortcuts/index.js +189 -0
- package/dist/lib/validation/validators.generated.d.ts +27 -0
- package/dist/lib/validation/validators.generated.js +19826 -0
- package/dist/locales/en-GB.js +188 -0
- package/dist/locales/en-US.js +188 -0
- package/dist/locales/fr.js +188 -0
- package/dist/locales/ja.js +188 -0
- package/dist/locales/ko.js +188 -0
- package/dist/locales/zh-Hans.js +188 -0
- package/dist/locales/zh-Hant.js +188 -0
- package/dist/package.js +3 -0
- package/dist/testing/app.d.ts +7 -0
- package/dist/testing/app.js +16 -0
- package/dist/testing/elements.d.ts +42 -0
- package/dist/testing/elements.js +58 -0
- package/dist/testing/fakeCanvas.d.ts +21 -0
- package/dist/testing/fakeCanvas.js +26 -0
- package/dist/testing/layerStore.d.ts +26 -0
- package/dist/testing/layerStore.js +43 -0
- package/dist/testing/locks.d.ts +15 -0
- package/dist/testing/locks.js +41 -0
- package/dist/testing/recordingCanvas.d.ts +14 -0
- package/dist/testing/recordingCanvas.js +28 -0
- package/dist/testing/recordingContext.d.ts +10 -0
- package/dist/testing/recordingContext.js +19 -0
- package/dist/ui/ChangeController/index.d.ts +40 -0
- package/dist/ui/ChangeController/index.js +62 -0
- package/dist/ui/base/index.d.ts +69 -0
- package/dist/ui/base/index.js +94 -0
- package/dist/ui/elements/PaintCanvas/index.d.ts +249 -0
- package/dist/ui/elements/PaintCanvas/index.js +584 -0
- package/dist/ui/elements/PaintCanvas/style.js +3 -0
- package/dist/ui/elements/PcBrushConfig/index.d.ts +130 -0
- package/dist/ui/elements/PcBrushConfig/index.js +463 -0
- package/dist/ui/elements/PcBrushConfig/style.js +3 -0
- package/dist/ui/elements/PcBrushMenu/index.d.ts +121 -0
- package/dist/ui/elements/PcBrushMenu/index.js +362 -0
- package/dist/ui/elements/PcBrushMenu/style.js +3 -0
- package/dist/ui/elements/PcBrushTest/index.d.ts +71 -0
- package/dist/ui/elements/PcBrushTest/index.js +136 -0
- package/dist/ui/elements/PcBrushTest/style.js +3 -0
- package/dist/ui/elements/PcButton/index.d.ts +57 -0
- package/dist/ui/elements/PcButton/index.js +124 -0
- package/dist/ui/elements/PcButtonGroup/index.d.ts +44 -0
- package/dist/ui/elements/PcButtonGroup/index.js +93 -0
- package/dist/ui/elements/PcCanvas/index.d.ts +356 -0
- package/dist/ui/elements/PcCanvas/index.js +1004 -0
- package/dist/ui/elements/PcCanvas/style.js +3 -0
- package/dist/ui/elements/PcCanvasMap/index.d.ts +96 -0
- package/dist/ui/elements/PcCanvasMap/index.js +187 -0
- package/dist/ui/elements/PcCanvasMap/style.js +3 -0
- package/dist/ui/elements/PcContextMenu/index.d.ts +72 -0
- package/dist/ui/elements/PcContextMenu/index.js +148 -0
- package/dist/ui/elements/PcDialog/index.d.ts +63 -0
- package/dist/ui/elements/PcDialog/index.js +114 -0
- package/dist/ui/elements/PcIcon/index.d.ts +36 -0
- package/dist/ui/elements/PcIcon/index.js +43 -0
- package/dist/ui/elements/PcIconToggle/index.d.ts +46 -0
- package/dist/ui/elements/PcIconToggle/index.js +87 -0
- package/dist/ui/elements/PcInfoTip/index.d.ts +40 -0
- package/dist/ui/elements/PcInfoTip/index.js +95 -0
- package/dist/ui/elements/PcInputSettings/index.d.ts +69 -0
- package/dist/ui/elements/PcInputSettings/index.js +150 -0
- package/dist/ui/elements/PcLayerItem/index.d.ts +221 -0
- package/dist/ui/elements/PcLayerItem/index.js +561 -0
- package/dist/ui/elements/PcLayerItem/style.js +3 -0
- package/dist/ui/elements/PcLayersMenu/index.d.ts +108 -0
- package/dist/ui/elements/PcLayersMenu/index.js +208 -0
- package/dist/ui/elements/PcLayersMenu/style.js +3 -0
- package/dist/ui/elements/PcMatch/index.d.ts +31 -0
- package/dist/ui/elements/PcMatch/index.js +58 -0
- package/dist/ui/elements/PcPanel/index.d.ts +43 -0
- package/dist/ui/elements/PcPanel/index.js +51 -0
- package/dist/ui/elements/PcPopover/index.d.ts +48 -0
- package/dist/ui/elements/PcPopover/index.js +86 -0
- package/dist/ui/elements/PcSlider/index.d.ts +57 -0
- package/dist/ui/elements/PcSlider/index.js +88 -0
- package/dist/ui/elements/PcStats/index.d.ts +54 -0
- package/dist/ui/elements/PcStats/index.js +141 -0
- package/dist/ui/elements/PcThemePicker/index.d.ts +56 -0
- package/dist/ui/elements/PcThemePicker/index.js +134 -0
- package/dist/ui/elements/PcToast/index.d.ts +58 -0
- package/dist/ui/elements/PcToast/index.js +110 -0
- package/dist/ui/elements/PcToggleCheckbox/index.d.ts +37 -0
- package/dist/ui/elements/PcToggleCheckbox/index.js +105 -0
- package/dist/ui/elements/PcToolSettings/index.d.ts +128 -0
- package/dist/ui/elements/PcToolSettings/index.js +543 -0
- package/dist/ui/elements/PcToolSettings/style.js +3 -0
- package/dist/ui/elements/PcToolbar/index.d.ts +71 -0
- package/dist/ui/elements/PcToolbar/index.js +190 -0
- package/dist/ui/elements/PcToolbar/style.js +3 -0
- package/dist/ui/icons/images/brush.js +3 -0
- package/dist/ui/icons/images/circle-mask.js +3 -0
- package/dist/ui/icons/images/clipboard.js +3 -0
- package/dist/ui/icons/images/cursor.js +3 -0
- package/dist/ui/icons/images/eraser.js +3 -0
- package/dist/ui/icons/images/eyedropper.js +3 -0
- package/dist/ui/icons/images/flip-horizontal.js +3 -0
- package/dist/ui/icons/images/flip-vertical.js +3 -0
- package/dist/ui/icons/images/focus-pen.js +3 -0
- package/dist/ui/icons/images/freehand-mask.js +3 -0
- package/dist/ui/icons/images/hidden.js +3 -0
- package/dist/ui/icons/images/layers.js +3 -0
- package/dist/ui/icons/images/linked.js +3 -0
- package/dist/ui/icons/images/locked.js +3 -0
- package/dist/ui/icons/images/moon.js +3 -0
- package/dist/ui/icons/images/pan.js +3 -0
- package/dist/ui/icons/images/polygon-mask.js +3 -0
- package/dist/ui/icons/images/pressure.js +3 -0
- package/dist/ui/icons/images/randomise.js +3 -0
- package/dist/ui/icons/images/rectangle-mask.js +3 -0
- package/dist/ui/icons/images/redo.js +3 -0
- package/dist/ui/icons/images/rotate-90-left.js +3 -0
- package/dist/ui/icons/images/rotate-90-right.js +3 -0
- package/dist/ui/icons/images/smoothing-bezier-cubic.js +3 -0
- package/dist/ui/icons/images/smoothing-bezier-quad.js +3 -0
- package/dist/ui/icons/images/smoothing-linear.js +3 -0
- package/dist/ui/icons/images/star.js +3 -0
- package/dist/ui/icons/images/sun.js +3 -0
- package/dist/ui/icons/images/tilt.js +3 -0
- package/dist/ui/icons/images/undo.js +3 -0
- package/dist/ui/icons/images/unlinked.js +3 -0
- package/dist/ui/icons/images/unlocked.js +3 -0
- package/dist/ui/icons/images/visible.js +3 -0
- package/dist/ui/icons/images/zoom-in.js +3 -0
- package/dist/ui/icons/images/zoom-out.js +3 -0
- package/dist/ui/icons/images/zoom.js +3 -0
- package/dist/ui/icons/index.d.ts +42 -0
- package/dist/ui/icons/index.js +130 -0
- package/dist/ui/index.d.ts +21 -0
- package/dist/ui/index.js +22 -0
- package/dist/ui/layout/index.d.ts +88 -0
- package/dist/ui/layout/index.js +96 -0
- package/dist/ui/styles/index.d.ts +13 -0
- package/dist/ui/styles/index.js +195 -0
- package/dist/ui/tokens/index.d.ts +29 -0
- package/dist/ui/tokens/index.js +114 -0
- package/locales/en-GB.json +186 -0
- package/locales/en-US.json +186 -0
- package/locales/fr.json +186 -0
- package/locales/ja.json +186 -0
- package/locales/ko.json +186 -0
- package/locales/zh-Hans.json +186 -0
- package/locales/zh-Hant.json +186 -0
- package/package.json +97 -3
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// src/lib/storage/index.ts
|
|
2
|
+
/** Version of a saved drawing's shape, so drawings saved by other versions can be recognised */
|
|
3
|
+
var SAVED_DRAWING_VERSION = 1;
|
|
4
|
+
/** Name of the IndexedDB database `IndexedDbLayerStore` keeps drawings in */
|
|
5
|
+
var DATABASE_NAME = "paintcanvas";
|
|
6
|
+
/** Version of the database's object stores */
|
|
7
|
+
var DATABASE_VERSION = 1;
|
|
8
|
+
/** Object store of saved drawings, by name */
|
|
9
|
+
var DRAWINGS = "drawings";
|
|
10
|
+
/** Object store of layer images (PNG blobs), by drawing name and layer ID */
|
|
11
|
+
var IMAGES = "images";
|
|
12
|
+
/**
|
|
13
|
+
* Wait for an IndexedDB request's result.
|
|
14
|
+
*/
|
|
15
|
+
var result = (request) => new Promise((resolve, reject) => {
|
|
16
|
+
request.onsuccess = () => resolve(request.result);
|
|
17
|
+
request.onerror = () => reject(request.error);
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Wait for an IndexedDB transaction to finish, failing if it's aborted.
|
|
21
|
+
*/
|
|
22
|
+
var completed = (transaction) => new Promise((resolve, reject) => {
|
|
23
|
+
transaction.oncomplete = () => resolve();
|
|
24
|
+
transaction.onabort = () => reject(transaction.error ?? /* @__PURE__ */ new Error("Transaction aborted"));
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* The layer ID in a layer image's key (`[name, layerId]`).
|
|
28
|
+
*/
|
|
29
|
+
var layerIdOf = (key) => key[1];
|
|
30
|
+
/**
|
|
31
|
+
* Keeps drawings in the browser's IndexedDB database (`DATABASE_NAME`), each under its own name: the drawing in one
|
|
32
|
+
* record, and each layer's pixels as a PNG in its own record, so saving rewrites only the layers that changed.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* const canvas = new PaintCanvas({ layerStore: new IndexedDbLayerStore("my-drawing") });
|
|
36
|
+
*/
|
|
37
|
+
var IndexedDbLayerStore = class {
|
|
38
|
+
name;
|
|
39
|
+
factory;
|
|
40
|
+
database;
|
|
41
|
+
/**
|
|
42
|
+
* @param name - the drawing's name
|
|
43
|
+
* @param factory - the IndexedDB to use (the browser's by default)
|
|
44
|
+
*/
|
|
45
|
+
constructor(name, factory = indexedDB) {
|
|
46
|
+
this.name = name;
|
|
47
|
+
this.factory = factory;
|
|
48
|
+
}
|
|
49
|
+
async load() {
|
|
50
|
+
const transaction = (await this.open()).transaction([DRAWINGS, IMAGES], "readonly");
|
|
51
|
+
const images = transaction.objectStore(IMAGES);
|
|
52
|
+
const [drawing, keys, blobs] = await Promise.all([
|
|
53
|
+
result(transaction.objectStore(DRAWINGS).get(this.name)),
|
|
54
|
+
result(images.getAllKeys(this.imageKeys())),
|
|
55
|
+
result(images.getAll(this.imageKeys()))
|
|
56
|
+
]);
|
|
57
|
+
if (!drawing) return void 0;
|
|
58
|
+
return {
|
|
59
|
+
drawing,
|
|
60
|
+
images: new Map(keys.map((key, i) => [layerIdOf(key), blobs[i]]))
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async save(drawing, images) {
|
|
64
|
+
const transaction = (await this.open()).transaction([DRAWINGS, IMAGES], "readwrite");
|
|
65
|
+
const done = completed(transaction);
|
|
66
|
+
const store = transaction.objectStore(IMAGES);
|
|
67
|
+
transaction.objectStore(DRAWINGS).put(drawing, this.name);
|
|
68
|
+
for (const [id, image] of images) store.put(image, [this.name, id]);
|
|
69
|
+
const kept = new Set(drawing.layers.map((layer) => layer.id));
|
|
70
|
+
const keys = store.getAllKeys(this.imageKeys());
|
|
71
|
+
keys.onsuccess = () => {
|
|
72
|
+
for (const key of keys.result) if (!kept.has(layerIdOf(key))) store.delete(key);
|
|
73
|
+
};
|
|
74
|
+
await done;
|
|
75
|
+
}
|
|
76
|
+
async clear() {
|
|
77
|
+
const transaction = (await this.open()).transaction([DRAWINGS, IMAGES], "readwrite");
|
|
78
|
+
const done = completed(transaction);
|
|
79
|
+
transaction.objectStore(DRAWINGS).delete(this.name);
|
|
80
|
+
transaction.objectStore(IMAGES).delete(this.imageKeys());
|
|
81
|
+
await done;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Open the database (once), creating its object stores the first time. If it can't be opened, the next call tries
|
|
85
|
+
* again.
|
|
86
|
+
*/
|
|
87
|
+
open() {
|
|
88
|
+
this.database ??= new Promise((resolve, reject) => {
|
|
89
|
+
const request = this.factory.open(DATABASE_NAME, DATABASE_VERSION);
|
|
90
|
+
request.onupgradeneeded = () => {
|
|
91
|
+
request.result.createObjectStore(DRAWINGS);
|
|
92
|
+
request.result.createObjectStore(IMAGES);
|
|
93
|
+
};
|
|
94
|
+
request.onsuccess = () => {
|
|
95
|
+
const database = request.result;
|
|
96
|
+
database.onversionchange = () => {
|
|
97
|
+
database.close();
|
|
98
|
+
this.database = void 0;
|
|
99
|
+
};
|
|
100
|
+
resolve(database);
|
|
101
|
+
};
|
|
102
|
+
request.onerror = () => {
|
|
103
|
+
this.database = void 0;
|
|
104
|
+
reject(request.error);
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
return this.database;
|
|
108
|
+
}
|
|
109
|
+
/** The keys of this drawing's layer images: `[name, layerId]` (arrays sort after strings) */
|
|
110
|
+
imageKeys() {
|
|
111
|
+
return IDBKeyRange.bound([this.name], [this.name, []]);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
export { DATABASE_NAME, SAVED_DRAWING_VERSION, IndexedDbLayerStore as default };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type Layer from "../../classes/Layer";
|
|
2
|
+
import type { Rect } from "../../classes/Viewport";
|
|
3
|
+
import { type CanvasFactory } from "../../classes/Layer";
|
|
4
|
+
import type { LayerOverride } from "../../rendering/compositing";
|
|
5
|
+
import { type TransformBox } from "../geometry";
|
|
6
|
+
/** The drawing operations needed to draw transformed pixels */
|
|
7
|
+
type TransformContext = Pick<OffscreenCanvasRenderingContext2D, "save" | "restore" | "translate" | "rotate" | "scale" | "drawImage" | "imageSmoothingEnabled" | "imageSmoothingQuality">;
|
|
8
|
+
/**
|
|
9
|
+
* Draw an image into a transform box (position, size, rotation and flips), smoothing the pixels.
|
|
10
|
+
*/
|
|
11
|
+
export declare const drawTransformed: (ctx: TransformContext, image: CanvasImageSource, box: TransformBox) => void;
|
|
12
|
+
/**
|
|
13
|
+
* Bounding box of the visible pixels (alpha above 0), optionally only where a mask is also above 0.
|
|
14
|
+
*
|
|
15
|
+
* @param rgba - image data, 4 bytes per pixel
|
|
16
|
+
* @param maskRgba - mask image data (alpha = mask value), same size
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* contentBounds(imageData.data, 100, 100); // { x: 10, y: 20, width: 30, height: 5 }
|
|
20
|
+
*/
|
|
21
|
+
export declare const contentBounds: (rgba: ArrayLike<number>, width: number, height: number, maskRgba?: ArrayLike<number>) => Rect | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Copy an area of an image into a new canvas the size of the area.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* copyArea(createOffscreenCanvas, layer.canvas, { x: 10, y: 10, width: 50, height: 20 }); // a 50×20 canvas
|
|
27
|
+
*/
|
|
28
|
+
export declare const copyArea: (createCanvas: CanvasFactory, image: CanvasImageSource, area: Rect) => OffscreenCanvas;
|
|
29
|
+
/**
|
|
30
|
+
* Cut a copied area down to a mask: only the parts where the mask piece (the same area of the mask) is above 0 are kept,
|
|
31
|
+
* partly where it is partly.
|
|
32
|
+
*/
|
|
33
|
+
export declare const applyMask: (image: OffscreenCanvas, maskPiece: OffscreenCanvas) => void;
|
|
34
|
+
/**
|
|
35
|
+
* Moves, scales and rotates a layer's pixels (or only the masked part).
|
|
36
|
+
*
|
|
37
|
+
* `prepare` finds the area to transform and shows its box. The first change lifts the pixels off the layer, and `preview` then shows the
|
|
38
|
+
* layer with them transformed. `commit` writes them back transformed; `cancel` puts the original pixels back.
|
|
39
|
+
*
|
|
40
|
+
* `float` shows pasted pixels over a layer instead, ready to transform; `commit` writes them into the layer and `cancel` discards them.
|
|
41
|
+
*
|
|
42
|
+
* Dispatches a `change` event whenever the box or preview changes.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* transformer.prepare(layer, selection.mask);
|
|
46
|
+
* transformer.setBox(moveBox(transformer.box, 20, 0, false));
|
|
47
|
+
* transformer.commit(); // or transformer.cancel()
|
|
48
|
+
*/
|
|
49
|
+
export default class Transformer extends EventTarget {
|
|
50
|
+
/** Canvas width in pixels */
|
|
51
|
+
readonly width: number;
|
|
52
|
+
/** Canvas height in pixels */
|
|
53
|
+
readonly height: number;
|
|
54
|
+
/** Layer being transformed */
|
|
55
|
+
layer?: Layer;
|
|
56
|
+
/** Area of the original pixels */
|
|
57
|
+
source?: Rect;
|
|
58
|
+
/** Where the pixels are placed now */
|
|
59
|
+
box?: TransformBox;
|
|
60
|
+
private mask?;
|
|
61
|
+
private lifted?;
|
|
62
|
+
private workingCanvas?;
|
|
63
|
+
private readonly createCanvas;
|
|
64
|
+
constructor(width: number, height: number, createCanvas?: CanvasFactory);
|
|
65
|
+
/** Whether there is something to transform (its box is shown) */
|
|
66
|
+
get active(): boolean;
|
|
67
|
+
/** Whether pixels have been lifted and changed (or pasted) but not yet committed */
|
|
68
|
+
get pending(): boolean;
|
|
69
|
+
/** Whether the pending pixels were pasted (rather than lifted off the layer) */
|
|
70
|
+
get pasting(): boolean;
|
|
71
|
+
/** Width ÷ height of the original area, kept when scaling proportionally */
|
|
72
|
+
get aspect(): number;
|
|
73
|
+
/** The layer with the lifted pixels transformed, to draw in its place; undefined until something changes */
|
|
74
|
+
get preview(): LayerOverride | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Find what to transform on a layer: all its visible pixels, or only those inside the mask. Shows nothing if there are none.
|
|
77
|
+
*/
|
|
78
|
+
prepare(layer: Layer, mask?: OffscreenCanvas): void;
|
|
79
|
+
/**
|
|
80
|
+
* Show pasted pixels over a layer, in an area, ready to transform. They are only written into the layer on `commit`.
|
|
81
|
+
*/
|
|
82
|
+
float(layer: Layer, image: OffscreenCanvas, area: Rect): void;
|
|
83
|
+
/**
|
|
84
|
+
* Place the pixels in a new box, lifting them off the layer first if needed.
|
|
85
|
+
*/
|
|
86
|
+
setBox(box: TransformBox): void;
|
|
87
|
+
/**
|
|
88
|
+
* Write the transformed pixels into the layer. Returns the mask piece and source area so the mask can be moved too, and the
|
|
89
|
+
* changed area with a copy of its pixels from before the transform (for undo), or undefined if nothing changed. Afterwards
|
|
90
|
+
* the transformer is empty; call `prepare` again to keep transforming.
|
|
91
|
+
*/
|
|
92
|
+
commit(): {
|
|
93
|
+
maskPiece?: OffscreenCanvas;
|
|
94
|
+
source: Rect;
|
|
95
|
+
box: TransformBox;
|
|
96
|
+
area: Rect;
|
|
97
|
+
before: OffscreenCanvas;
|
|
98
|
+
} | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Put the original pixels back and return the box to where it started (Escape). Pasted pixels are discarded.
|
|
101
|
+
*/
|
|
102
|
+
cancel(): void;
|
|
103
|
+
/**
|
|
104
|
+
* Forget the current transform without changing the layer.
|
|
105
|
+
*/
|
|
106
|
+
reset(): void;
|
|
107
|
+
/**
|
|
108
|
+
* Copy the pixels to transform off the layer, leaving a hole where they were.
|
|
109
|
+
*/
|
|
110
|
+
private lift;
|
|
111
|
+
/**
|
|
112
|
+
* A copy of the layer's pixels within an area as they were before any pixels were lifted off it.
|
|
113
|
+
*/
|
|
114
|
+
private original;
|
|
115
|
+
/**
|
|
116
|
+
* Redraw the preview: the layer (with the hole) plus the lifted pixels in their box.
|
|
117
|
+
*/
|
|
118
|
+
private renderPreview;
|
|
119
|
+
/**
|
|
120
|
+
* Dispatch a `change` event.
|
|
121
|
+
*/
|
|
122
|
+
private notify;
|
|
123
|
+
}
|
|
124
|
+
export {};
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { createOffscreenCanvas } from "../../classes/Layer/index.js";
|
|
2
|
+
import { clipRect, unionRect } from "../../brushes/StrokeEngine/index.js";
|
|
3
|
+
import { boxBounds, boxFromRect } from "../geometry/index.js";
|
|
4
|
+
// src/lib/transform/Transformer/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* Draw an image into a transform box (position, size, rotation and flips), smoothing the pixels.
|
|
7
|
+
*/
|
|
8
|
+
var drawTransformed = (ctx, image, box) => {
|
|
9
|
+
ctx.save();
|
|
10
|
+
ctx.imageSmoothingEnabled = true;
|
|
11
|
+
ctx.imageSmoothingQuality = "high";
|
|
12
|
+
ctx.translate(box.cx, box.cy);
|
|
13
|
+
if (box.rotation) ctx.rotate(box.rotation * Math.PI / 180);
|
|
14
|
+
ctx.scale(box.width < 0 ? -1 : 1, box.height < 0 ? -1 : 1);
|
|
15
|
+
const width = Math.abs(box.width);
|
|
16
|
+
const height = Math.abs(box.height);
|
|
17
|
+
ctx.drawImage(image, -width / 2, -height / 2, width, height);
|
|
18
|
+
ctx.restore();
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Bounding box of the visible pixels (alpha above 0), optionally only where a mask is also above 0.
|
|
22
|
+
*
|
|
23
|
+
* @param rgba - image data, 4 bytes per pixel
|
|
24
|
+
* @param maskRgba - mask image data (alpha = mask value), same size
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* contentBounds(imageData.data, 100, 100); // { x: 10, y: 20, width: 30, height: 5 }
|
|
28
|
+
*/
|
|
29
|
+
var contentBounds = (rgba, width, height, maskRgba) => {
|
|
30
|
+
let left = width;
|
|
31
|
+
let top = height;
|
|
32
|
+
let right = -1;
|
|
33
|
+
let bottom = -1;
|
|
34
|
+
for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) {
|
|
35
|
+
const alpha = (y * width + x) * 4 + 3;
|
|
36
|
+
if (!rgba[alpha] || maskRgba && !maskRgba[alpha]) continue;
|
|
37
|
+
if (x < left) left = x;
|
|
38
|
+
if (x > right) right = x;
|
|
39
|
+
if (y < top) top = y;
|
|
40
|
+
if (y > bottom) bottom = y;
|
|
41
|
+
}
|
|
42
|
+
return right < 0 ? void 0 : {
|
|
43
|
+
x: left,
|
|
44
|
+
y: top,
|
|
45
|
+
width: right - left + 1,
|
|
46
|
+
height: bottom - top + 1
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Copy an area of an image into a new canvas the size of the area.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* copyArea(createOffscreenCanvas, layer.canvas, { x: 10, y: 10, width: 50, height: 20 }); // a 50×20 canvas
|
|
54
|
+
*/
|
|
55
|
+
var copyArea = (createCanvas, image, area) => {
|
|
56
|
+
const { x, y, width, height } = area;
|
|
57
|
+
const canvas = createCanvas(width, height);
|
|
58
|
+
canvas.getContext("2d").drawImage(image, x, y, width, height, 0, 0, width, height);
|
|
59
|
+
return canvas;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Cut a copied area down to a mask: only the parts where the mask piece (the same area of the mask) is above 0 are kept,
|
|
63
|
+
* partly where it is partly.
|
|
64
|
+
*/
|
|
65
|
+
var applyMask = (image, maskPiece) => {
|
|
66
|
+
const ctx = image.getContext("2d");
|
|
67
|
+
ctx.globalCompositeOperation = "destination-in";
|
|
68
|
+
ctx.drawImage(maskPiece, 0, 0);
|
|
69
|
+
ctx.globalCompositeOperation = "source-over";
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Moves, scales and rotates a layer's pixels (or only the masked part).
|
|
73
|
+
*
|
|
74
|
+
* `prepare` finds the area to transform and shows its box. The first change lifts the pixels off the layer, and `preview` then shows the
|
|
75
|
+
* layer with them transformed. `commit` writes them back transformed; `cancel` puts the original pixels back.
|
|
76
|
+
*
|
|
77
|
+
* `float` shows pasted pixels over a layer instead, ready to transform; `commit` writes them into the layer and `cancel` discards them.
|
|
78
|
+
*
|
|
79
|
+
* Dispatches a `change` event whenever the box or preview changes.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* transformer.prepare(layer, selection.mask);
|
|
83
|
+
* transformer.setBox(moveBox(transformer.box, 20, 0, false));
|
|
84
|
+
* transformer.commit(); // or transformer.cancel()
|
|
85
|
+
*/
|
|
86
|
+
var Transformer = class extends EventTarget {
|
|
87
|
+
/** Canvas width in pixels */
|
|
88
|
+
width;
|
|
89
|
+
/** Canvas height in pixels */
|
|
90
|
+
height;
|
|
91
|
+
/** Layer being transformed */
|
|
92
|
+
layer;
|
|
93
|
+
/** Area of the original pixels */
|
|
94
|
+
source;
|
|
95
|
+
/** Where the pixels are placed now */
|
|
96
|
+
box;
|
|
97
|
+
mask;
|
|
98
|
+
lifted;
|
|
99
|
+
workingCanvas;
|
|
100
|
+
createCanvas;
|
|
101
|
+
constructor(width, height, createCanvas = createOffscreenCanvas) {
|
|
102
|
+
super();
|
|
103
|
+
this.width = width;
|
|
104
|
+
this.height = height;
|
|
105
|
+
this.createCanvas = createCanvas;
|
|
106
|
+
}
|
|
107
|
+
/** Whether there is something to transform (its box is shown) */
|
|
108
|
+
get active() {
|
|
109
|
+
return this.box !== void 0;
|
|
110
|
+
}
|
|
111
|
+
/** Whether pixels have been lifted and changed (or pasted) but not yet committed */
|
|
112
|
+
get pending() {
|
|
113
|
+
return this.lifted !== void 0;
|
|
114
|
+
}
|
|
115
|
+
/** Whether the pending pixels were pasted (rather than lifted off the layer) */
|
|
116
|
+
get pasting() {
|
|
117
|
+
return this.lifted !== void 0 && this.lifted.backup === void 0;
|
|
118
|
+
}
|
|
119
|
+
/** Width ÷ height of the original area, kept when scaling proportionally */
|
|
120
|
+
get aspect() {
|
|
121
|
+
return this.source ? this.source.width / this.source.height : 1;
|
|
122
|
+
}
|
|
123
|
+
/** The layer with the lifted pixels transformed, to draw in its place; undefined until something changes */
|
|
124
|
+
get preview() {
|
|
125
|
+
return this.layer && this.lifted && this.workingCanvas ? {
|
|
126
|
+
layer: this.layer,
|
|
127
|
+
image: this.workingCanvas
|
|
128
|
+
} : void 0;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Find what to transform on a layer: all its visible pixels, or only those inside the mask. Shows nothing if there are none.
|
|
132
|
+
*/
|
|
133
|
+
prepare(layer, mask) {
|
|
134
|
+
this.reset();
|
|
135
|
+
const rgba = layer.context.getImageData(0, 0, layer.width, layer.height).data;
|
|
136
|
+
const maskRgba = mask?.getContext("2d")?.getImageData(0, 0, layer.width, layer.height).data;
|
|
137
|
+
const source = contentBounds(rgba, layer.width, layer.height, maskRgba);
|
|
138
|
+
if (source) {
|
|
139
|
+
this.layer = layer;
|
|
140
|
+
this.mask = mask;
|
|
141
|
+
this.source = source;
|
|
142
|
+
this.box = boxFromRect(source);
|
|
143
|
+
}
|
|
144
|
+
this.notify();
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Show pasted pixels over a layer, in an area, ready to transform. They are only written into the layer on `commit`.
|
|
148
|
+
*/
|
|
149
|
+
float(layer, image, area) {
|
|
150
|
+
this.reset();
|
|
151
|
+
this.layer = layer;
|
|
152
|
+
this.source = area;
|
|
153
|
+
this.box = boxFromRect(area);
|
|
154
|
+
this.lifted = { floating: image };
|
|
155
|
+
this.renderPreview();
|
|
156
|
+
this.notify();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Place the pixels in a new box, lifting them off the layer first if needed.
|
|
160
|
+
*/
|
|
161
|
+
setBox(box) {
|
|
162
|
+
if (!this.layer || !this.source) return;
|
|
163
|
+
this.lifted ??= this.lift(this.layer, this.source);
|
|
164
|
+
this.box = box;
|
|
165
|
+
this.renderPreview();
|
|
166
|
+
this.notify();
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Write the transformed pixels into the layer. Returns the mask piece and source area so the mask can be moved too, and the
|
|
170
|
+
* changed area with a copy of its pixels from before the transform (for undo), or undefined if nothing changed. Afterwards
|
|
171
|
+
* the transformer is empty; call `prepare` again to keep transforming.
|
|
172
|
+
*/
|
|
173
|
+
commit() {
|
|
174
|
+
const { layer, lifted, source, box } = this;
|
|
175
|
+
if (!layer || !lifted || !source || !box) {
|
|
176
|
+
this.reset();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const area = clipRect(lifted.backup ? unionRect(source, boxBounds(box)) : boxBounds(box), this.width, this.height);
|
|
180
|
+
if (!area) {
|
|
181
|
+
this.reset();
|
|
182
|
+
this.notify();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const before = this.original(layer, lifted, source, area);
|
|
186
|
+
drawTransformed(layer.context, lifted.floating, box);
|
|
187
|
+
this.reset();
|
|
188
|
+
this.notify();
|
|
189
|
+
return {
|
|
190
|
+
maskPiece: lifted.maskPiece,
|
|
191
|
+
source,
|
|
192
|
+
box,
|
|
193
|
+
area,
|
|
194
|
+
before
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Put the original pixels back and return the box to where it started (Escape). Pasted pixels are discarded.
|
|
199
|
+
*/
|
|
200
|
+
cancel() {
|
|
201
|
+
const { layer, lifted, source } = this;
|
|
202
|
+
if (this.pasting) {
|
|
203
|
+
this.reset();
|
|
204
|
+
this.notify();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (layer && lifted?.backup && source) {
|
|
208
|
+
const ctx = layer.context;
|
|
209
|
+
ctx.clearRect(source.x, source.y, source.width, source.height);
|
|
210
|
+
ctx.drawImage(lifted.backup, source.x, source.y);
|
|
211
|
+
this.lifted = void 0;
|
|
212
|
+
this.box = boxFromRect(source);
|
|
213
|
+
this.notify();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Forget the current transform without changing the layer.
|
|
218
|
+
*/
|
|
219
|
+
reset() {
|
|
220
|
+
this.layer = void 0;
|
|
221
|
+
this.mask = void 0;
|
|
222
|
+
this.source = void 0;
|
|
223
|
+
this.box = void 0;
|
|
224
|
+
this.lifted = void 0;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Copy the pixels to transform off the layer, leaving a hole where they were.
|
|
228
|
+
*/
|
|
229
|
+
lift(layer, source) {
|
|
230
|
+
const { x, y, width, height } = source;
|
|
231
|
+
const copy = (image) => copyArea(this.createCanvas, image, source);
|
|
232
|
+
const backup = copy(layer.canvas);
|
|
233
|
+
const floating = copy(layer.canvas);
|
|
234
|
+
const ctx = layer.context;
|
|
235
|
+
if (this.mask) {
|
|
236
|
+
const maskPiece = copy(this.mask);
|
|
237
|
+
applyMask(floating, maskPiece);
|
|
238
|
+
ctx.save();
|
|
239
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
240
|
+
ctx.drawImage(maskPiece, x, y);
|
|
241
|
+
ctx.restore();
|
|
242
|
+
return {
|
|
243
|
+
floating,
|
|
244
|
+
backup,
|
|
245
|
+
maskPiece
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
ctx.clearRect(x, y, width, height);
|
|
249
|
+
return {
|
|
250
|
+
floating,
|
|
251
|
+
backup
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* A copy of the layer's pixels within an area as they were before any pixels were lifted off it.
|
|
256
|
+
*/
|
|
257
|
+
original(layer, lifted, source, area) {
|
|
258
|
+
const canvas = copyArea(this.createCanvas, layer.canvas, area);
|
|
259
|
+
if (lifted.backup) {
|
|
260
|
+
const ctx = canvas.getContext("2d");
|
|
261
|
+
const x = source.x - area.x;
|
|
262
|
+
const y = source.y - area.y;
|
|
263
|
+
ctx.clearRect(x, y, source.width, source.height);
|
|
264
|
+
ctx.drawImage(lifted.backup, x, y);
|
|
265
|
+
}
|
|
266
|
+
return canvas;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Redraw the preview: the layer (with the hole) plus the lifted pixels in their box.
|
|
270
|
+
*/
|
|
271
|
+
renderPreview() {
|
|
272
|
+
const { layer, lifted, box } = this;
|
|
273
|
+
this.workingCanvas ??= this.createCanvas(this.width, this.height);
|
|
274
|
+
const ctx = this.workingCanvas.getContext("2d");
|
|
275
|
+
ctx.clearRect(0, 0, this.width, this.height);
|
|
276
|
+
ctx.drawImage(layer.canvas, 0, 0);
|
|
277
|
+
drawTransformed(ctx, lifted.floating, box);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Dispatch a `change` event.
|
|
281
|
+
*/
|
|
282
|
+
notify() {
|
|
283
|
+
this.dispatchEvent(new Event("change"));
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
export { applyMask, contentBounds, copyArea, Transformer as default, drawTransformed };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Point, Rect } from "../../classes/Viewport";
|
|
2
|
+
/**
|
|
3
|
+
* Where and how transformed pixels are placed: the centre, size and rotation of the box they are drawn in.
|
|
4
|
+
* Width and height are negative when the box has been flipped by dragging a handle past the opposite side.
|
|
5
|
+
*/
|
|
6
|
+
export interface TransformBox {
|
|
7
|
+
cx: number;
|
|
8
|
+
cy: number;
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
/** Rotation in degrees, clockwise */
|
|
12
|
+
rotation: number;
|
|
13
|
+
}
|
|
14
|
+
/** A handle position relative to the box centre: -1, 0 or 1 along each axis (0, 0 is the centre) */
|
|
15
|
+
export interface Handle {
|
|
16
|
+
hx: -1 | 0 | 1;
|
|
17
|
+
hy: -1 | 0 | 1;
|
|
18
|
+
}
|
|
19
|
+
/** The 8 move & scale handles: corners and edge centres */
|
|
20
|
+
export declare const SCALE_HANDLES: readonly Handle[];
|
|
21
|
+
/** Distance (screen pixels) of the rotate handle beyond the top-centre handle */
|
|
22
|
+
export declare const ROTATE_HANDLE_DISTANCE = 24;
|
|
23
|
+
/** Degrees per rotation step when Shift is held */
|
|
24
|
+
export declare const ROTATION_STEP = 45;
|
|
25
|
+
/**
|
|
26
|
+
* The box that exactly covers a rectangle, unrotated.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* boxFromRect({ x: 0, y: 0, width: 10, height: 20 }); // { cx: 5, cy: 10, width: 10, height: 20, rotation: 0 }
|
|
30
|
+
*/
|
|
31
|
+
export declare const boxFromRect: ({ x, y, width, height }: Rect) => TransformBox;
|
|
32
|
+
/**
|
|
33
|
+
* Where a handle is on the canvas.
|
|
34
|
+
*/
|
|
35
|
+
export declare const handlePosition: (box: TransformBox, { hx, hy }: Handle) => Point;
|
|
36
|
+
/**
|
|
37
|
+
* The box's four corners in drawing order (top-left, top-right, bottom-right, bottom-left), for drawing its outline.
|
|
38
|
+
*/
|
|
39
|
+
export declare const boxCorners: (box: TransformBox) => Point[];
|
|
40
|
+
/**
|
|
41
|
+
* Where the rotate handle is, in screen units: `distance` beyond the top-centre handle, away from the box's centre, so it
|
|
42
|
+
* turns with the box. Returns the handle and the top-centre handle (`anchor`) it is joined to by a line.
|
|
43
|
+
*
|
|
44
|
+
* @param toScreen - converts canvas points to screen points
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* rotateHandle({ cx: 50, cy: 50, width: 40, height: 20, rotation: 0 }); // { anchor: { x: 50, y: 40 }, handle: { x: 50, y: 16 } }
|
|
48
|
+
*/
|
|
49
|
+
export declare const rotateHandle: (box: TransformBox, toScreen?: (p: Point) => Point, distance?: number) => {
|
|
50
|
+
anchor: Point;
|
|
51
|
+
handle: Point;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Find the handle under a point, if any. Points and radius are in the same units (usually screen pixels).
|
|
55
|
+
*
|
|
56
|
+
* @param toScreen - converts canvas points to the point's units
|
|
57
|
+
*/
|
|
58
|
+
export declare const handleAt: (box: TransformBox, handles: readonly Handle[], point: Point, radius: number, toScreen?: (p: Point) => Point) => Handle | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Check whether a canvas point is inside the (possibly rotated) box.
|
|
61
|
+
*/
|
|
62
|
+
export declare const isInsideBox: (box: TransformBox, point: Point) => boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Move the box by a distance. With `constrain` (Shift), it only moves along the axis it has moved furthest on.
|
|
65
|
+
*
|
|
66
|
+
* @param start - the box when the drag started
|
|
67
|
+
*/
|
|
68
|
+
export declare const moveBox: (start: TransformBox, dx: number, dy: number, constrain: boolean) => TransformBox;
|
|
69
|
+
/**
|
|
70
|
+
* Scale the box by dragging a handle to a point, keeping the opposite side (or corner) fixed. Works on rotated boxes.
|
|
71
|
+
* With `proportional` (Shift), the box keeps the original's proportions; edge handles then scale the other axis around its centre.
|
|
72
|
+
*
|
|
73
|
+
* @param start - the box when the drag started
|
|
74
|
+
* @param aspect - original width ÷ height, kept when proportional
|
|
75
|
+
*/
|
|
76
|
+
export declare const scaleBox: (start: TransformBox, handle: Handle, point: Point, proportional: boolean, aspect: number) => TransformBox;
|
|
77
|
+
/**
|
|
78
|
+
* Rotate the box around its centre by how far the pointer has turned around it since the drag started.
|
|
79
|
+
* With `snap` (Shift), the rotation is a multiple of 45°.
|
|
80
|
+
*
|
|
81
|
+
* @param start - the box when the drag started
|
|
82
|
+
* @param from - pointer position when the drag started
|
|
83
|
+
*/
|
|
84
|
+
export declare const rotateBox: (start: TransformBox, from: Point, to: Point, snap: boolean) => TransformBox;
|
|
85
|
+
/**
|
|
86
|
+
* CSS resize cursor for a handle, taking the box's rotation into account.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* resizeCursor({ hx: 1, hy: 0 }, 0); // "ew-resize"
|
|
90
|
+
*/
|
|
91
|
+
export declare const resizeCursor: ({ hx, hy }: Handle, rotation: number) => string;
|
|
92
|
+
/**
|
|
93
|
+
* Smallest unrotated rectangle containing the box (e.g. the area the transformed pixels now cover).
|
|
94
|
+
*/
|
|
95
|
+
export declare const boxBounds: (box: TransformBox) => Rect;
|