@nextcloud/image-editor 1.0.0-beta.1 → 1.0.0-beta.2

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/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import './assets/index.css';
2
- import { shallowRef, computed, defineComponent, openBlock, createElementBlock, normalizeClass, renderSlot, mergeProps, createElementVNode, toDisplayString, createCommentVNode, createTextVNode, watch, provide, inject, Fragment, renderList, createVNode, withCtx, createBlock, resolveDynamicComponent, unref, normalizeStyle, ref, useTemplateRef, onMounted, withDirectives, withKeys, withModifiers, vModelText, onBeforeUnmount } from "vue";
2
+ import { shallowRef, computed, defineComponent, openBlock, createElementBlock, normalizeClass, renderSlot, mergeProps, createElementVNode, toDisplayString, createCommentVNode, createTextVNode, watch, provide, inject, Fragment, renderList, createVNode, withCtx, createBlock, resolveDynamicComponent, unref, normalizeStyle, createSlots, ref, useTemplateRef, onMounted, withDirectives, withKeys, withModifiers, vModelText, onBeforeUnmount } from "vue";
3
3
  import Konva from "konva";
4
4
  import NcButton from "@nextcloud/vue/components/NcButton";
5
5
  import NcLoadingIcon from "@nextcloud/vue/components/NcLoadingIcon";
@@ -67,14 +67,21 @@ function createInitialState() {
67
67
  flipX: false,
68
68
  flipY: false,
69
69
  crop: null,
70
- adjustments: { brightness: 0, contrast: 0, saturation: 0 },
70
+ adjustments: {
71
+ exposure: 0,
72
+ brightness: 0,
73
+ contrast: 0,
74
+ saturation: 0,
75
+ temperature: 0,
76
+ tint: 0,
77
+ sharpen: 0
78
+ },
71
79
  preset: "none",
72
80
  annotations: []
73
81
  };
74
82
  }
75
83
  function isPristine(state) {
76
- const { adjustments } = state;
77
- return state.rotation === 0 && state.fineRotation === 0 && state.zoom === 1 && !state.flipX && !state.flipY && state.crop === null && adjustments.brightness === 0 && adjustments.contrast === 0 && adjustments.saturation === 0 && state.preset === "none" && state.annotations.length === 0;
84
+ return state.rotation === 0 && state.fineRotation === 0 && state.zoom === 1 && !state.flipX && !state.flipY && state.crop === null && Object.values(state.adjustments).every((value) => value === 0) && state.preset === "none" && state.annotations.length === 0;
78
85
  }
