@pixerate/editor 0.104.0 → 0.106.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.
Files changed (31) hide show
  1. package/dist/{chunk-NQLQ2JOI.js → chunk-4X5MIVCE.js} +1155 -295
  2. package/dist/chunk-4X5MIVCE.js.map +1 -0
  3. package/dist/{chunk-VSMM7B3P.cjs → chunk-ZID226ZG.cjs} +1169 -309
  4. package/dist/chunk-ZID226ZG.cjs.map +1 -0
  5. package/dist/{compositor-CTMU-B1t.d.cts → compositor-Bc3PNP3C.d.cts} +1 -1
  6. package/dist/{compositor-B1ZrKR89.d.ts → compositor-BsYYllvR.d.ts} +1 -1
  7. package/dist/{export-X9rY79p0.d.cts → export-_m3Es4ru.d.cts} +19 -4
  8. package/dist/{export-BLpMyCYg.d.ts → export-iMrop3Jm.d.ts} +19 -4
  9. package/dist/image-editor/index.cjs +714 -539
  10. package/dist/image-editor/index.cjs.map +1 -1
  11. package/dist/image-editor/index.d.cts +50 -9
  12. package/dist/image-editor/index.d.ts +50 -9
  13. package/dist/image-editor/index.js +228 -53
  14. package/dist/image-editor/index.js.map +1 -1
  15. package/dist/{mixer-DGcH6HLu.d.cts → mixer-C1M1Nua_.d.cts} +1 -1
  16. package/dist/{mixer-DG_9Yu2W.d.ts → mixer-Cu5a9Bzd.d.ts} +1 -1
  17. package/dist/paint/index.d.cts +2 -2
  18. package/dist/paint/index.d.ts +2 -2
  19. package/dist/paint-editor/index.cjs +348 -32
  20. package/dist/paint-editor/index.cjs.map +1 -1
  21. package/dist/paint-editor/index.d.cts +154 -13
  22. package/dist/paint-editor/index.d.ts +154 -13
  23. package/dist/paint-editor/index.js +317 -1
  24. package/dist/paint-editor/index.js.map +1 -1
  25. package/dist/raster/index.d.cts +4 -4
  26. package/dist/raster/index.d.ts +4 -4
  27. package/dist/{surface-AfHCH7BR.d.cts → surface-BBJN9wxg.d.cts} +1 -1
  28. package/dist/{surface-AfHCH7BR.d.ts → surface-BBJN9wxg.d.ts} +1 -1
  29. package/package.json +1 -1
  30. package/dist/chunk-NQLQ2JOI.js.map +0 -1
  31. package/dist/chunk-VSMM7B3P.cjs.map +0 -1
@@ -4,7 +4,7 @@ import {
4
4
  resolveExternalAssets,
5
5
  serializeLayers,
6
6
  serializeLayersExternal
7
- } from "../chunk-NQLQ2JOI.js";
7
+ } from "../chunk-4X5MIVCE.js";
8
8
  import {
9
9
  RasterCompositor,
10
10
  bytesToBase64,
@@ -1509,6 +1509,28 @@ function defaultImageEncoder() {
1509
1509
  return new Uint8Array(await blob.arrayBuffer());
1510
1510
  };
1511
1511
  }