79
86
  function orientedSize(natural, rotation) {
80
87
  return rotation % 180 === 0 ? { width: natural.width, height: natural.height } : { width: natural.height, height: natural.width };
@@ -109,6 +116,7 @@ function mapAnnotationCW(annotation, height) {
109
116
  case "draw":
110
117
  return { ...annotation, points: mapPointsCW(annotation.points, height) };
111
118
  case "arrow":
119
+ case "line":
112
120
  return { ...annotation, points: mapPointsCW(annotation.points, height) };
113
121
  case "rectangle":
114
122
  case "ellipse":
@@ -140,7 +148,8 @@ function rotateCW(state, oriented) {
140
148
  function mapAnnotationFlipX(annotation, width) {
141
149
  switch (annotation.type) {
142
150
  case "draw":
143
- case "arrow": {
151
+ case "arrow":
152
+ case "line": {
144
153
  const points = annotation.points.map((value, i) => i % 2 === 0 ? width - value : value);
145
154
  return { ...annotation, points };
146
155
  }
@@ -185,7 +194,8 @@ function flipHorizontal(state, oriented) {
185
194
  function mapAnnotationFlipY(annotation, height) {
186
195
  switch (annotation.type) {
187
196
  case "draw":
188
- case "arrow": {
197
+ case "arrow":
198
+ case "line": {
189
199
  const points = annotation.points.map((value, i) => i % 2 === 1 ? height - value : value);
190
200
  return { ...annotation, points };
191
201
  }
@@ -230,7 +240,8 @@ function flipVertical(state, oriented) {
230
240
  function translateAnnotation(annotation, dx, dy) {
231
241
  switch (annotation.type) {
232
242
  case "draw":
233
- case "arrow": {
243
+ case "arrow":
244
+ case "line": {
234
245
  const points = annotation.points.map((value, i) => value + (i % 2 === 0 ? dx : dy));
235
246
  return { ...annotation, points };
236
247
  }
@@ -249,7 +260,7 @@ function translateAnnotation(annotation, dx, dy) {
249
260
  function duplicateAnnotation(annotation, offset = 16) {
250
261
  return { ...translateAnnotation(annotation, offset, offset), id: newId() };
251
262
  }
252
- const _sfc_main$I = /* @__PURE__ */ defineComponent({
263
+ const _sfc_main$N = /* @__PURE__ */ defineComponent({
253
264
  __name: "GlassSurface",
254
265
  props: {
255
266
  variant: {}
@@ -271,8 +282,51 @@ const _export_sfc = (sfc, props) => {
271
282
  }
272
283
  return target;
273
284
  };
274
- const GlassSurface = /* @__PURE__ */ _export_sfc(_sfc_main$I, [["__scopeId", "data-v-b60e81d9"]]);
275
- const _sfc_main$H = {
285
+ const GlassSurface = /* @__PURE__ */ _export_sfc(_sfc_main$N, [["__scopeId", "data-v-b60e81d9"]]);
286
+ const _sfc_main$M = {
287
+ name: "CameraIrisIcon",
288
+ emits: ["click"],
289
+ props: {
290
+ title: {
291
+ type: String
292
+ },
293
+ fillColor: {
294
+ type: String,
295
+ default: "currentColor"
296
+ },
297
+ size: {
298
+ type: Number,
299
+ default: 24
300
+ }
301
+ }
302
+ };
303
+ const _hoisted_1$J = ["aria-hidden", "aria-label"];
304
+ const _hoisted_2$E = ["fill", "width", "height"];
305
+ const _hoisted_3$B = { d: "M13.73,15L9.83,21.76C10.53,21.91 11.25,22 12,22C14.4,22 16.6,21.15 18.32,19.75L14.66,13.4M2.46,15C3.38,17.92 5.61,20.26 8.45,21.34L12.12,15M8.54,12L4.64,5.25C3,7 2,9.39 2,12C2,12.68 2.07,13.35 2.2,14H9.69M21.8,10H14.31L14.6,10.5L19.36,18.75C21,16.97 22,14.6 22,12C22,11.31 21.93,10.64 21.8,10M21.54,9C20.62,6.07 18.39,3.74 15.55,2.66L11.88,9M9.4,10.5L14.17,2.24C13.47,2.09 12.75,2 12,2C9.6,2 7.4,2.84 5.68,4.25L9.34,10.6L9.4,10.5Z" };
306
+ const _hoisted_4$A = { key: 0 };
307
+ function _sfc_render$w(_ctx, _cache, $props, $setup, $data, $options) {
308
+ return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, {
309
+ "aria-hidden": $props.title ? null : "true",
310
+ "aria-label": $props.title,
311
+ class: "material-design-icon camera-iris-icon",
312
+ role: "img",
313
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
314
+ }), [
315
+ (openBlock(), createElementBlock("svg", {
316
+ fill: $props.fillColor,
317
+ class: "material-design-icon__svg",
318
+ width: $props.size,
319
+ height: $props.size,
320
+ viewBox: "0 0 24 24"
321
+ }, [
322
+ createElementVNode("path", _hoisted_3$B, [
323
+ $props.title ? (openBlock(), createElementBlock("title", _hoisted_4$A, toDisplayString($props.title), 1)) : createCommentVNode("", true)
324
+ ])
325
+ ], 8, _hoisted_2$E))
326
+ ], 16, _hoisted_1$J);
327
+ }
328
+ const CameraIris = /* @__PURE__ */ _export_sfc(_sfc_main$M, [["render", _sfc_render$w]]);
329
+ const _sfc_main$L = {
276
330
  name: "ContrastCircleIcon",
277
331
  emits: ["click"],
278
332
  props: {
@@ -289,11 +343,11 @@ const _sfc_main$H = {
289
343
  }
290
344
  }
291
345
  };
292
- const _hoisted_1$E = ["aria-hidden", "aria-label"];
293
- const _hoisted_2$z = ["fill", "width", "height"];
294
- const _hoisted_3$w = { d: "M12,20C9.79,20 7.79,19.1 6.34,17.66L17.66,6.34C19.1,7.79 20,9.79 20,12A8,8 0 0,1 12,20M6,8H8V6H9.5V8H11.5V9.5H9.5V11.5H8V9.5H6M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,16H17V14.5H12V16Z" };
295
- const _hoisted_4$v = { key: 0 };
296
- function _sfc_render$r(_ctx, _cache, $props, $setup, $data, $options) {
346
+ const _hoisted_1$I = ["aria-hidden", "aria-label"];
347
+ const _hoisted_2$D = ["fill", "width", "height"];
348
+ const _hoisted_3$A = { d: "M12,20C9.79,20 7.79,19.1 6.34,17.66L17.66,6.34C19.1,7.79 20,9.79 20,12A8,8 0 0,1 12,20M6,8H8V6H9.5V8H11.5V9.5H9.5V11.5H8V9.5H6M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,16H17V14.5H12V16Z" };
349
+ const _hoisted_4$z = { key: 0 };
350
+ function _sfc_render$v(_ctx, _cache, $props, $setup, $data, $options) {
297
351
  return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, {
298
352
  "aria-hidden": $props.title ? null : "true",
299
353
  "aria-label": $props.title,
@@ -308,14 +362,57 @@ function _sfc_render$r(_ctx, _cache, $props, $setup, $data, $options) {
308
362
  height: $props.size,
309
363
  viewBox: "0 0 24 24"
310
364
  }, [
311
- createElementVNode("path", _hoisted_3$w, [
312
- $props.title ? (openBlock(), createElementBlock("title", _hoisted_4$v, toDisplayString($props.title), 1)) : createCommentVNode("", true)
365
+ createElementVNode("path", _hoisted_3$A, [
366
+ $props.title ? (openBlock(), createElementBlock("title", _hoisted_4$z, toDisplayString($props.title), 1)) : createCommentVNode("", true)
313
367
  ])
314
- ], 8, _hoisted_2$z))
315
- ], 16, _hoisted_1$E);
368
+ ], 8, _hoisted_2$D))
369
+ ], 16, _hoisted_1$I);
316
370
  }
317
- const ContrastCircle = /* @__PURE__ */ _export_sfc(_sfc_main$H, [["render", _sfc_render$r]]);
318
- const _sfc_main$G = {
371
+ const ContrastCircle = /* @__PURE__ */ _export_sfc(_sfc_main$L, [["render", _sfc_render$v]]);
372
+ const _sfc_main$K = {
373
+ name: "ImageFilterCenterFocusIcon",
374
+ emits: ["click"],
375
+ props: {
376
+ title: {
377
+ type: String
378
+ },
379
+ fillColor: {
380
+ type: String,
381
+ default: "currentColor"
382
+ },
383
+ size: {
384
+ type: Number,
385
+ default: 24
386
+ }
387
+ }
388
+ };
389
+ const _hoisted_1$H = ["aria-hidden", "aria-label"];
390
+ const _hoisted_2$C = ["fill", "width", "height"];
391
+ const _hoisted_3$z = { d: "M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M19,19H15V21H19A2,2 0 0,0 21,19V15H19M19,3H15V5H19V9H21V5A2,2 0 0,0 19,3M5,5H9V3H5A2,2 0 0,0 3,5V9H5M5,15H3V19A2,2 0 0,0 5,21H9V19H5V15Z" };
392
+ const _hoisted_4$y = { key: 0 };
393
+ function _sfc_render$u(_ctx, _cache, $props, $setup, $data, $options) {
394
+ return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, {
395
+ "aria-hidden": $props.title ? null : "true",
396
+ "aria-label": $props.title,
397
+ class: "material-design-icon image-filter-center-focus-icon",
398
+ role: "img",
399
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
400
+ }), [
401
+ (openBlock(), createElementBlock("svg", {
402
+ fill: $props.fillColor,
403
+ class: "material-design-icon__svg",
404
+ width: $props.size,
405
+ height: $props.size,
406
+ viewBox: "0 0 24 24"
407
+ }, [
408
+ createElementVNode("path", _hoisted_3$z, [
409
+ $props.title ? (openBlock(), createElementBlock("title", _hoisted_4$y, toDisplayString($props.title), 1)) : createCommentVNode("", true)
410
+ ])
411
+ ], 8, _hoisted_2$C))
412
+ ], 16, _hoisted_1$H);
413
+ }
414
+ const ImageFilterCenterFocus = /* @__PURE__ */ _export_sfc(_sfc_main$K, [["render", _sfc_render$u]]);
415
+ const _sfc_main$J = {
319
416
  name: "InvertColorsIcon",
320
417
  emits: ["click"],
321
418
  props: {
@@ -332,11 +429,11 @@ const _sfc_main$G = {
332
429
  }
333
430
  }
334
431
  };
335
- const _hoisted_1$D = ["aria-hidden", "aria-label"];
336
- const _hoisted_2$y = ["fill", "width", "height"];
337
- const _hoisted_3$v = { d: "M12,19.58V19.58C10.4,19.58 8.89,18.96 7.76,17.83C6.62,16.69 6,15.19 6,13.58C6,12 6.62,10.47 7.76,9.34L12,5.1M17.66,7.93L12,2.27V2.27L6.34,7.93C3.22,11.05 3.22,16.12 6.34,19.24C7.9,20.8 9.95,21.58 12,21.58C14.05,21.58 16.1,20.8 17.66,19.24C20.78,16.12 20.78,11.05 17.66,7.93Z" };
338
- const _hoisted_4$u = { key: 0 };
339
- function _sfc_render$q(_ctx, _cache, $props, $setup, $data, $options) {
432
+ const _hoisted_1$G = ["aria-hidden", "aria-label"];
433
+ const _hoisted_2$B = ["fill", "width", "height"];
434
+ const _hoisted_3$y = { d: "M12,19.58V19.58C10.4,19.58 8.89,18.96 7.76,17.83C6.62,16.69 6,15.19 6,13.58C6,12 6.62,10.47 7.76,9.34L12,5.1M17.66,7.93L12,2.27V2.27L6.34,7.93C3.22,11.05 3.22,16.12 6.34,19.24C7.9,20.8 9.95,21.58 12,21.58C14.05,21.58 16.1,20.8 17.66,19.24C20.78,16.12 20.78,11.05 17.66,7.93Z" };
435
+ const _hoisted_4$x = { key: 0 };
436
+ function _sfc_render$t(_ctx, _cache, $props, $setup, $data, $options) {
340
437
  return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, {
341
438
  "aria-hidden": $props.title ? null : "true",
342
439
  "aria-label": $props.title,
@@ -351,14 +448,100 @@ function _sfc_render$q(_ctx, _cache, $props, $setup, $data, $options) {
351
448
  height: $props.size,
352
449
  viewBox: "0 0 24 24"
353
450
  }, [
354
- createElementVNode("path", _hoisted_3$v, [
355
- $props.title ? (openBlock(), createElementBlock("title", _hoisted_4$u, toDisplayString($props.title), 1)) : createCommentVNode("", true)
451
+ createElementVNode("path", _hoisted_3$y, [
452
+ $props.title ? (openBlock(), createElementBlock("title", _hoisted_4$x, toDisplayString($props.title), 1)) : createCommentVNode("", true)
356
453
  ])
357
- ], 8, _hoisted_2$y))
358
- ], 16, _hoisted_1$D);
454
+ ], 8, _hoisted_2$B))
455
+ ], 16, _hoisted_1$G);
456
+ }
457
+ const InvertColors = /* @__PURE__ */ _export_sfc(_sfc_main$J, [["render", _sfc_render$t]]);
458
+ const _sfc_main$I = {
459
+ name: "ThermometerIcon",
460
+ emits: ["click"],
461
+ props: {
462
+ title: {
463
+ type: String
464
+ },
465
+ fillColor: {
466
+ type: String,
467
+ default: "currentColor"
468
+ },
469
+ size: {
470
+ type: Number,
471
+ default: 24
472
+ }
473
+ }
474
+ };
475
+ const _hoisted_1$F = ["aria-hidden", "aria-label"];
476
+ const _hoisted_2$A = ["fill", "width", "height"];
477
+ const _hoisted_3$x = { d: "M15 13V5A3 3 0 0 0 9 5V13A5 5 0 1 0 15 13M12 4A1 1 0 0 1 13 5V8H11V5A1 1 0 0 1 12 4Z" };
478
+ const _hoisted_4$w = { key: 0 };
479
+ function _sfc_render$s(_ctx, _cache, $props, $setup, $data, $options) {
480
+ return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, {
481
+ "aria-hidden": $props.title ? null : "true",
482
+ "aria-label": $props.title,
483
+ class: "material-design-icon thermometer-icon",
484
+ role: "img",
485
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
486
+ }), [
487
+ (openBlock(), createElementBlock("svg", {
488
+ fill: $props.fillColor,
489
+ class: "material-design-icon__svg",
490
+ width: $props.size,
491
+ height: $props.size,
492
+ viewBox: "0 0 24 24"
493
+ }, [
494
+ createElementVNode("path", _hoisted_3$x, [
495
+ $props.title ? (openBlock(), createElementBlock("title", _hoisted_4$w, toDisplayString($props.title), 1)) : createCommentVNode("", true)
496
+ ])
497
+ ], 8, _hoisted_2$A))
498
+ ], 16, _hoisted_1$F);
359
499
  }
360
- const InvertColors = /* @__PURE__ */ _export_sfc(_sfc_main$G, [["render", _sfc_render$q]]);
361
- const _sfc_main$F = {
500
+ const Thermometer = /* @__PURE__ */ _export_sfc(_sfc_main$I, [["render", _sfc_render$s]]);
501
+ const _sfc_main$H = {
502
+ name: "WhiteBalanceIridescentIcon",
503
+ emits: ["click"],
504
+ props: {
505
+ title: {
506
+ type: String
507
+ },
508
+ fillColor: {
509
+ type: String,
510
+ default: "currentColor"
511
+ },
512
+ size: {
513
+ type: Number,
514
+ default: 24
515
+ }
516
+ }
517
+ };
518
+ const _hoisted_1$E = ["aria-hidden", "aria-label"];
519
+ const _hoisted_2$z = ["fill", "width", "height"];
520
+ const _hoisted_3$w = { d: "M4.96,19.95L6.76,18.15L5.34,16.74L3.55,18.53M3.55,4.46L5.34,6.26L6.76,4.84L4.96,3.05M20.45,18.53L18.66,16.74L17.24,18.15L19.04,19.95M13,22.45V19.5H11V22.45C11.32,22.45 13,22.45 13,22.45M19.04,3.05L17.24,4.84L18.66,6.26L20.45,4.46M11,3.5H13V0.55H11M5,14.5H19V8.5H5V14.5Z" };
521
+ const _hoisted_4$v = { key: 0 };
522
+ function _sfc_render$r(_ctx, _cache, $props, $setup, $data, $options) {
523
+ return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, {
524
+ "aria-hidden": $props.title ? null : "true",
525
+ "aria-label": $props.title,
526
+ class: "material-design-icon white-balance-iridescent-icon",
527
+ role: "img",
528
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
529
+ }), [
530
+ (openBlock(), createElementBlock("svg", {
531
+ fill: $props.fillColor,
532
+ class: "material-design-icon__svg",
533
+ width: $props.size,
534
+ height: $props.size,
535
+ viewBox: "0 0 24 24"
536
+ }, [
537
+ createElementVNode("path", _hoisted_3$w, [
538
+ $props.title ? (openBlock(), createElementBlock("title", _hoisted_4$v, toDisplayString($props.title), 1)) : createCommentVNode("", true)
539
+ ])
540
+ ], 8, _hoisted_2$z))
541
+ ], 16, _hoisted_1$E);
542
+ }
543
+ const WhiteBalanceIridescent = /* @__PURE__ */ _export_sfc(_sfc_main$H, [["render", _sfc_render$r]]);
544
+ const _sfc_main$G = {
362
545
  name: "WhiteBalanceSunnyIcon",
363
546
  emits: ["click"],
364
547
  props: {
@@ -375,11 +558,11 @@ const _sfc_main$F = {
375
558
  }
376
559
  }
377
560
  };
378
- const _hoisted_1$C = ["aria-hidden", "aria-label"];
379
- const _hoisted_2$x = ["fill", "width", "height"];
380
- const _hoisted_3$u = { d: "M3.55 19.09L4.96 20.5L6.76 18.71L5.34 17.29M12 6C8.69 6 6 8.69 6 12S8.69 18 12 18 18 15.31 18 12C18 8.68 15.31 6 12 6M20 13H23V11H20M17.24 18.71L19.04 20.5L20.45 19.09L18.66 17.29M20.45 5L19.04 3.6L17.24 5.39L18.66 6.81M13 1H11V4H13M6.76 5.39L4.96 3.6L3.55 5L5.34 6.81L6.76 5.39M1 13H4V11H1M13 20H11V23H13" };
381
- const _hoisted_4$t = { key: 0 };
382
- function _sfc_render$p(_ctx, _cache, $props, $setup, $data, $options) {
561
+ const _hoisted_1$D = ["aria-hidden", "aria-label"];
562
+ const _hoisted_2$y = ["fill", "width", "height"];
563
+ const _hoisted_3$v = { d: "M3.55 19.09L4.96 20.5L6.76 18.71L5.34 17.29M12 6C8.69 6 6 8.69 6 12S8.69 18 12 18 18 15.31 18 12C18 8.68 15.31 6 12 6M20 13H23V11H20M17.24 18.71L19.04 20.5L20.45 19.09L18.66 17.29M20.45 5L19.04 3.6L17.24 5.39L18.66 6.81M13 1H11V4H13M6.76 5.39L4.96 3.6L3.55 5L5.34 6.81L6.76 5.39M1 13H4V11H1M13 20H11V23H13" };
564
+ const _hoisted_4$u = { key: 0 };
565
+ function _sfc_render$q(_ctx, _cache, $props, $setup, $data, $options) {
383
566
  return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, {
384
567
  "aria-hidden": $props.title ? null : "true",
385
568
  "aria-label": $props.title,
@@ -394,16 +577,16 @@ function _sfc_render$p(_ctx, _cache, $props, $setup, $data, $options) {
394
577
  height: $props.size,
395
578
  viewBox: "0 0 24 24"
396
579
  }, [
397
- createElementVNode("path", _hoisted_3$u, [
398
- $props.title ? (openBlock(), createElementBlock("title", _hoisted_4$t, toDisplayString($props.title), 1)) : createCommentVNode("", true)
580
+ createElementVNode("path", _hoisted_3$v, [
581
+ $props.title ? (openBlock(), createElementBlock("title", _hoisted_4$u, toDisplayString($props.title), 1)) : createCommentVNode("", true)
399
582
  ])
400
- ], 8, _hoisted_2$x))
401
- ], 16, _hoisted_1$C);
583
+ ], 8, _hoisted_2$y))
584
+ ], 16, _hoisted_1$D);
402
585
  }