1512
+ async function decodeImageBlob(blob) {
1513
+ const g = globalThis;
1514
+ if (typeof g.createImageBitmap !== "function" || typeof g.OffscreenCanvas !== "function") throw new Error("Decoding images needs a browser (createImageBitmap and OffscreenCanvas).");
1515
+ const bitmap = await g.createImageBitmap(blob);
1516
+ const canvas = new g.OffscreenCanvas(bitmap.width, bitmap.height);
1517
+ const ctx = canvas.getContext("2d");
1518
+ if (!ctx) throw new Error("No 2D context to decode with.");
1519
+ ctx.drawImage(bitmap, 0, 0);
1520
+ const data = new Uint8ClampedArray(ctx.getImageData(0, 0, bitmap.width, bitmap.height).data);
1521
+ bitmap.close?.();
1522
+ return { width: bitmap.width, height: bitmap.height, data };
1523
+ }
1524
+ function downloadBytes(bytes, fileName, mimeType) {
1525
+ const g = globalThis;
1526
+ if (!g.document || !g.URL || !g.Blob) throw new Error("Downloading needs a browser.");
1527
+ const url = g.URL.createObjectURL(new g.Blob([bytes], { type: mimeType }));
1528
+ const a = g.document.createElement("a");
1529
+ a.href = url;
1530
+ a.download = fileName;
1531
+ a.click();
1532
+ setTimeout(() => g.URL.revokeObjectURL(url), 1e3);
1533
+ }
1512
1534
 
1513
1535
  // src/paint-editor/thumbnails.ts