403
- const WhiteBalanceSunny = /* @__PURE__ */ _export_sfc(_sfc_main$F, [["render", _sfc_render$p]]);
404
- const _hoisted_1$B = { class: "editor-slider" };
405
- const _hoisted_2$w = ["value", "data-test", "disabled", "aria-label", "min", "max", "step"];
406
- const _sfc_main$E = /* @__PURE__ */ defineComponent({
586
+ const WhiteBalanceSunny = /* @__PURE__ */ _export_sfc(_sfc_main$G, [["render", _sfc_render$q]]);
587
+ const _hoisted_1$C = { class: "editor-slider" };
588
+ const _hoisted_2$x = ["value", "data-test", "disabled", "aria-label", "min", "max", "step"];
589
+ const _sfc_main$F = /* @__PURE__ */ defineComponent({
407
590
  __name: "EditorSlider",
408
591
  props: {
409
592
  value: {},
@@ -423,7 +606,7 @@ const _sfc_main$E = /* @__PURE__ */ defineComponent({
423
606
  emit("input", Number(event.target.value));
424
607
  }
425
608
  return (_ctx, _cache) => {
426
- return openBlock(), createElementBlock("div", _hoisted_1$B, [
609
+ return openBlock(), createElementBlock("div", _hoisted_1$C, [
427
610
  createElementVNode("input", {
428
611
  value: props.value,
429
612
  "data-test": __props.dataTest,
@@ -435,7 +618,7 @@ const _sfc_main$E = /* @__PURE__ */ defineComponent({
435
618
  step: __props.step,
436
619
  onInput,
437
620
  onChange: _cache[0] || (_cache[0] = ($event) => emit("commit"))
438
- }, null, 40, _hoisted_2$w),
621
+ }, null, 40, _hoisted_2$x),
439
622
  createElementVNode("output", null, [
440
623
  renderSlot(_ctx.$slots, "preview", {}, () => [
441
624
  createTextVNode(toDisplayString(__props.display ?? props.value), 1)
@@ -445,9 +628,9 @@ const _sfc_main$E = /* @__PURE__ */ defineComponent({
445
628
  };
446
629
  }
447
630
  });
448
- const EditorSlider = /* @__PURE__ */ _export_sfc(_sfc_main$E, [["__scopeId", "data-v-6562ea60"]]);
449
- const _hoisted_1$A = ["disabled", "data-test", "aria-pressed"];
450
- const _sfc_main$D = /* @__PURE__ */ defineComponent({
631
+ const EditorSlider = /* @__PURE__ */ _export_sfc(_sfc_main$F, [["__scopeId", "data-v-6562ea60"]]);
632
+ const _hoisted_1$B = ["disabled", "data-test", "aria-pressed"];
633
+ const _sfc_main$E = /* @__PURE__ */ defineComponent({
451
634
  __name: "IconTab",
452
635
  props: {
453
636
  label: {},
@@ -469,11 +652,11 @@ const _sfc_main$D = /* @__PURE__ */ defineComponent({
469
652
  }, [
470
653
  renderSlot(_ctx.$slots, "default", {}, void 0, true),
471
654
  createElementVNode("span", null, toDisplayString(__props.label), 1)
472
- ], 10, _hoisted_1$A);
655
+ ], 10, _hoisted_1$B);
473
656
  };
474
657
  }
475
658
  });
476
- const IconTab = /* @__PURE__ */ _export_sfc(_sfc_main$D, [["__scopeId", "data-v-95889662"]]);
659
+ const IconTab = /* @__PURE__ */ _export_sfc(_sfc_main$E, [["__scopeId", "data-v-95889662"]]);
477
660
  const gtBuilder = getGettextBuilder().detectLocale();
478
661
  [].map((data) => gtBuilder.addTranslation(data.locale, data.json));
479
662
  const gt = gtBuilder.build();
@@ -629,9 +812,9 @@ function useEditorContext() {
629
812
  }
630
813
  return context;
631
814
  }
632
- const _hoisted_1$z = { class: "adjust-panel" };
633
- const _hoisted_2$v = { class: "adjust-panel__tabs" };
634
- const _sfc_main$C = /* @__PURE__ */ defineComponent({
815
+ const _hoisted_1$A = { class: "adjust-panel" };
816
+ const _hoisted_2$w = { class: "adjust-panel__tabs" };
817
+ const _sfc_main$D = /* @__PURE__ */ defineComponent({
635
818
  __name: "AdjustPanel",
636
819
  props: {
637
820
  loaded: { type: Boolean }
@@ -639,11 +822,15 @@ const _sfc_main$C = /* @__PURE__ */ defineComponent({
639
822
  setup(__props) {
640
823
  const context = useEditorContext();
641
824
  const adjustments = [
825
+ { id: "exposure", label: t("Exposure"), icon: CameraIris },
642
826
  { id: "brightness", label: t("Brightness"), icon: WhiteBalanceSunny },
643
827
  { id: "contrast", label: t("Contrast"), icon: ContrastCircle },
644
- { id: "saturation", label: t("Saturation"), icon: InvertColors }
828
+ { id: "saturation", label: t("Saturation"), icon: InvertColors },
829
+ { id: "temperature", label: t("Temperature"), icon: Thermometer },
830
+ { id: "tint", label: t("Tint"), icon: WhiteBalanceIridescent },
831
+ { id: "sharpen", label: t("Sharpen"), icon: ImageFilterCenterFocus }
645
832
  ];
646
- const activeAdjustment = shallowRef("brightness");
833
+ const activeAdjustment = shallowRef("exposure");
647
834
  const display = computed(() => {
648
835
  const value = context.state.value.adjustments[activeAdjustment.value];
649
836
  return value > 0 ? `+${value}` : `${value}`;
@@ -659,8 +846,8 @@ const _sfc_main$C = /* @__PURE__ */ defineComponent({
659
846
  context.commit(context.state.value, adjustments.find((entry) => entry.id === activeAdjustment.value).label);
660
847
  }
661
848
  return (_ctx, _cache) => {
662
- return openBlock(), createElementBlock("div", _hoisted_1$z, [
663
- createElementVNode("div", _hoisted_2$v, [
849
+ return openBlock(), createElementBlock("div", _hoisted_1$A, [
850
+ createElementVNode("div", _hoisted_2$w, [
664
851
  (openBlock(), createElementBlock(Fragment, null, renderList(adjustments, (adjustment) => {
665
852
  return createVNode(IconTab, {
666
853
  key: adjustment.id,
@@ -693,8 +880,8 @@ const _sfc_main$C = /* @__PURE__ */ defineComponent({
693
880
  };
694
881
  }
695
882
  });
696
- const AdjustPanel = /* @__PURE__ */ _export_sfc(_sfc_main$C, [["__scopeId", "data-v-d7f848cf"]]);
697
- const _sfc_main$B = {
883
+ const AdjustPanel = /* @__PURE__ */ _export_sfc(_sfc_main$D, [["__scopeId", "data-v-3ad63bd8"]]);
884
+ const _sfc_main$C = {
698
885
  name: "ArrowTopRightIcon",
699
886
  emits: ["click"],
700
887
  props: {
@@ -711,15 +898,58 @@ const _sfc_main$B = {
711
898
  }
712
899
  }
713
900
  };
901
+ const _hoisted_1$z = ["aria-hidden", "aria-label"];
902
+ const _hoisted_2$v = ["fill", "width", "height"];
903
+ const _hoisted_3$u = { d: "M5,17.59L15.59,7H9V5H19V15H17V8.41L6.41,19L5,17.59Z" };
904
+ const _hoisted_4$t = { key: 0 };
905
+ function _sfc_render$p(_ctx, _cache, $props, $setup, $data, $options) {
906
+ return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, {
907
+ "aria-hidden": $props.title ? null : "true",
908
+ "aria-label": $props.title,
909
+ class: "material-design-icon arrow-top-right-icon",
910
+ role: "img",
911
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
912
+ }), [
913
+ (openBlock(), createElementBlock("svg", {
914
+ fill: $props.fillColor,
915
+ class: "material-design-icon__svg",
916
+ width: $props.size,
917
+ height: $props.size,
918
+ viewBox: "0 0 24 24"
919
+ }, [
920
+ createElementVNode("path", _hoisted_3$u, [
921
+ $props.title ? (openBlock(), createElementBlock("title", _hoisted_4$t, toDisplayString($props.title), 1)) : createCommentVNode("", true)
922
+ ])
923
+ ], 8, _hoisted_2$v))
924
+ ], 16, _hoisted_1$z);
925
+ }
926
+ const ArrowTopRight = /* @__PURE__ */ _export_sfc(_sfc_main$C, [["render", _sfc_render$p]]);
927
+ const _sfc_main$B = {
928
+ name: "EllipseOutlineIcon",
929
+ emits: ["click"],
930
+ props: {
931
+ title: {
932
+ type: String
933
+ },
934
+ fillColor: {
935
+ type: String,
936
+ default: "currentColor"
937
+ },
938
+ size: {
939
+ type: Number,
940
+ default: 24
941
+ }
942
+ }
943
+ };
714
944
  const _hoisted_1$y = ["aria-hidden", "aria-label"];
715
945
  const _hoisted_2$u = ["fill", "width", "height"];
716
- const _hoisted_3$t = { d: "M5,17.59L15.59,7H9V5H19V15H17V8.41L6.41,19L5,17.59Z" };
946
+ const _hoisted_3$t = { d: "M12,6C16.41,6 20,8.69 20,12C20,15.31 16.41,18 12,18C7.59,18 4,15.31 4,12C4,8.69 7.59,6 12,6M12,4C6.5,4 2,7.58 2,12C2,16.42 6.5,20 12,20C17.5,20 22,16.42 22,12C22,7.58 17.5,4 12,4Z" };
717
947
  const _hoisted_4$s = { key: 0 };
718
948
  function _sfc_render$o(_ctx, _cache, $props, $setup, $data, $options) {
719
949
  return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, {
720
950
  "aria-hidden": $props.title ? null : "true",
721
951
  "aria-label": $props.title,
722
- class: "material-design-icon arrow-top-right-icon",
952
+ class: "material-design-icon ellipse-outline-icon",
723
953
  role: "img",
724
954
  onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
725
955
  }), [
@@ -736,9 +966,9 @@ function _sfc_render$o(_ctx, _cache, $props, $setup, $data, $options) {
736
966
  ], 8, _hoisted_2$u))
737
967
  ], 16, _hoisted_1$y);
738
968
  }
739
- const ArrowTopRight = /* @__PURE__ */ _export_sfc(_sfc_main$B, [["render", _sfc_render$o]]);
969
+ const EllipseOutline = /* @__PURE__ */ _export_sfc(_sfc_main$B, [["render", _sfc_render$o]]);
740
970
  const _sfc_main$A = {
741
- name: "EllipseOutlineIcon",
971
+ name: "FormatTextIcon",
742
972
  emits: ["click"],
743
973
  props: {
744
974
  title: {
@@ -756,13 +986,13 @@ const _sfc_main$A = {
756
986
  };
757
987
  const _hoisted_1$x = ["aria-hidden", "aria-label"];
758
988
  const _hoisted_2$t = ["fill", "width", "height"];
759
- const _hoisted_3$s = { d: "M12,6C16.41,6 20,8.69 20,12C20,15.31 16.41,18 12,18C7.59,18 4,15.31 4,12C4,8.69 7.59,6 12,6M12,4C6.5,4 2,7.58 2,12C2,16.42 6.5,20 12,20C17.5,20 22,16.42 22,12C22,7.58 17.5,4 12,4Z" };
989
+ const _hoisted_3$s = { d: "M18.5,4L19.66,8.35L18.7,8.61C18.25,7.74 17.79,6.87 17.26,6.43C16.73,6 16.11,6 15.5,6H13V16.5C13,17 13,17.5 13.33,17.75C13.67,18 14.33,18 15,18V19H9V18C9.67,18 10.33,18 10.67,17.75C11,17.5 11,17 11,16.5V6H8.5C7.89,6 7.27,6 6.74,6.43C6.21,6.87 5.75,7.74 5.3,8.61L4.34,8.35L5.5,4H18.5Z" };
760
990
  const _hoisted_4$r = { key: 0 };
761
991
  function _sfc_render$n(_ctx, _cache, $props, $setup, $data, $options) {
762
992
  return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, {
763
993
  "aria-hidden": $props.title ? null : "true",
764
994
  "aria-label": $props.title,
765
- class: "material-design-icon ellipse-outline-icon",
995
+ class: "material-design-icon format-text-icon",
766
996
  role: "img",
767
997
  onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
768
998
  }), [
@@ -779,9 +1009,9 @@ function _sfc_render$n(_ctx, _cache, $props, $setup, $data, $options) {
779
1009
  ], 8, _hoisted_2$t))
780
1010
  ], 16, _hoisted_1$x);
781
1011
  }
782
- const EllipseOutline = /* @__PURE__ */ _export_sfc(_sfc_main$A, [["render", _sfc_render$n]]);
1012
+ const FormatText = /* @__PURE__ */ _export_sfc(_sfc_main$A, [["render", _sfc_render$n]]);
783
1013
  const _sfc_main$z = {
784
- name: "FormatTextIcon",
1014
+ name: "PencilIcon",
785
1015
  emits: ["click"],
786
1016
  props: {
787
1017
  title: {
@@ -799,13 +1029,13 @@ const _sfc_main$z = {
799
1029
  };
800
1030
  const _hoisted_1$w = ["aria-hidden", "aria-label"];
801
1031
  const _hoisted_2$s = ["fill", "width", "height"];
802
- const _hoisted_3$r = { d: "M18.5,4L19.66,8.35L18.7,8.61C18.25,7.74 17.79,6.87 17.26,6.43C16.73,6 16.11,6 15.5,6H13V16.5C13,17 13,17.5 13.33,17.75C13.67,18 14.33,18 15,18V19H9V18C9.67,18 10.33,18 10.67,17.75C11,17.5 11,17 11,16.5V6H8.5C7.89,6 7.27,6 6.74,6.43C6.21,6.87 5.75,7.74 5.3,8.61L4.34,8.35L5.5,4H18.5Z" };
1032
+ const _hoisted_3$r = { d: "M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z" };
803
1033
  const _hoisted_4$q = { key: 0 };
804
1034
  function _sfc_render$m(_ctx, _cache, $props, $setup, $data, $options) {
805
1035
  return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, {
806
1036
  "aria-hidden": $props.title ? null : "true",
807
1037
  "aria-label": $props.title,
808
- class: "material-design-icon format-text-icon",
1038
+ class: "material-design-icon pencil-icon",
809
1039
  role: "img",
810
1040
  onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
811
1041
  }), [
@@ -822,9 +1052,9 @@ function _sfc_render$m(_ctx, _cache, $props, $setup, $data, $options) {
822
1052
  ], 8, _hoisted_2$s))
823
1053
  ], 16, _hoisted_1$w);
824
1054
  }
825
- const FormatText = /* @__PURE__ */ _export_sfc(_sfc_main$z, [["render", _sfc_render$m]]);
1055
+ const Pencil = /* @__PURE__ */ _export_sfc(_sfc_main$z, [["render", _sfc_render$m]]);
826
1056
  const _sfc_main$y = {
827
- name: "PencilIcon",
1057
+ name: "RectangleOutlineIcon",
828
1058
  emits: ["click"],
829
1059
  props: {
830
1060
  title: {
@@ -842,13 +1072,13 @@ const _sfc_main$y = {
842
1072
  };
843
1073
  const _hoisted_1$v = ["aria-hidden", "aria-label"];
844
1074
  const _hoisted_2$r = ["fill", "width", "height"];
845
- const _hoisted_3$q = { d: "M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z" };
1075
+ const _hoisted_3$q = { d: "M4,6V19H20V6H4M18,17H6V8H18V17Z" };
846
1076
  const _hoisted_4$p = { key: 0 };
847
1077
  function _sfc_render$l(_ctx, _cache, $props, $setup, $data, $options) {
848
1078
  return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, {
849
1079
  "aria-hidden": $props.title ? null : "true",
850
1080
  "aria-label": $props.title,
851
- class: "material-design-icon pencil-icon",
1081
+ class: "material-design-icon rectangle-outline-icon",
852
1082
  role: "img",
853
1083
  onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
854
1084
  }), [
@@ -865,9 +1095,9 @@ function _sfc_render$l(_ctx, _cache, $props, $setup, $data, $options) {
865
1095
  ], 8, _hoisted_2$r))
866
1096
  ], 16, _hoisted_1$v);
867
1097
  }
868
- const Pencil = /* @__PURE__ */ _export_sfc(_sfc_main$y, [["render", _sfc_render$l]]);
1098
+ const RectangleOutline = /* @__PURE__ */ _export_sfc(_sfc_main$y, [["render", _sfc_render$l]]);
869
1099
  const _sfc_main$x = {
870
- name: "RectangleOutlineIcon",
1100
+ name: "VectorLineIcon",
871
1101
  emits: ["click"],
872
1102
  props: {
873
1103
  title: {
@@ -885,13 +1115,13 @@ const _sfc_main$x = {
885
1115
  };
886
1116
  const _hoisted_1$u = ["aria-hidden", "aria-label"];
887
1117
  const _hoisted_2$q = ["fill", "width", "height"];
888
- const _hoisted_3$p = { d: "M4,6V19H20V6H4M18,17H6V8H18V17Z" };
1118
+ const _hoisted_3$p = { d: "M15,3V7.59L7.59,15H3V21H9V16.42L16.42,9H21V3M17,5H19V7H17M5,17H7V19H5" };
889
1119
  const _hoisted_4$o = { key: 0 };
890
1120
  function _sfc_render$k(_ctx, _cache, $props, $setup, $data, $options) {
891
1121
  return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, {
892
1122
  "aria-hidden": $props.title ? null : "true",
893
1123
  "aria-label": $props.title,
894
- class: "material-design-icon rectangle-outline-icon",
1124
+ class: "material-design-icon vector-line-icon",
895
1125
  role: "img",
896
1126
  onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
897
1127
  }), [
@@ -908,7 +1138,7 @@ function _sfc_render$k(_ctx, _cache, $props, $setup, $data, $options) {
908
1138
  ], 8, _hoisted_2$q))
909
1139
  ], 16, _hoisted_1$u);
910
1140
  }
911
- const RectangleOutline = /* @__PURE__ */ _export_sfc(_sfc_main$x, [["render", _sfc_render$k]]);
1141
+ const VectorLine = /* @__PURE__ */ _export_sfc(_sfc_main$x, [["render", _sfc_render$k]]);
912
1142
  function useAnnotationColor(context) {
913
1143
  function recolorable() {
914
1144
  const annotation = context.state.value.annotations.find((entry) => entry.id === context.selectedId.value);
@@ -963,9 +1193,10 @@ const _sfc_main$w = /* @__PURE__ */ defineComponent({
963
1193
  { id: "rectangle", label: t("Rectangle"), icon: RectangleOutline },
964
1194
  { id: "ellipse", label: t("Ellipse"), icon: EllipseOutline },
965
1195
  { id: "arrow", label: t("Arrow"), icon: ArrowTopRight },
1196
+ { id: "line", label: t("Line"), icon: VectorLine },
966
1197
  { id: "text", label: t("Text"), icon: FormatText }
967
1198
  ];
968
- const showStrokeOptions = computed(() => ["draw", "rectangle", "ellipse", "arrow"].includes(context.activeTool.value));
1199
+ const showStrokeOptions = computed(() => ["draw", "rectangle", "ellipse", "arrow", "line"].includes(context.activeTool.value));
969
1200
  const viewScale = computed(() => (context.viewFit.value?.scale ?? 1) * context.viewZoom.value);
970
1201
  const strokePreview = computed(() => Math.min(PREVIEW_CAP, Math.max(2, context.strokeWidth.value * viewScale.value)));
971
1202
  const fontPreview = computed(() => Math.min(PREVIEW_CAP, Math.max(8, context.fontSize.value * viewScale.value)));
@@ -1050,7 +1281,7 @@ const _sfc_main$w = /* @__PURE__ */ defineComponent({
1050
1281
  };
1051
1282
  }
1052
1283
  });
1053
- const AnnotatePanel = /* @__PURE__ */ _export_sfc(_sfc_main$w, [["__scopeId", "data-v-1bf135e4"]]);
1284
+ const AnnotatePanel = /* @__PURE__ */ _export_sfc(_sfc_main$w, [["__scopeId", "data-v-f7e8a194"]]);
1054
1285
  const _sfc_main$v = {
1055
1286
  name: "FlipHorizontalIcon",
1056
1287
  emits: ["click"],
@@ -1450,6 +1681,43 @@ const PresetChip = /* @__PURE__ */ _export_sfc(_sfc_main$q, [["__scopeId", "data
1450
1681
  function luma(r, g, b) {
1451
1682
  return 0.299 * r + 0.587 * g + 0.114 * b;
1452
1683
  }
1684
+ const EXPOSURE_RANGE = 2;
1685
+ const TEMPERATURE_RANGE = 0.25;
1686
+ const TINT_RANGE = 0.25;
1687
+ function tone(imageData) {
1688
+ const { data } = imageData;
1689
+ const exposure = 2 ** ((this.getAttr("exposure") ?? 0) * EXPOSURE_RANGE);
1690
+ const temperature = (this.getAttr("temperature") ?? 0) * TEMPERATURE_RANGE;
1691
+ const tint = (this.getAttr("tint") ?? 0) * TINT_RANGE;
1692
+ const red = exposure * (1 + temperature) * (1 + tint / 2);
1693
+ const green = exposure * (1 - tint);
1694
+ const blue = exposure * (1 - temperature) * (1 + tint / 2);
1695
+ for (let i = 0; i < data.length; i += 4) {
1696
+ data[i] = data[i] * red;
1697
+ data[i + 1] = data[i + 1] * green;
1698
+ data[i + 2] = data[i + 2] * blue;
1699
+ }
1700
+ }
1701
+ const SHARPEN_RANGE = 1.2;
1702
+ function sharpen(imageData) {
1703
+ const amount = (this.getAttr("sharpen") ?? 0) * SHARPEN_RANGE;
1704
+ if (amount === 0) {
1705
+ return;
1706
+ }
1707
+ const { data, width, height } = imageData;
1708
+ const source = new Uint8ClampedArray(data);
1709
+ const stride = width * 4;
1710
+ for (let y = 1; y < height - 1; y++) {
1711
+ for (let x = 1; x < width - 1; x++) {
1712
+ const centre = y * stride + x * 4;
1713
+ for (let channel = 0; channel < 3; channel++) {
1714
+ const i = centre + channel;
1715
+ const neighbours = source[i - 4] + source[i + 4] + source[i - stride] + source[i + stride];
1716
+ data[i] = source[i] * (1 + 4 * amount) - amount * neighbours;
1717
+ }
1718
+ }
1719
+ }
1720
+ }
1453
1721
  function saturate(imageData) {
1454
1722
  const { data } = imageData;
1455
1723
  const factor = this.saturation() + 1;
@@ -1612,6 +1880,14 @@ function buildAnnotationNode(annotation, oriented) {
1612
1880
  lineCap: "round",
1613
1881
  lineJoin: "round"
1614
1882
  });
1883
+ case "line":
1884
+ return new Konva.Line({
1885
+ ...base,
1886
+ points: [...annotation.points],
1887
+ stroke: annotation.color,
1888
+ strokeWidth: annotation.strokeWidth,
1889
+ lineCap: "round"
1890
+ });
1615
1891
  case "arrow":
1616
1892
  return new Konva.Arrow({
1617
1893
  ...base,
@@ -1670,8 +1946,11 @@ function buildAnnotationNode(annotation, oriented) {
1670
1946
  }
1671
1947
  }
1672
1948
  function applyFilters(node, state, pixelRatio = 1) {
1673
- const { brightness, contrast, saturation } = state.adjustments;
1949
+ const { exposure, brightness, contrast, saturation, temperature, tint } = state.adjustments;
1674
1950
  const filters = [];
1951
+ if (exposure !== 0 || temperature !== 0 || tint !== 0) {
1952
+ filters.push(tone);
1953
+ }
1675
1954
  if (brightness !== 0) {
1676
1955
  filters.push(Konva.Filters.Brighten);
1677
1956
  }
@@ -1703,6 +1982,9 @@ function applyFilters(node, state, pixelRatio = 1) {
1703
1982
  if (presetFilters !== null) {
1704
1983
  filters.push(presetFilters);
1705
1984
  }
1985
+ if (state.adjustments.sharpen !== 0) {
1986
+ filters.push(sharpen);
1987
+ }
1706
1988
  if (filters.length === 0) {
1707
1989
  node.filters([]);
1708
1990
  node.clearCache();
@@ -1712,6 +1994,10 @@ function applyFilters(node, state, pixelRatio = 1) {
1712
1994
  node.brightness(brightness / 100);
1713
1995
  node.contrast(contrast);
1714
1996
  node.saturation(saturation / 100);
1997
+ node.setAttr("exposure", exposure / 100);
1998
+ node.setAttr("temperature", temperature / 100);
1999
+ node.setAttr("tint", tint / 100);
2000
+ node.setAttr("sharpen", state.adjustments.sharpen / 100);
1715
2001
  if (state.preset === "posterize") {
1716
2002
  node.levels(0.02);
1717
2003
  }
@@ -1727,9 +2013,7 @@ function thumbnailKey(state) {
1727
2013
  crop?.y,
1728
2014
  crop?.width,
1729
2015
  crop?.height,
1730
- adjustments.brightness,
1731
- adjustments.contrast,
1732
- adjustments.saturation
2016
+ ...Object.values(adjustments)
1733
2017
  ].join("|");
1734
2018
  }
1735
2019
  function presetThumbnail(oriented, state, preset, size = 96) {
@@ -1784,8 +2068,7 @@ function createScene(stage) {
1784
2068
  viewGroup.clipHeight(void 0);
1785
2069
  }
1786
2070
  const pixelRatio = options.fastFilters ? Math.min(1, options.scale * (globalThis.devicePixelRatio || 1)) : 1;
1787
- const { brightness, contrast, saturation } = state.adjustments;
1788
- const nextFilterKey = `${brightness}|${contrast}|${saturation}|${state.preset}|${pixelRatio}`;
2071
+ const nextFilterKey = `${Object.values(state.adjustments).join("|")}|${state.preset}|${pixelRatio}`;
1789
2072
  if (orientedChanged || nextFilterKey !== filterKey) {
1790
2073
  applyFilters(imageNode, state, pixelRatio);
1791
2074
  filterKey = nextFilterKey;
@@ -2759,7 +3042,8 @@ const _hoisted_4$3 = { class: "editor-topbar__actions" };
2759
3042
  const _sfc_main$5 = /* @__PURE__ */ defineComponent({
2760
3043
  __name: "EditorTopBar",
2761
3044
  props: {
2762
- loaded: { type: Boolean }
3045
+ loaded: { type: Boolean },
3046
+ saving: { type: Boolean }
2763
3047
  },
2764
3048
  emits: ["save", "cancel"],
2765
3049
  setup(__props, { emit: __emit }) {
@@ -2942,20 +3226,31 @@ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
2942
3226
  createVNode(unref(NcButton), {
2943
3227
  "data-test": "save",
2944
3228
  variant: "primary",
2945
- disabled: !__props.loaded,
3229
+ disabled: !__props.loaded || __props.saving,
2946
3230
  onClick: _cache[6] || (_cache[6] = ($event) => emit("save"))
2947
- }, {
3231
+ }, createSlots({
2948
3232
  default: withCtx(() => [
2949
- createTextVNode(toDisplayString(labels.save), 1)
3233
+ createTextVNode(" " + toDisplayString(labels.save), 1)
2950
3234
  ]),
2951
- _: 1
2952
- }, 8, ["disabled"])
3235
+ _: 2
3236
+ }, [
3237
+ __props.saving ? {
3238
+ name: "icon",
3239
+ fn: withCtx(() => [
3240
+ createVNode(unref(NcLoadingIcon), {
3241
+ size: 20,
3242
+ "data-test": "saving"
3243
+ })
3244
+ ]),
3245
+ key: "0"
3246
+ } : void 0
3247
+ ]), 1032, ["disabled"])
2953
3248
  ])
2954
3249
  ]);
2955
3250
  };
2956
3251
  }
2957
3252
  });
2958
- const EditorTopBar = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-9ba9bd23"]]);
3253
+ const EditorTopBar = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-50392429"]]);
2959
3254
  const _sfc_main$4 = {
2960
3255
  name: "ContentCopyIcon",
2961
3256
  emits: ["click"],
@@ -3376,7 +3671,13 @@ async function canvasToBlob(canvas, type = "image/png", quality) {
3376
3671
  }, type, quality);
3377
3672
  });
3378
3673
  }
3674
+ async function painted() {
3675
+ await new Promise((resolve) => {
3676
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve(null)));
3677
+ });
3678
+ }
3379
3679
  function useExportImage(deps) {
3680
+ const exporting = ref(false);
3380
3681
  async function exportImage(options = {}) {
3381
3682
  const oriented = deps.oriented();
3382
3683
  if (oriented === null) {
@@ -3386,6 +3687,15 @@ function useExportImage(deps) {
3386
3687
  if (source !== null && source.type !== "" && isPristine(deps.getState()) && options.maxSize === void 0 && (options.format === void 0 || options.format === source.type)) {
3387
3688
  return { blob: source, width: oriented.width, height: oriented.height, mimeType: source.type };
3388
3689
  }
3690
+ exporting.value = true;
3691
+ await painted();
3692
+ try {
3693
+ return await encode(oriented, options);
3694
+ } finally {
3695
+ exporting.value = false;
3696
+ }
3697
+ }
3698
+ async function encode(oriented, options) {
3389
3699
  const canvas = renderToCanvas(oriented, deps.getState(), options.maxSize);
3390
3700
  const mimeType = options.format ?? "image/png";
3391
3701
  try {
@@ -3408,7 +3718,7 @@ function useExportImage(deps) {
3408
3718
  deps.onError(error instanceof Error ? error : new Error(String(error)));
3409
3719
  }
3410
3720
  }
3411
- return { exportImage, save };
3721
+ return { exporting, exportImage, save };
3412
3722
  }
3413
3723
  function useTextEditing(deps) {
3414
3724
  const { context } = deps;
@@ -3896,7 +4206,8 @@ function transformPoints(points, node) {
3896
4206
  function applyNodeTransform(annotation, node) {
3897
4207
  switch (annotation.type) {
3898
4208
  case "draw":
3899
- case "arrow": {
4209
+ case "arrow":
4210
+ case "line": {
3900
4211
  const points = transformPoints(annotation.points, node);
3901
4212
  const scale = (Math.abs(node.scaleX()) + Math.abs(node.scaleY())) / 2;
3902
4213
  const strokeWidth = Math.max(1, annotation.strokeWidth * scale);
@@ -4025,11 +4336,12 @@ const TOOL_LABELS = {
4025
4336
  rectangle: t("Rectangle"),
4026
4337
  ellipse: t("Ellipse"),
4027
4338
  arrow: t("Arrow"),
4339
+ line: t("Line"),
4028
4340
  sticker: t("Sticker"),
4029
4341
  redact: t("Redact")
4030
4342
  };
4031
4343
  function attachPointerTools(tool, deps) {
4032
- if (!["draw", "rectangle", "ellipse", "arrow", "text", "sticker", "redact"].includes(tool)) {
4344
+ if (!["draw", "rectangle", "ellipse", "arrow", "line", "text", "sticker", "redact"].includes(tool)) {
4033
4345
  return () => {
4034
4346
  };
4035
4347
  }
@@ -4047,7 +4359,7 @@ function attachPointerTools(tool, deps) {
4047
4359
  previewNode = null;
4048
4360
  return;
4049
4361
  }
4050
- if (previewNode !== null && (active.type === "draw" || active.type === "arrow")) {
4362
+ if (previewNode !== null && (active.type === "draw" || active.type === "arrow" || active.type === "line")) {
4051
4363
  previewNode.points(active.points);
4052
4364
  return;
4053
4365
  }
@@ -4078,7 +4390,8 @@ function attachPointerTools(tool, deps) {
4078
4390
  active = { id: newId(), type: "draw", points: [point.x, point.y], color: options.color, strokeWidth: options.strokeWidth };
4079
4391
  break;
4080
4392
  case "arrow":
4081
- active = { id: newId(), type: "arrow", points: [point.x, point.y, point.x, point.y], color: options.color, strokeWidth: options.strokeWidth };
4393
+ case "line":
4394
+ active = { id: newId(), type: tool, points: [point.x, point.y, point.x, point.y], color: options.color, strokeWidth: options.strokeWidth };
4082
4395
  break;
4083
4396
  case "rectangle":
4084
4397
  case "ellipse":
@@ -4125,6 +4438,7 @@ function attachPointerTools(tool, deps) {
4125
4438
  active.points.push(point.x, point.y);
4126
4439
  break;
4127
4440
  case "arrow":
4441
+ case "line":
4128
4442
  active.points = [start.x, start.y, point.x, point.y];
4129
4443
  break;
4130
4444
  case "rectangle":
@@ -4188,7 +4502,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
4188
4502
  src: {},
4189
4503
  label: {},
4190
4504
  exportOptions: {},
4191
- initialState: {}
4505
+ initialState: {},
4506
+ saving: { type: Boolean }
4192
4507
  },
4193
4508
  emits: ["save", "cancel", "error", "change"],
4194
4509
  setup(__props, { expose: __expose, emit: __emit }) {
@@ -4228,7 +4543,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
4228
4543
  return "grab";
4229
4544
  }
4230
4545
  const tool = context.activeTool.value;
4231
- if (["draw", "rectangle", "ellipse", "arrow", "text", "sticker", "redact"].includes(tool)) {
4546
+ if (["draw", "rectangle", "ellipse", "arrow", "line", "text", "sticker", "redact"].includes(tool)) {
4232
4547
  return "crosshair";
4233
4548
  }
4234
4549
  return "default";
@@ -4272,7 +4587,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
4272
4587
  viewOptions: () => viewOptions.value,
4273
4588
  oriented: () => orientedCanvas.value
4274
4589
  });
4275
- const { exportImage, save: onSave } = useExportImage({
4590
+ const { exporting, exportImage, save: onSave } = useExportImage({
4276
4591
  oriented: () => orientedCanvas.value,
4277
4592
  getState: () => context.state.value,
4278
4593
  source: () => props.src instanceof Blob ? props.src : null,
@@ -4654,9 +4969,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
4654
4969
  createVNode(EditorTopBar, {
4655
4970
  class: "image-editor__topbar",
4656
4971
  loaded: loaded.value,
4972
+ saving: unref(exporting) || __props.saving === true,
4657
4973
  onSave: unref(onSave),
4658
4974
  onCancel: _cache[2] || (_cache[2] = ($event) => emit("cancel"))
4659
- }, null, 8, ["loaded", "onSave"]),
4975
+ }, null, 8, ["loaded", "saving", "onSave"]),
4660
4976
  createVNode(EditorPanel, {
4661
4977
  class: normalizeClass(unref(context).activeMode.value === "filter" ? "image-editor__strip" : "image-editor__controls"),
4662
4978
  loaded: loaded.value,
@@ -4673,7 +4989,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
4673
4989
  };
4674
4990
  }
4675
4991
  });
4676
- const ImageEditor = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-e3346ccd"]]);
4992
+ const ImageEditor = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-5225c517"]]);
4677
4993
  export {
4678
4994
  ImageEditor,
4679
4995
  createInitialState,