1514
1536
  function thumbnailStroke(width, height) {
@@ -4292,9 +4314,288 @@ function createPaintEditorToolProvider(controller, options = {}) {
4292
4314
  tools.push(...createPigmentTools());
4293
4315
  return { id: options.id ?? "paint-editor", namespace: options.namespace, tools };
4294
4316
  }
4317
+
4318
+ // src/paint-editor/ui.ts
4319
+ var PAINT_TOOLS = [
4320
+ { id: "brush", label: "Brush", key: "b" },
4321
+ { id: "eyedropper", label: "Eyedropper", key: "i" },
4322
+ { id: "pan", label: "Pan", key: "h" },
4323
+ { id: "rotate", label: "Rotate view", key: "r" }
4324
+ ];
4325
+ var PaintGesture = class {
4326
+ constructor(controller, box) {
4327
+ this.controller = controller;
4328
+ this.box = box;
4329
+ }
4330
+ controller;
4331
+ box;
4332
+ tool = "brush";
4333
+ /** The last refusal (a hidden or locked layer), for the editor to show. */
4334
+ error = null;
4335
+ /** The predicted tail of the current stroke in document pixels (draw it as a thin preview). */
4336
+ predicted = [];
4337
+ mode = null;
4338
+ penAt = null;
4339
+ penDown = false;
4340
+ doc(e) {
4341
+ const b = this.box();
4342
+ const s = this.controller.getState();
4343
+ return screenToDocument(s.view, s.document.canvas, b, e.clientX - b.left, e.clientY - b.top);
4344
+ }
4345
+ pen(e) {
4346
+ const p = this.doc(e);
4347
+ return pointerToPenPoint(e, p.x, p.y, this.controller.getState().view);
4348
+ }
4349
+ /** Pointer down; returns true when the gesture took the pointer (capture it). */
4350
+ down(e) {
4351
+ this.error = null;
4352
+ if (this.mode) return false;
4353
+ if (e.pointerType === "pen") {
4354
+ this.penDown = true;
4355
+ this.penAt = { x: e.clientX, y: e.clientY };
4356
+ } else if (e.pointerType === "touch" && (this.penDown || isPalmTouch({ x: e.clientX, y: e.clientY }, this.penAt, this.controller.getState().hand))) {
4357
+ return false;
4358
+ }
4359
+ const middle = e.button === 1;
4360
+ const tool = middle ? "pan" : this.tool;
4361
+ if (tool === "pan") {
4362
+ this.mode = { kind: "pan", pointerId: e.pointerId, x: e.clientX, y: e.clientY, pan: { ...this.controller.getState().view.pan } };
4363
+ return true;
4364
+ }
4365
+ if (tool === "rotate") {
4366
+ const b = this.box();
4367
+ this.mode = { kind: "rotate", pointerId: e.pointerId, angle: Math.atan2(e.clientY - b.top - b.height / 2, e.clientX - b.left - b.width / 2), rotation: this.controller.getState().view.rotation };
4368
+ return true;
4369
+ }
4370
+ if (tool === "eyedropper" || e.altKey) {
4371
+ this.pick(e);
4372
+ return false;
4373
+ }
4374
+ try {
4375
+ const p = this.pen(e);
4376
+ const session = this.controller.beginStroke({ start: p });
4377
+ session.push([p]);
4378
+ this.mode = { kind: "stroke", pointerId: e.pointerId };
4379
+ return true;
4380
+ } catch (err) {
4381
+ this.error = err instanceof Error ? err.message : String(err);
4382
+ return false;
4383
+ }
4384
+ }
4385
+ pick(e) {
4386
+ const p = this.doc(e);
4387
+ const { width, height } = this.controller.document.canvas;
4388
+ if (p.x < 0 || p.y < 0 || p.x >= width || p.y >= height) return;
4389
+ this.controller.setColor(this.controller.pickColor(p.x, p.y).color);
4390
+ }
4391
+ move(e) {
4392
+ if (e.pointerType === "pen") this.penAt = { x: e.clientX, y: e.clientY };
4393
+ const m = this.mode;
4394
+ if (!m || m.pointerId !== e.pointerId) return;
4395
+ if (m.kind === "pan") {
4396
+ this.controller.setView({ pan: { x: m.pan.x + e.clientX - m.x, y: m.pan.y + e.clientY - m.y } });
4397
+ return;
4398
+ }
4399
+ if (m.kind === "rotate") {
4400
+ const b = this.box();
4401
+ const a = Math.atan2(e.clientY - b.top - b.height / 2, e.clientX - b.left - b.width / 2);
4402
+ let r = m.rotation + (a - m.angle) * 180 / Math.PI;
4403
+ if (e.shiftKey) r = Math.round(r / 15) * 15;
4404
+ this.controller.setView({ rotation: r });
4405
+ return;
4406
+ }
4407
+ const session = this.controller.activeStroke;
4408
+ if (!session) return;
4409
+ const events = typeof e.getCoalescedEvents === "function" ? e.getCoalescedEvents() : [];
4410
+ session.push((events.length ? events : [e]).map((ev) => this.pen(ev)));
4411
+ const predicted = typeof e.getPredictedEvents === "function" ? e.getPredictedEvents() : [];
4412
+ this.predicted = predicted.map((ev) => this.pen(ev));
4413
+ }
4414
+ up(e) {
4415
+ if (e.pointerType === "pen") this.penDown = false;
4416
+ const m = this.mode;
4417
+ if (!m || m.pointerId !== e.pointerId) return;
4418
+ this.mode = null;
4419
+ this.predicted = [];
4420
+ if (m.kind === "stroke") this.controller.activeStroke?.end();
4421
+ }
4422
+ cancel() {
4423
+ this.penDown = false;
4424
+ this.predicted = [];
4425
+ if (this.mode?.kind === "stroke") this.controller.activeStroke?.cancel();
4426
+ this.mode = null;
4427
+ }
4428
+ get active() {
4429
+ return this.mode !== null;
4430
+ }
4431
+ /** Wheel: zoom about the pointer (Ctrl / pinch or plain wheel), Shift+wheel rotates the view. */
4432
+ wheel(e) {
4433
+ const s = this.controller.getState();
4434
+ if (e.shiftKey) {
4435
+ this.controller.setView({ rotation: s.view.rotation + (e.deltaY > 0 ? 5 : -5) });
4436
+ return;
4437
+ }
4438
+ const b = this.box();
4439
+ const zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, s.view.zoom * Math.exp(-e.deltaY * 15e-4)));
4440
+ const k = zoom / s.view.zoom;
4441
+ const px = e.clientX - b.left - b.width / 2;
4442
+ const py = e.clientY - b.top - b.height / 2;
4443
+ this.controller.setView({ zoom, pan: { x: px - (px - s.view.pan.x) * k, y: py - (py - s.view.pan.y) * k } });
4444
+ }
4445
+ };
4446
+ function handlePaintShortcut(controller, gesture, e) {
4447
+ const mod = e.ctrlKey || e.metaKey;
4448
+ const key = e.key.toLowerCase();
4449
+ if (mod && key === "z") {
4450
+ if (controller.getState().stroking) return true;
4451
+ if (e.shiftKey) controller.redo();
4452
+ else controller.undo();
4453
+ return true;
4454
+ }
4455
+ if (mod && key === "y") {
4456
+ controller.redo();
4457
+ return true;
4458
+ }
4459
+ if (mod) return false;
4460
+ const tool = PAINT_TOOLS.find((t) => t.key === key);
4461
+ if (tool) {
4462
+ gesture.tool = tool.id;
4463
+ return true;
4464
+ }
4465
+ const brush = controller.getState().brush;
4466
+ if (key === "[" || key === "]") {
4467
+ const step = Math.max(1, Math.round(brush.tip.size * 0.15));
4468
+ controller.setBrush({ tip: { size: Math.max(1, brush.tip.size + (key === "]" ? step : -step)) } });
4469
+ return true;
4470
+ }
4471
+ if (key === "x") {
4472
+ controller.swapColors();
4473
+ return true;
4474
+ }
4475
+ if (key === "m") {
4476
+ controller.setView({ mirror: !controller.getState().view.mirror });
4477
+ return true;
4478
+ }
4479
+ if (key === "escape") {
4480
+ controller.resetView();
4481
+ return true;
4482
+ }
4483
+ return false;
4484
+ }
4485
+ var PaintCanvasPresenter = class {
4486
+ constructor(controller) {
4487
+ this.controller = controller;
4488
+ }
4489
+ controller;
4490
+ uploaded = /* @__PURE__ */ new Map();
4491
+ scratch = null;
4492
+ size = "";
4493
+ /** Draws what changed into `ctx` (a canvas the size of the document); returns the tiles drawn. */
4494
+ render(ctx) {
4495
+ const { width, height } = this.controller.document.canvas;
4496
+ const size = `${width}x${height}`;
4497
+ let dirty = this.controller.takeDirty();
4498
+ if (size !== this.size) {
4499
+ this.size = size;
4500
+ this.uploaded.clear();
4501
+ dirty = "all";
4502
+ }
4503
+ let drawn = 0;
4504
+ const rect = { x: 0, y: 0, width, height };
4505
+ this.controller.compositeTiles(rect, (tx, ty, data) => {
4506
+ const key = `${tx},${ty}`;
4507
+ if (this.uploaded.get(key) === data) return;
4508
+ this.uploaded.set(key, data);
4509
+ if (!this.scratch) this.scratch = ctx.createImageData(TILE, TILE);
4510
+ this.scratch.data.set(data);
4511
+ const ox = tx * TILE;
4512
+ const oy = ty * TILE;
4513
+ ctx.putImageData(this.scratch, ox, oy, 0, 0, Math.min(TILE, width - ox), Math.min(TILE, height - oy));
4514
+ drawn++;
4515
+ }, { dirty });
4516
+ return drawn;
4517
+ }
4518
+ };
4519
+ function guideOverlay(controller) {
4520
+ const s = controller.getState();
4521
+ const size = s.document.canvas;
4522
+ const out = [];
4523
+ for (const [from, to] of symmetryAxisSegments(s.guides.symmetry, size)) out.push({ from, to, kind: "symmetry" });
4524
+ if (s.guides.perspective.enabled) for (const [from, to] of perspectiveGuideLines(s.guides.perspective, size, 16)) out.push({ from, to, kind: "perspective" });
4525
+ return out;
4526
+ }
4527
+ var BRUSH_SLIDERS = [
4528
+ { path: "tip.size", label: "Size", min: 1, max: 400, step: 1 },
4529
+ { path: "tip.opacity", label: "Opacity", min: 0, max: 1, step: 0.01 },
4530
+ { path: "tip.flow", label: "Flow", min: 0, max: 1, step: 0.01 },
4531
+ { path: "tip.hardness", label: "Hardness", min: 0, max: 1, step: 0.01 },
4532
+ { path: "tip.spacing", label: "Spacing", min: 0.01, max: 1, step: 0.01 },
4533
+ { path: "tip.roundness", label: "Roundness", min: 0.05, max: 1, step: 0.01 },
4534
+ { path: "tip.angle", label: "Angle", min: -180, max: 180, step: 1 },
4535
+ { path: "grain", label: "Grain", min: 0, max: 1, step: 0.01 },
4536
+ { path: "tip.smoothing", label: "Smoothing", min: 0, max: 0.95, step: 0.01 },
4537
+ { path: "pressureSmoothing", label: "Pressure smoothing", min: 0, max: 0.95, step: 0.01 },
4538
+ { path: "dynamics.minSize", label: "Min size", min: 0, max: 1, step: 0.01 },
4539
+ { path: "dynamics.tiltSize", label: "Tilt size", min: 0, max: 1, step: 0.01 },
4540
+ { path: "dynamics.velocitySize", label: "Speed size", min: -1, max: 1, step: 0.01 }
4541
+ ];
4542
+ var PAPER_SLIDERS = [
4543
+ { path: "depth", label: "Grain depth", min: 0, max: 1, step: 0.01 },
4544
+ { path: "scale", label: "Grain scale", min: 0.25, max: 8, step: 0.05 },
4545
+ { path: "shading", label: "Shading", min: 0, max: 1, step: 0.01 },
4546
+ { path: "dry.contrast", label: "Contrast", min: 0, max: 3, step: 0.05 },
4547
+ { path: "absorption", label: "Absorption", min: 0, max: 1, step: 0.01 },
4548
+ { path: "permeability", label: "Permeability", min: 0, max: 1, step: 0.01 },
4549
+ { path: "roughness", label: "Roughness", min: 0, max: 1, step: 0.01 }
4550
+ ];
4551
+ function readPath(obj, path) {
4552
+ let v = obj;
4553
+ for (const k of path.split(".")) v = v?.[k];
4554
+ return typeof v === "number" ? v : 0;
4555
+ }
4556
+ function patchPath(obj, path, value) {
4557
+ const [head, ...rest] = path.split(".");
4558
+ if (!rest.length) return { [head]: value };
4559
+ return { [head]: { ...obj[head], ...patchPath(obj[head] ?? {}, rest.join("."), value) } };
4560
+ }
4561
+ function wheelPoint(angle, radius) {
4562
+ const a = (angle - 90) * Math.PI / 180;
4563
+ return { x: Math.cos(a) * radius, y: Math.sin(a) * radius };
4564
+ }
4565
+ function wheelAngle(x, y) {
4566
+ return (Math.atan2(y, x) * 180 / Math.PI + 90 + 360) % 360;
4567
+ }
4568
+ function renderColorWheel(size, wheel, inner = 0.64) {
4569
+ const data = new Uint8ClampedArray(size * size * 4);
4570
+ const r = size / 2;
4571
+ for (let y = 0; y < size; y++) {
4572
+ for (let x = 0; x < size; x++) {
4573
+ const dx = x + 0.5 - r;
4574
+ const dy = y + 0.5 - r;
4575
+ const d = Math.hypot(dx, dy) / r;
4576
+ if (d > 1 || d < inner) continue;
4577
+ const [cr, cg, cb] = hsvToRgb(wheelToHue(wheelAngle(dx, dy), wheel), 1, 1);
4578
+ const i = (y * size + x) * 4;
4579
+ const edge = Math.min(1, (1 - d) * r, (d - inner) * r);
4580
+ data[i] = cr;
4581
+ data[i + 1] = cg;
4582
+ data[i + 2] = cb;
4583
+ data[i + 3] = Math.round(255 * Math.max(0, edge));
4584
+ }
4585
+ }
4586
+ return { width: size, height: size, data };
4587
+ }
4588
+ function wheelPick(dx, dy, wheel, color) {
4589
+ const [r, g, b] = parseHexColor(color);
4590
+ const { s, v } = rgbToHsv(r, g, b);
4591
+ const [nr, ng, nb] = hsvToRgb(wheelToHue(wheelAngle(dx, dy), wheel), s || 1, v || 1);
4592
+ return rgbToHexColor(nr, ng, nb);
4593
+ }
4295
4594
  export {
4595
+ BLEND_MODES,
4296
4596
  BRUSH_ID,
4297
4597
  BRUSH_ROTATIONS,
4598
+ BRUSH_SLIDERS,
4298
4599
  BUILTIN_BRUSHES,
4299
4600
  DEFAULT_BRUSH_ID,
4300
4601
  DEFAULT_DPI,
@@ -4322,12 +4623,16 @@ export {
4322
4623
  PAINT_BRUSH_ENGINES,
4323
4624
  PAINT_FORMAT,
4324
4625
  PAINT_SCHEMA_VERSION,
4626
+ PAINT_TOOLS,
4325
4627
  PAPER_FAMILIES,
4326
4628
  PAPER_LIBRARY,
4327
4629
  PAPER_NODE_ID,
4630
+ PAPER_SLIDERS,
4631
+ PaintCanvasPresenter,
4328
4632
  PaintDocumentError,
4329
4633
  PaintEditorController,
4330
4634
  PaintEditorError,
4635
+ PaintGesture,
4331
4636
  PaintStrokeSession,
4332
4637
  PaperGrain,
4333
4638
  PenDabWalker,
@@ -4347,22 +4652,27 @@ export {
4347
4652
  createPaintDocument,
4348
4653
  createPaintEditorToolProvider,
4349
4654
  createPaintLayer,
4655
+ decodeImageBlob,
4350
4656
  defaultGuides,
4351
4657
  defaultImageEncoder,
4352
4658
  deserializePaintDocument,
4353
4659
  deserializePaintDocumentAsync,
4354
4660
  documentExtensions,
4355
4661
  documentToScreen,
4662
+ downloadBytes,
4356
4663
  downsampleRgba,
4357
4664
  fitZoom,
4358
4665
  grainCatch,
4359
4666
  grainHeights,
4667
+ guideOverlay,
4668
+ handlePaintShortcut,
4360
4669
  hash2,
4361
4670
  hexToRgb8,
4362
4671
  hslToRgb,
4363
4672
  hsvToRgb,
4364
4673
  hueToWheel,
4365
4674
  invertAffine,
4675
+ isBlendMode,
4366
4676
  isPalmTouch,
4367
4677
  mapPenPoint,
4368
4678
  mix32,
@@ -4384,13 +4694,16 @@ export {
4384
4694
  paperPreset,
4385
4695
  parseBrushLibrary,
4386
4696
  parseHexColor,
4697
+ patchPath,
4387
4698
  penOrientation,
4388
4699
  perspectiveDirections,
4389
4700
  perspectiveGuideLines,
4390
4701
  pointerToPenPoint,
4391
4702
  rand01,
4392
4703
  randSigned,
4704
+ readPath,
4393
4705
  renderBrushThumbnail,
4706
+ renderColorWheel,
4394
4707
  renderPaper,
4395
4708
  rgbToHexColor,
4396
4709
  rgbToHsl,
@@ -4412,6 +4725,9 @@ export {
4412
4725
  viewCssTransform,
4413
4726
  viewMatrix,
4414
4727
  viewPenToDocument,
4728
+ wheelAngle,
4729
+ wheelPick,
4730
+ wheelPoint,
4415
4731
  wheelToHue
4416
4732
  };
4417
4733
  //# sourceMappingURL=index.js.map