@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.cjs CHANGED
@@ -76,14 +76,21 @@ function createInitialState() {
76
76
  flipX: false,
77
77
  flipY: false,
78
78
  crop: null,
79
- adjustments: { brightness: 0, contrast: 0, saturation: 0 },
79
+ adjustments: {
80
+ exposure: 0,
81
+ brightness: 0,
82
+ contrast: 0,
83
+ saturation: 0,
84
+ temperature: 0,
85
+ tint: 0,
86
+ sharpen: 0
87
+ },
80
88
  preset: "none",
81
89
  annotations: []
82
90
  };
83
91
  }
84
92
  function isPristine(state) {
85
- const { adjustments } = state;
86
- 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;
93
+ 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;
87
94
  }
88
95
  function orientedSize(natural, rotation) {
89
96
  return rotation % 180 === 0 ? { width: natural.width, height: natural.height } : { width: natural.height, height: natural.width };
@@ -118,6 +125,7 @@ function mapAnnotationCW(annotation, height) {
118
125
  case "draw":
119
126
  return { ...annotation, points: mapPointsCW(annotation.points, height) };
120
127
  case "arrow":
128
+ case "line":
121
129
  return { ...annotation, points: mapPointsCW(annotation.points, height) };
122
130
  case "rectangle":
123
131
  case "ellipse":
@@ -149,7 +157,8 @@ function rotateCW(state, oriented) {
149
157
  function mapAnnotationFlipX(annotation, width) {
150
158
  switch (annotation.type) {
151
159
  case "draw":
152
- case "arrow": {
160
+ case "arrow":
161
+ case "line": {
153
162
  const points = annotation.points.map((value, i) => i % 2 === 0 ? width - value : value);
154
163
  return { ...annotation, points };
155
164
  }
@@ -194,7 +203,8 @@ function flipHorizontal(state, oriented) {
194
203
  function mapAnnotationFlipY(annotation, height) {
195
204
  switch (annotation.type) {
196
205
  case "draw":
197
- case "arrow": {
206
+ case "arrow":
207
+ case "line": {
198
208
  const points = annotation.points.map((value, i) => i % 2 === 1 ? height - value : value);
199
209
  return { ...annotation, points };
200
210
  }
@@ -239,7 +249,8 @@ function flipVertical(state, oriented) {
239
249
  function translateAnnotation(annotation, dx, dy) {
240
250
  switch (annotation.type) {
241
251
  case "draw":
242
- case "arrow": {
252
+ case "arrow":
253
+ case "line": {
243
254
  const points = annotation.points.map((value, i) => value + (i % 2 === 0 ? dx : dy));
244
255
  return { ...annotation, points };
245
256
  }
@@ -258,7 +269,7 @@ function translateAnnotation(annotation, dx, dy) {
258
269
  function duplicateAnnotation(annotation, offset = 16) {
259
270
  return { ...translateAnnotation(annotation, offset, offset), id: newId() };
260
271
  }
261
- const _sfc_main$I = /* @__PURE__ */ vue.defineComponent({
272
+ const _sfc_main$N = /* @__PURE__ */ vue.defineComponent({
262
273
  __name: "GlassSurface",
263
274
  props: {
264
275
  variant: {}
@@ -280,8 +291,51 @@ const _export_sfc = (sfc, props) => {
280
291
  }
281
292
  return target;
282
293
  };
283
- const GlassSurface = /* @__PURE__ */ _export_sfc(_sfc_main$I, [["__scopeId", "data-v-b60e81d9"]]);
284
- const _sfc_main$H = {
294
+ const GlassSurface = /* @__PURE__ */ _export_sfc(_sfc_main$N, [["__scopeId", "data-v-b60e81d9"]]);
295
+ const _sfc_main$M = {
296
+ name: "CameraIrisIcon",
297
+ emits: ["click"],
298
+ props: {
299
+ title: {
300
+ type: String
301
+ },
302
+ fillColor: {
303
+ type: String,
304
+ default: "currentColor"
305
+ },
306
+ size: {
307
+ type: Number,
308
+ default: 24
309
+ }
310
+ }
311
+ };
312
+ const _hoisted_1$J = ["aria-hidden", "aria-label"];
313
+ const _hoisted_2$E = ["fill", "width", "height"];
314
+ 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" };
315
+ const _hoisted_4$A = { key: 0 };
316
+ function _sfc_render$w(_ctx, _cache, $props, $setup, $data, $options) {
317
+ return vue.openBlock(), vue.createElementBlock("span", vue.mergeProps(_ctx.$attrs, {
318
+ "aria-hidden": $props.title ? null : "true",
319
+ "aria-label": $props.title,
320
+ class: "material-design-icon camera-iris-icon",
321
+ role: "img",
322
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
323
+ }), [
324
+ (vue.openBlock(), vue.createElementBlock("svg", {
325
+ fill: $props.fillColor,
326
+ class: "material-design-icon__svg",
327
+ width: $props.size,
328
+ height: $props.size,
329
+ viewBox: "0 0 24 24"
330
+ }, [
331
+ vue.createElementVNode("path", _hoisted_3$B, [
332
+ $props.title ? (vue.openBlock(), vue.createElementBlock("title", _hoisted_4$A, vue.toDisplayString($props.title), 1)) : vue.createCommentVNode("", true)
333
+ ])
334
+ ], 8, _hoisted_2$E))
335
+ ], 16, _hoisted_1$J);
336
+ }
337
+ const CameraIris = /* @__PURE__ */ _export_sfc(_sfc_main$M, [["render", _sfc_render$w]]);
338
+ const _sfc_main$L = {
285
339
  name: "ContrastCircleIcon",
286
340
  emits: ["click"],
287
341
  props: {
@@ -298,11 +352,11 @@ const _sfc_main$H = {
298
352
  }
299
353
  }
300
354
  };
301
- const _hoisted_1$E = ["aria-hidden", "aria-label"];
302
- const _hoisted_2$z = ["fill", "width", "height"];
303
- 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" };
304
- const _hoisted_4$v = { key: 0 };
305
- function _sfc_render$r(_ctx, _cache, $props, $setup, $data, $options) {
355
+ const _hoisted_1$I = ["aria-hidden", "aria-label"];
356
+ const _hoisted_2$D = ["fill", "width", "height"];
357
+ 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" };
358
+ const _hoisted_4$z = { key: 0 };
359
+ function _sfc_render$v(_ctx, _cache, $props, $setup, $data, $options) {
306
360
  return vue.openBlock(), vue.createElementBlock("span", vue.mergeProps(_ctx.$attrs, {
307
361
  "aria-hidden": $props.title ? null : "true",
308
362
  "aria-label": $props.title,
@@ -317,14 +371,57 @@ function _sfc_render$r(_ctx, _cache, $props, $setup, $data, $options) {
317
371
  height: $props.size,
318
372
  viewBox: "0 0 24 24"
319
373
  }, [
320
- vue.createElementVNode("path", _hoisted_3$w, [
321
- $props.title ? (vue.openBlock(), vue.createElementBlock("title", _hoisted_4$v, vue.toDisplayString($props.title), 1)) : vue.createCommentVNode("", true)
374
+ vue.createElementVNode("path", _hoisted_3$A, [
375
+ $props.title ? (vue.openBlock(), vue.createElementBlock("title", _hoisted_4$z, vue.toDisplayString($props.title), 1)) : vue.createCommentVNode("", true)
322
376
  ])
323
- ], 8, _hoisted_2$z))
324
- ], 16, _hoisted_1$E);
377
+ ], 8, _hoisted_2$D))
378
+ ], 16, _hoisted_1$I);
325
379
  }
326
- const ContrastCircle = /* @__PURE__ */ _export_sfc(_sfc_main$H, [["render", _sfc_render$r]]);
327
- const _sfc_main$G = {
380
+ const ContrastCircle = /* @__PURE__ */ _export_sfc(_sfc_main$L, [["render", _sfc_render$v]]);
381
+ const _sfc_main$K = {
382
+ name: "ImageFilterCenterFocusIcon",
383
+ emits: ["click"],
384
+ props: {
385
+ title: {
386
+ type: String
387
+ },
388
+ fillColor: {
389
+ type: String,
390
+ default: "currentColor"
391
+ },
392
+ size: {
393
+ type: Number,
394
+ default: 24
395
+ }
396
+ }
397
+ };
398
+ const _hoisted_1$H = ["aria-hidden", "aria-label"];
399
+ const _hoisted_2$C = ["fill", "width", "height"];
400
+ 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" };
401
+ const _hoisted_4$y = { key: 0 };
402
+ function _sfc_render$u(_ctx, _cache, $props, $setup, $data, $options) {
403
+ return vue.openBlock(), vue.createElementBlock("span", vue.mergeProps(_ctx.$attrs, {
404
+ "aria-hidden": $props.title ? null : "true",
405
+ "aria-label": $props.title,
406
+ class: "material-design-icon image-filter-center-focus-icon",
407
+ role: "img",
408
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
409
+ }), [
410
+ (vue.openBlock(), vue.createElementBlock("svg", {
411
+ fill: $props.fillColor,
412
+ class: "material-design-icon__svg",
413
+ width: $props.size,
414
+ height: $props.size,
415
+ viewBox: "0 0 24 24"
416
+ }, [
417
+ vue.createElementVNode("path", _hoisted_3$z, [
418
+ $props.title ? (vue.openBlock(), vue.createElementBlock("title", _hoisted_4$y, vue.toDisplayString($props.title), 1)) : vue.createCommentVNode("", true)
419
+ ])
420
+ ], 8, _hoisted_2$C))
421
+ ], 16, _hoisted_1$H);
422
+ }
423
+ const ImageFilterCenterFocus = /* @__PURE__ */ _export_sfc(_sfc_main$K, [["render", _sfc_render$u]]);
424
+ const _sfc_main$J = {
328
425
  name: "InvertColorsIcon",
329
426
  emits: ["click"],
330
427
  props: {
@@ -341,11 +438,11 @@ const _sfc_main$G = {
341
438
  }
342
439
  }
343
440
  };
344
- const _hoisted_1$D = ["aria-hidden", "aria-label"];
345
- const _hoisted_2$y = ["fill", "width", "height"];
346
- 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" };
347
- const _hoisted_4$u = { key: 0 };
348
- function _sfc_render$q(_ctx, _cache, $props, $setup, $data, $options) {
441
+ const _hoisted_1$G = ["aria-hidden", "aria-label"];
442
+ const _hoisted_2$B = ["fill", "width", "height"];
443
+ 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" };
444
+ const _hoisted_4$x = { key: 0 };
445
+ function _sfc_render$t(_ctx, _cache, $props, $setup, $data, $options) {
349
446
  return vue.openBlock(), vue.createElementBlock("span", vue.mergeProps(_ctx.$attrs, {
350
447
  "aria-hidden": $props.title ? null : "true",
351
448
  "aria-label": $props.title,
@@ -360,14 +457,100 @@ function _sfc_render$q(_ctx, _cache, $props, $setup, $data, $options) {
360
457
  height: $props.size,
361
458
  viewBox: "0 0 24 24"
362
459
  }, [
363
- vue.createElementVNode("path", _hoisted_3$v, [
364
- $props.title ? (vue.openBlock(), vue.createElementBlock("title", _hoisted_4$u, vue.toDisplayString($props.title), 1)) : vue.createCommentVNode("", true)
460
+ vue.createElementVNode("path", _hoisted_3$y, [
461
+ $props.title ? (vue.openBlock(), vue.createElementBlock("title", _hoisted_4$x, vue.toDisplayString($props.title), 1)) : vue.createCommentVNode("", true)
365
462
  ])
366
- ], 8, _hoisted_2$y))
367
- ], 16, _hoisted_1$D);
463
+ ], 8, _hoisted_2$B))
464
+ ], 16, _hoisted_1$G);
465
+ }
466
+ const InvertColors = /* @__PURE__ */ _export_sfc(_sfc_main$J, [["render", _sfc_render$t]]);
467
+ const _sfc_main$I = {
468
+ name: "ThermometerIcon",
469
+ emits: ["click"],
470
+ props: {
471
+ title: {
472
+ type: String
473
+ },
474
+ fillColor: {
475
+ type: String,
476
+ default: "currentColor"
477
+ },
478
+ size: {
479
+ type: Number,
480
+ default: 24
481
+ }
482
+ }
483
+ };
484
+ const _hoisted_1$F = ["aria-hidden", "aria-label"];
485
+ const _hoisted_2$A = ["fill", "width", "height"];
486
+ 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" };
487
+ const _hoisted_4$w = { key: 0 };
488
+ function _sfc_render$s(_ctx, _cache, $props, $setup, $data, $options) {
489
+ return vue.openBlock(), vue.createElementBlock("span", vue.mergeProps(_ctx.$attrs, {
490
+ "aria-hidden": $props.title ? null : "true",
491
+ "aria-label": $props.title,
492
+ class: "material-design-icon thermometer-icon",
493
+ role: "img",
494
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
495
+ }), [
496
+ (vue.openBlock(), vue.createElementBlock("svg", {
497
+ fill: $props.fillColor,
498
+ class: "material-design-icon__svg",
499
+ width: $props.size,
500
+ height: $props.size,
501
+ viewBox: "0 0 24 24"
502
+ }, [
503
+ vue.createElementVNode("path", _hoisted_3$x, [
504
+ $props.title ? (vue.openBlock(), vue.createElementBlock("title", _hoisted_4$w, vue.toDisplayString($props.title), 1)) : vue.createCommentVNode("", true)
505
+ ])
506
+ ], 8, _hoisted_2$A))
507
+ ], 16, _hoisted_1$F);
368
508
  }
369
- const InvertColors = /* @__PURE__ */ _export_sfc(_sfc_main$G, [["render", _sfc_render$q]]);
370
- const _sfc_main$F = {
509
+ const Thermometer = /* @__PURE__ */ _export_sfc(_sfc_main$I, [["render", _sfc_render$s]]);
510
+ const _sfc_main$H = {
511
+ name: "WhiteBalanceIridescentIcon",
512
+ emits: ["click"],
513
+ props: {
514
+ title: {
515
+ type: String
516
+ },
517
+ fillColor: {
518
+ type: String,
519
+ default: "currentColor"
520
+ },
521
+ size: {
522
+ type: Number,
523
+ default: 24
524
+ }
525
+ }
526
+ };
527
+ const _hoisted_1$E = ["aria-hidden", "aria-label"];
528
+ const _hoisted_2$z = ["fill", "width", "height"];
529
+ 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" };
530
+ const _hoisted_4$v = { key: 0 };
531
+ function _sfc_render$r(_ctx, _cache, $props, $setup, $data, $options) {
532
+ return vue.openBlock(), vue.createElementBlock("span", vue.mergeProps(_ctx.$attrs, {
533
+ "aria-hidden": $props.title ? null : "true",
534
+ "aria-label": $props.title,
535
+ class: "material-design-icon white-balance-iridescent-icon",
536
+ role: "img",
537
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
538
+ }), [
539
+ (vue.openBlock(), vue.createElementBlock("svg", {
540
+ fill: $props.fillColor,
541
+ class: "material-design-icon__svg",
542
+ width: $props.size,
543
+ height: $props.size,
544
+ viewBox: "0 0 24 24"
545
+ }, [
546
+ vue.createElementVNode("path", _hoisted_3$w, [
547
+ $props.title ? (vue.openBlock(), vue.createElementBlock("title", _hoisted_4$v, vue.toDisplayString($props.title), 1)) : vue.createCommentVNode("", true)
548
+ ])
549
+ ], 8, _hoisted_2$z))
550
+ ], 16, _hoisted_1$E);
551
+ }
552
+ const WhiteBalanceIridescent = /* @__PURE__ */ _export_sfc(_sfc_main$H, [["render", _sfc_render$r]]);
553
+ const _sfc_main$G = {
371
554
  name: "WhiteBalanceSunnyIcon",
372
555
  emits: ["click"],
373
556
  props: {
@@ -384,11 +567,11 @@ const _sfc_main$F = {
384
567
  }
385
568
  }
386
569
  };
387
- const _hoisted_1$C = ["aria-hidden", "aria-label"];
388
- const _hoisted_2$x = ["fill", "width", "height"];
389
- 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" };
390
- const _hoisted_4$t = { key: 0 };
391
- function _sfc_render$p(_ctx, _cache, $props, $setup, $data, $options) {
570
+ const _hoisted_1$D = ["aria-hidden", "aria-label"];
571
+ const _hoisted_2$y = ["fill", "width", "height"];
572
+ 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" };
573
+ const _hoisted_4$u = { key: 0 };
574
+ function _sfc_render$q(_ctx, _cache, $props, $setup, $data, $options) {
392
575
  return vue.openBlock(), vue.createElementBlock("span", vue.mergeProps(_ctx.$attrs, {
393
576
  "aria-hidden": $props.title ? null : "true",
394
577
  "aria-label": $props.title,
@@ -403,16 +586,16 @@ function _sfc_render$p(_ctx, _cache, $props, $setup, $data, $options) {
403
586
  height: $props.size,
404
587
  viewBox: "0 0 24 24"
405
588
  }, [
406
- vue.createElementVNode("path", _hoisted_3$u, [
407
- $props.title ? (vue.openBlock(), vue.createElementBlock("title", _hoisted_4$t, vue.toDisplayString($props.title), 1)) : vue.createCommentVNode("", true)
589
+ vue.createElementVNode("path", _hoisted_3$v, [
590
+ $props.title ? (vue.openBlock(), vue.createElementBlock("title", _hoisted_4$u, vue.toDisplayString($props.title), 1)) : vue.createCommentVNode("", true)
408
591
  ])
409
- ], 8, _hoisted_2$x))
410
- ], 16, _hoisted_1$C);
592
+ ], 8, _hoisted_2$y))
593
+ ], 16, _hoisted_1$D);
411
594
  }
412
- const WhiteBalanceSunny = /* @__PURE__ */ _export_sfc(_sfc_main$F, [["render", _sfc_render$p]]);
413
- const _hoisted_1$B = { class: "editor-slider" };
414
- const _hoisted_2$w = ["value", "data-test", "disabled", "aria-label", "min", "max", "step"];
415
- const _sfc_main$E = /* @__PURE__ */ vue.defineComponent({
595
+ const WhiteBalanceSunny = /* @__PURE__ */ _export_sfc(_sfc_main$G, [["render", _sfc_render$q]]);
596
+ const _hoisted_1$C = { class: "editor-slider" };
597
+ const _hoisted_2$x = ["value", "data-test", "disabled", "aria-label", "min", "max", "step"];
598
+ const _sfc_main$F = /* @__PURE__ */ vue.defineComponent({
416
599
  __name: "EditorSlider",
417
600
  props: {
418
601
  value: {},
@@ -432,7 +615,7 @@ const _sfc_main$E = /* @__PURE__ */ vue.defineComponent({
432
615
  emit("input", Number(event.target.value));
433
616
  }
434
617
  return (_ctx, _cache) => {
435
- return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$B, [
618
+ return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$C, [
436
619
  vue.createElementVNode("input", {
437
620
  value: props.value,
438
621
  "data-test": __props.dataTest,
@@ -444,7 +627,7 @@ const _sfc_main$E = /* @__PURE__ */ vue.defineComponent({
444
627
  step: __props.step,
445
628
  onInput,
446
629
  onChange: _cache[0] || (_cache[0] = ($event) => emit("commit"))
447
- }, null, 40, _hoisted_2$w),
630
+ }, null, 40, _hoisted_2$x),
448
631
  vue.createElementVNode("output", null, [
449
632
  vue.renderSlot(_ctx.$slots, "preview", {}, () => [
450
633
  vue.createTextVNode(vue.toDisplayString(__props.display ?? props.value), 1)
@@ -454,9 +637,9 @@ const _sfc_main$E = /* @__PURE__ */ vue.defineComponent({
454
637
  };
455
638
  }
456
639
  });
457
- const EditorSlider = /* @__PURE__ */ _export_sfc(_sfc_main$E, [["__scopeId", "data-v-6562ea60"]]);
458
- const _hoisted_1$A = ["disabled", "data-test", "aria-pressed"];
459
- const _sfc_main$D = /* @__PURE__ */ vue.defineComponent({
640
+ const EditorSlider = /* @__PURE__ */ _export_sfc(_sfc_main$F, [["__scopeId", "data-v-6562ea60"]]);
641
+ const _hoisted_1$B = ["disabled", "data-test", "aria-pressed"];
642
+ const _sfc_main$E = /* @__PURE__ */ vue.defineComponent({
460
643
  __name: "IconTab",
461
644
  props: {
462
645
  label: {},
@@ -478,11 +661,11 @@ const _sfc_main$D = /* @__PURE__ */ vue.defineComponent({
478
661
  }, [
479
662
  vue.renderSlot(_ctx.$slots, "default", {}, void 0, true),
480
663
  vue.createElementVNode("span", null, vue.toDisplayString(__props.label), 1)
481
- ], 10, _hoisted_1$A);
664
+ ], 10, _hoisted_1$B);
482
665
  };
483
666
  }
484
667
  });
485
- const IconTab = /* @__PURE__ */ _export_sfc(_sfc_main$D, [["__scopeId", "data-v-95889662"]]);
668
+ const IconTab = /* @__PURE__ */ _export_sfc(_sfc_main$E, [["__scopeId", "data-v-95889662"]]);
486
669
  const gtBuilder = gettext.getGettextBuilder().detectLocale();
487
670
  [].map((data) => gtBuilder.addTranslation(data.locale, data.json));
488
671
  const gt = gtBuilder.build();
@@ -638,9 +821,9 @@ function useEditorContext() {
638
821
  }
639
822
  return context;
640
823
  }
641
- const _hoisted_1$z = { class: "adjust-panel" };
642
- const _hoisted_2$v = { class: "adjust-panel__tabs" };
643
- const _sfc_main$C = /* @__PURE__ */ vue.defineComponent({
824
+ const _hoisted_1$A = { class: "adjust-panel" };
825
+ const _hoisted_2$w = { class: "adjust-panel__tabs" };
826
+ const _sfc_main$D = /* @__PURE__ */ vue.defineComponent({
644
827
  __name: "AdjustPanel",
645
828
  props: {
646
829
  loaded: { type: Boolean }
@@ -648,11 +831,15 @@ const _sfc_main$C = /* @__PURE__ */ vue.defineComponent({
648
831
  setup(__props) {
649
832
  const context = useEditorContext();
650
833
  const adjustments = [
834
+ { id: "exposure", label: t("Exposure"), icon: CameraIris },
651
835
  { id: "brightness", label: t("Brightness"), icon: WhiteBalanceSunny },
652
836
  { id: "contrast", label: t("Contrast"), icon: ContrastCircle },
653
- { id: "saturation", label: t("Saturation"), icon: InvertColors }
837
+ { id: "saturation", label: t("Saturation"), icon: InvertColors },
838
+ { id: "temperature", label: t("Temperature"), icon: Thermometer },
839
+ { id: "tint", label: t("Tint"), icon: WhiteBalanceIridescent },
840
+ { id: "sharpen", label: t("Sharpen"), icon: ImageFilterCenterFocus }
654
841
  ];
655
- const activeAdjustment = vue.shallowRef("brightness");
842
+ const activeAdjustment = vue.shallowRef("exposure");
656
843
  const display = vue.computed(() => {
657
844
  const value = context.state.value.adjustments[activeAdjustment.value];
658
845
  return value > 0 ? `+${value}` : `${value}`;
@@ -668,8 +855,8 @@ const _sfc_main$C = /* @__PURE__ */ vue.defineComponent({
668
855
  context.commit(context.state.value, adjustments.find((entry) => entry.id === activeAdjustment.value).label);
669
856
  }
670
857
  return (_ctx, _cache) => {
671
- return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$z, [
672
- vue.createElementVNode("div", _hoisted_2$v, [
858
+ return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$A, [
859
+ vue.createElementVNode("div", _hoisted_2$w, [
673
860
  (vue.openBlock(), vue.createElementBlock(vue.Fragment, null, vue.renderList(adjustments, (adjustment) => {
674
861
  return vue.createVNode(IconTab, {
675
862
  key: adjustment.id,
@@ -702,8 +889,8 @@ const _sfc_main$C = /* @__PURE__ */ vue.defineComponent({
702
889
  };
703
890
  }
704
891
  });
705
- const AdjustPanel = /* @__PURE__ */ _export_sfc(_sfc_main$C, [["__scopeId", "data-v-d7f848cf"]]);
706
- const _sfc_main$B = {
892
+ const AdjustPanel = /* @__PURE__ */ _export_sfc(_sfc_main$D, [["__scopeId", "data-v-3ad63bd8"]]);
893
+ const _sfc_main$C = {
707
894
  name: "ArrowTopRightIcon",
708
895
  emits: ["click"],
709
896
  props: {
@@ -720,15 +907,58 @@ const _sfc_main$B = {
720
907
  }
721
908
  }
722
909
  };
910
+ const _hoisted_1$z = ["aria-hidden", "aria-label"];
911
+ const _hoisted_2$v = ["fill", "width", "height"];
912
+ const _hoisted_3$u = { d: "M5,17.59L15.59,7H9V5H19V15H17V8.41L6.41,19L5,17.59Z" };
913
+ const _hoisted_4$t = { key: 0 };
914
+ function _sfc_render$p(_ctx, _cache, $props, $setup, $data, $options) {
915
+ return vue.openBlock(), vue.createElementBlock("span", vue.mergeProps(_ctx.$attrs, {
916
+ "aria-hidden": $props.title ? null : "true",
917
+ "aria-label": $props.title,
918
+ class: "material-design-icon arrow-top-right-icon",
919
+ role: "img",
920
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
921
+ }), [
922
+ (vue.openBlock(), vue.createElementBlock("svg", {
923
+ fill: $props.fillColor,
924
+ class: "material-design-icon__svg",
925
+ width: $props.size,
926
+ height: $props.size,
927
+ viewBox: "0 0 24 24"
928
+ }, [
929
+ vue.createElementVNode("path", _hoisted_3$u, [
930
+ $props.title ? (vue.openBlock(), vue.createElementBlock("title", _hoisted_4$t, vue.toDisplayString($props.title), 1)) : vue.createCommentVNode("", true)
931
+ ])
932
+ ], 8, _hoisted_2$v))
933
+ ], 16, _hoisted_1$z);
934
+ }
935
+ const ArrowTopRight = /* @__PURE__ */ _export_sfc(_sfc_main$C, [["render", _sfc_render$p]]);
936
+ const _sfc_main$B = {
937
+ name: "EllipseOutlineIcon",
938
+ emits: ["click"],
939
+ props: {
940
+ title: {
941
+ type: String
942
+ },
943
+ fillColor: {
944
+ type: String,
945
+ default: "currentColor"
946
+ },
947
+ size: {
948
+ type: Number,
949
+ default: 24
950
+ }
951
+ }
952
+ };
723
953
  const _hoisted_1$y = ["aria-hidden", "aria-label"];
724
954
  const _hoisted_2$u = ["fill", "width", "height"];
725
- const _hoisted_3$t = { d: "M5,17.59L15.59,7H9V5H19V15H17V8.41L6.41,19L5,17.59Z" };
955
+ 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" };
726
956
  const _hoisted_4$s = { key: 0 };
727
957
  function _sfc_render$o(_ctx, _cache, $props, $setup, $data, $options) {
728
958
  return vue.openBlock(), vue.createElementBlock("span", vue.mergeProps(_ctx.$attrs, {
729
959
  "aria-hidden": $props.title ? null : "true",
730
960
  "aria-label": $props.title,
731
- class: "material-design-icon arrow-top-right-icon",
961
+ class: "material-design-icon ellipse-outline-icon",
732
962
  role: "img",
733
963
  onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
734
964
  }), [
@@ -745,9 +975,9 @@ function _sfc_render$o(_ctx, _cache, $props, $setup, $data, $options) {
745
975
  ], 8, _hoisted_2$u))
746
976
  ], 16, _hoisted_1$y);
747
977
  }
748
- const ArrowTopRight = /* @__PURE__ */ _export_sfc(_sfc_main$B, [["render", _sfc_render$o]]);
978
+ const EllipseOutline = /* @__PURE__ */ _export_sfc(_sfc_main$B, [["render", _sfc_render$o]]);
749
979
  const _sfc_main$A = {
750
- name: "EllipseOutlineIcon",
980
+ name: "FormatTextIcon",
751
981
  emits: ["click"],
752
982
  props: {
753
983
  title: {
@@ -765,13 +995,13 @@ const _sfc_main$A = {
765
995
  };
766
996
  const _hoisted_1$x = ["aria-hidden", "aria-label"];
767
997
  const _hoisted_2$t = ["fill", "width", "height"];
768
- 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" };
998
+ 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" };
769
999
  const _hoisted_4$r = { key: 0 };
770
1000
  function _sfc_render$n(_ctx, _cache, $props, $setup, $data, $options) {
771
1001
  return vue.openBlock(), vue.createElementBlock("span", vue.mergeProps(_ctx.$attrs, {
772
1002
  "aria-hidden": $props.title ? null : "true",
773
1003
  "aria-label": $props.title,
774
- class: "material-design-icon ellipse-outline-icon",
1004
+ class: "material-design-icon format-text-icon",
775
1005
  role: "img",
776
1006
  onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
777
1007
  }), [
@@ -788,9 +1018,9 @@ function _sfc_render$n(_ctx, _cache, $props, $setup, $data, $options) {
788
1018
  ], 8, _hoisted_2$t))
789
1019
  ], 16, _hoisted_1$x);
790
1020
  }
791
- const EllipseOutline = /* @__PURE__ */ _export_sfc(_sfc_main$A, [["render", _sfc_render$n]]);
1021
+ const FormatText = /* @__PURE__ */ _export_sfc(_sfc_main$A, [["render", _sfc_render$n]]);
792
1022
  const _sfc_main$z = {
793
- name: "FormatTextIcon",
1023
+ name: "PencilIcon",
794
1024
  emits: ["click"],
795
1025
  props: {
796
1026
  title: {
@@ -808,13 +1038,13 @@ const _sfc_main$z = {
808
1038
  };
809
1039
  const _hoisted_1$w = ["aria-hidden", "aria-label"];
810
1040
  const _hoisted_2$s = ["fill", "width", "height"];
811
- 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" };
1041
+ 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" };
812
1042
  const _hoisted_4$q = { key: 0 };
813
1043
  function _sfc_render$m(_ctx, _cache, $props, $setup, $data, $options) {
814
1044
  return vue.openBlock(), vue.createElementBlock("span", vue.mergeProps(_ctx.$attrs, {
815
1045
  "aria-hidden": $props.title ? null : "true",
816
1046
  "aria-label": $props.title,
817
- class: "material-design-icon format-text-icon",
1047
+ class: "material-design-icon pencil-icon",
818
1048
  role: "img",
819
1049
  onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
820
1050
  }), [
@@ -831,9 +1061,9 @@ function _sfc_render$m(_ctx, _cache, $props, $setup, $data, $options) {
831
1061
  ], 8, _hoisted_2$s))
832
1062
  ], 16, _hoisted_1$w);
833
1063
  }
834
- const FormatText = /* @__PURE__ */ _export_sfc(_sfc_main$z, [["render", _sfc_render$m]]);
1064
+ const Pencil = /* @__PURE__ */ _export_sfc(_sfc_main$z, [["render", _sfc_render$m]]);
835
1065
  const _sfc_main$y = {
836
- name: "PencilIcon",
1066
+ name: "RectangleOutlineIcon",
837
1067
  emits: ["click"],
838
1068
  props: {
839
1069
  title: {
@@ -851,13 +1081,13 @@ const _sfc_main$y = {
851
1081
  };
852
1082
  const _hoisted_1$v = ["aria-hidden", "aria-label"];
853
1083
  const _hoisted_2$r = ["fill", "width", "height"];
854
- 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" };
1084
+ const _hoisted_3$q = { d: "M4,6V19H20V6H4M18,17H6V8H18V17Z" };
855
1085
  const _hoisted_4$p = { key: 0 };
856
1086
  function _sfc_render$l(_ctx, _cache, $props, $setup, $data, $options) {
857
1087
  return vue.openBlock(), vue.createElementBlock("span", vue.mergeProps(_ctx.$attrs, {
858
1088
  "aria-hidden": $props.title ? null : "true",
859
1089
  "aria-label": $props.title,
860
- class: "material-design-icon pencil-icon",
1090
+ class: "material-design-icon rectangle-outline-icon",
861
1091
  role: "img",
862
1092
  onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
863
1093
  }), [
@@ -874,9 +1104,9 @@ function _sfc_render$l(_ctx, _cache, $props, $setup, $data, $options) {
874
1104
  ], 8, _hoisted_2$r))
875
1105
  ], 16, _hoisted_1$v);
876
1106
  }
877
- const Pencil = /* @__PURE__ */ _export_sfc(_sfc_main$y, [["render", _sfc_render$l]]);
1107
+ const RectangleOutline = /* @__PURE__ */ _export_sfc(_sfc_main$y, [["render", _sfc_render$l]]);
878
1108
  const _sfc_main$x = {
879
- name: "RectangleOutlineIcon",
1109
+ name: "VectorLineIcon",
880
1110
  emits: ["click"],
881
1111
  props: {
882
1112
  title: {
@@ -894,13 +1124,13 @@ const _sfc_main$x = {
894
1124
  };
895
1125
  const _hoisted_1$u = ["aria-hidden", "aria-label"];
896
1126
  const _hoisted_2$q = ["fill", "width", "height"];
897
- const _hoisted_3$p = { d: "M4,6V19H20V6H4M18,17H6V8H18V17Z" };
1127
+ const _hoisted_3$p = { d: "M15,3V7.59L7.59,15H3V21H9V16.42L16.42,9H21V3M17,5H19V7H17M5,17H7V19H5" };
898
1128
  const _hoisted_4$o = { key: 0 };
899
1129
  function _sfc_render$k(_ctx, _cache, $props, $setup, $data, $options) {
900
1130
  return vue.openBlock(), vue.createElementBlock("span", vue.mergeProps(_ctx.$attrs, {
901
1131
  "aria-hidden": $props.title ? null : "true",
902
1132
  "aria-label": $props.title,
903
- class: "material-design-icon rectangle-outline-icon",
1133
+ class: "material-design-icon vector-line-icon",
904
1134
  role: "img",
905
1135
  onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event))
906
1136
  }), [
@@ -917,7 +1147,7 @@ function _sfc_render$k(_ctx, _cache, $props, $setup, $data, $options) {
917
1147
  ], 8, _hoisted_2$q))
918
1148
  ], 16, _hoisted_1$u);
919
1149
  }
920
- const RectangleOutline = /* @__PURE__ */ _export_sfc(_sfc_main$x, [["render", _sfc_render$k]]);
1150
+ const VectorLine = /* @__PURE__ */ _export_sfc(_sfc_main$x, [["render", _sfc_render$k]]);
921
1151
  function useAnnotationColor(context) {
922
1152
  function recolorable() {
923
1153
  const annotation = context.state.value.annotations.find((entry) => entry.id === context.selectedId.value);
@@ -972,9 +1202,10 @@ const _sfc_main$w = /* @__PURE__ */ vue.defineComponent({
972
1202
  { id: "rectangle", label: t("Rectangle"), icon: RectangleOutline },
973
1203
  { id: "ellipse", label: t("Ellipse"), icon: EllipseOutline },
974
1204
  { id: "arrow", label: t("Arrow"), icon: ArrowTopRight },
1205
+ { id: "line", label: t("Line"), icon: VectorLine },
975
1206
  { id: "text", label: t("Text"), icon: FormatText }
976
1207
  ];
977
- const showStrokeOptions = vue.computed(() => ["draw", "rectangle", "ellipse", "arrow"].includes(context.activeTool.value));
1208
+ const showStrokeOptions = vue.computed(() => ["draw", "rectangle", "ellipse", "arrow", "line"].includes(context.activeTool.value));
978
1209
  const viewScale = vue.computed(() => (context.viewFit.value?.scale ?? 1) * context.viewZoom.value);
979
1210
  const strokePreview = vue.computed(() => Math.min(PREVIEW_CAP, Math.max(2, context.strokeWidth.value * viewScale.value)));
980
1211
  const fontPreview = vue.computed(() => Math.min(PREVIEW_CAP, Math.max(8, context.fontSize.value * viewScale.value)));
@@ -1059,7 +1290,7 @@ const _sfc_main$w = /* @__PURE__ */ vue.defineComponent({
1059
1290
  };
1060
1291
  }
1061
1292
  });
1062
- const AnnotatePanel = /* @__PURE__ */ _export_sfc(_sfc_main$w, [["__scopeId", "data-v-1bf135e4"]]);
1293
+ const AnnotatePanel = /* @__PURE__ */ _export_sfc(_sfc_main$w, [["__scopeId", "data-v-f7e8a194"]]);
1063
1294
  const _sfc_main$v = {
1064
1295
  name: "FlipHorizontalIcon",
1065
1296
  emits: ["click"],
@@ -1459,6 +1690,43 @@ const PresetChip = /* @__PURE__ */ _export_sfc(_sfc_main$q, [["__scopeId", "data
1459
1690
  function luma(r, g, b) {
1460
1691
  return 0.299 * r + 0.587 * g + 0.114 * b;
1461
1692
  }
1693
+ const EXPOSURE_RANGE = 2;
1694
+ const TEMPERATURE_RANGE = 0.25;
1695
+ const TINT_RANGE = 0.25;
1696
+ function tone(imageData) {
1697
+ const { data } = imageData;
1698
+ const exposure = 2 ** ((this.getAttr("exposure") ?? 0) * EXPOSURE_RANGE);
1699
+ const temperature = (this.getAttr("temperature") ?? 0) * TEMPERATURE_RANGE;
1700
+ const tint = (this.getAttr("tint") ?? 0) * TINT_RANGE;
1701
+ const red = exposure * (1 + temperature) * (1 + tint / 2);
1702
+ const green = exposure * (1 - tint);
1703
+ const blue = exposure * (1 - temperature) * (1 + tint / 2);
1704
+ for (let i = 0; i < data.length; i += 4) {
1705
+ data[i] = data[i] * red;
1706
+ data[i + 1] = data[i + 1] * green;
1707
+ data[i + 2] = data[i + 2] * blue;
1708
+ }
1709
+ }
1710
+ const SHARPEN_RANGE = 1.2;
1711
+ function sharpen(imageData) {
1712
+ const amount = (this.getAttr("sharpen") ?? 0) * SHARPEN_RANGE;
1713
+ if (amount === 0) {
1714
+ return;
1715
+ }
1716
+ const { data, width, height } = imageData;
1717
+ const source = new Uint8ClampedArray(data);
1718
+ const stride = width * 4;
1719
+ for (let y = 1; y < height - 1; y++) {
1720
+ for (let x = 1; x < width - 1; x++) {
1721
+ const centre = y * stride + x * 4;
1722
+ for (let channel = 0; channel < 3; channel++) {
1723
+ const i = centre + channel;
1724
+ const neighbours = source[i - 4] + source[i + 4] + source[i - stride] + source[i + stride];
1725
+ data[i] = source[i] * (1 + 4 * amount) - amount * neighbours;
1726
+ }
1727
+ }
1728
+ }
1729
+ }
1462
1730
  function saturate(imageData) {
1463
1731
  const { data } = imageData;
1464
1732
  const factor = this.saturation() + 1;
@@ -1621,6 +1889,14 @@ function buildAnnotationNode(annotation, oriented) {
1621
1889
  lineCap: "round",
1622
1890
  lineJoin: "round"
1623
1891
  });
1892
+ case "line":
1893
+ return new Konva__default.default.Line({
1894
+ ...base,
1895
+ points: [...annotation.points],
1896
+ stroke: annotation.color,
1897
+ strokeWidth: annotation.strokeWidth,
1898
+ lineCap: "round"
1899
+ });
1624
1900
  case "arrow":
1625
1901
  return new Konva__default.default.Arrow({
1626
1902
  ...base,
@@ -1679,8 +1955,11 @@ function buildAnnotationNode(annotation, oriented) {
1679
1955
  }
1680
1956
  }
1681
1957
  function applyFilters(node, state, pixelRatio = 1) {
1682
- const { brightness, contrast, saturation } = state.adjustments;
1958
+ const { exposure, brightness, contrast, saturation, temperature, tint } = state.adjustments;
1683
1959
  const filters = [];
1960
+ if (exposure !== 0 || temperature !== 0 || tint !== 0) {
1961
+ filters.push(tone);
1962
+ }
1684
1963
  if (brightness !== 0) {
1685
1964
  filters.push(Konva__default.default.Filters.Brighten);
1686
1965
  }
@@ -1712,6 +1991,9 @@ function applyFilters(node, state, pixelRatio = 1) {
1712
1991
  if (presetFilters !== null) {
1713
1992
  filters.push(presetFilters);
1714
1993
  }
1994
+ if (state.adjustments.sharpen !== 0) {
1995
+ filters.push(sharpen);
1996
+ }
1715
1997
  if (filters.length === 0) {
1716
1998
  node.filters([]);
1717
1999
  node.clearCache();
@@ -1721,6 +2003,10 @@ function applyFilters(node, state, pixelRatio = 1) {
1721
2003
  node.brightness(brightness / 100);
1722
2004
  node.contrast(contrast);
1723
2005
  node.saturation(saturation / 100);
2006
+ node.setAttr("exposure", exposure / 100);
2007
+ node.setAttr("temperature", temperature / 100);
2008
+ node.setAttr("tint", tint / 100);
2009
+ node.setAttr("sharpen", state.adjustments.sharpen / 100);
1724
2010
  if (state.preset === "posterize") {
1725
2011
  node.levels(0.02);
1726
2012
  }
@@ -1736,9 +2022,7 @@ function thumbnailKey(state) {
1736
2022
  crop?.y,
1737
2023
  crop?.width,
1738
2024
  crop?.height,
1739
- adjustments.brightness,
1740
- adjustments.contrast,
1741
- adjustments.saturation
2025
+ ...Object.values(adjustments)
1742
2026
  ].join("|");
1743
2027
  }
1744
2028
  function presetThumbnail(oriented, state, preset, size = 96) {
@@ -1793,8 +2077,7 @@ function createScene(stage) {
1793
2077
  viewGroup.clipHeight(void 0);
1794
2078
  }
1795
2079
  const pixelRatio = options.fastFilters ? Math.min(1, options.scale * (globalThis.devicePixelRatio || 1)) : 1;
1796
- const { brightness, contrast, saturation } = state.adjustments;
1797
- const nextFilterKey = `${brightness}|${contrast}|${saturation}|${state.preset}|${pixelRatio}`;
2080
+ const nextFilterKey = `${Object.values(state.adjustments).join("|")}|${state.preset}|${pixelRatio}`;
1798
2081
  if (orientedChanged || nextFilterKey !== filterKey) {
1799
2082
  applyFilters(imageNode, state, pixelRatio);
1800
2083
  filterKey = nextFilterKey;
@@ -2768,7 +3051,8 @@ const _hoisted_4$3 = { class: "editor-topbar__actions" };
2768
3051
  const _sfc_main$5 = /* @__PURE__ */ vue.defineComponent({
2769
3052
  __name: "EditorTopBar",
2770
3053
  props: {
2771
- loaded: { type: Boolean }
3054
+ loaded: { type: Boolean },
3055
+ saving: { type: Boolean }
2772
3056
  },
2773
3057
  emits: ["save", "cancel"],
2774
3058
  setup(__props, { emit: __emit }) {
@@ -2951,20 +3235,31 @@ const _sfc_main$5 = /* @__PURE__ */ vue.defineComponent({
2951
3235
  vue.createVNode(vue.unref(NcButton__default.default), {
2952
3236
  "data-test": "save",
2953
3237
  variant: "primary",
2954
- disabled: !__props.loaded,
3238
+ disabled: !__props.loaded || __props.saving,
2955
3239
  onClick: _cache[6] || (_cache[6] = ($event) => emit("save"))
2956
- }, {
3240
+ }, vue.createSlots({
2957
3241
  default: vue.withCtx(() => [
2958
- vue.createTextVNode(vue.toDisplayString(labels.save), 1)
3242
+ vue.createTextVNode(" " + vue.toDisplayString(labels.save), 1)
2959
3243
  ]),
2960
- _: 1
2961
- }, 8, ["disabled"])
3244
+ _: 2
3245
+ }, [
3246
+ __props.saving ? {
3247
+ name: "icon",
3248
+ fn: vue.withCtx(() => [
3249
+ vue.createVNode(vue.unref(NcLoadingIcon__default.default), {
3250
+ size: 20,
3251
+ "data-test": "saving"
3252
+ })
3253
+ ]),
3254
+ key: "0"
3255
+ } : void 0
3256
+ ]), 1032, ["disabled"])
2962
3257
  ])
2963
3258
  ]);
2964
3259
  };
2965
3260
  }
2966
3261
  });
2967
- const EditorTopBar = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-9ba9bd23"]]);
3262
+ const EditorTopBar = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-50392429"]]);
2968
3263
  const _sfc_main$4 = {
2969
3264
  name: "ContentCopyIcon",
2970
3265
  emits: ["click"],
@@ -3385,7 +3680,13 @@ async function canvasToBlob(canvas, type = "image/png", quality) {
3385
3680
  }, type, quality);
3386
3681
  });
3387
3682
  }
3683
+ async function painted() {
3684
+ await new Promise((resolve) => {
3685
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve(null)));
3686
+ });
3687
+ }
3388
3688
  function useExportImage(deps) {
3689
+ const exporting = vue.ref(false);
3389
3690
  async function exportImage(options = {}) {
3390
3691
  const oriented = deps.oriented();
3391
3692
  if (oriented === null) {
@@ -3395,6 +3696,15 @@ function useExportImage(deps) {
3395
3696
  if (source !== null && source.type !== "" && isPristine(deps.getState()) && options.maxSize === void 0 && (options.format === void 0 || options.format === source.type)) {
3396
3697
  return { blob: source, width: oriented.width, height: oriented.height, mimeType: source.type };
3397
3698
  }
3699
+ exporting.value = true;
3700
+ await painted();
3701
+ try {
3702
+ return await encode(oriented, options);
3703
+ } finally {
3704
+ exporting.value = false;
3705
+ }
3706
+ }
3707
+ async function encode(oriented, options) {
3398
3708
  const canvas = renderToCanvas(oriented, deps.getState(), options.maxSize);
3399
3709
  const mimeType = options.format ?? "image/png";
3400
3710
  try {
@@ -3417,7 +3727,7 @@ function useExportImage(deps) {
3417
3727
  deps.onError(error instanceof Error ? error : new Error(String(error)));
3418
3728
  }
3419
3729
  }
3420
- return { exportImage, save };
3730
+ return { exporting, exportImage, save };
3421
3731
  }
3422
3732
  function useTextEditing(deps) {
3423
3733
  const { context } = deps;
@@ -3905,7 +4215,8 @@ function transformPoints(points, node) {
3905
4215
  function applyNodeTransform(annotation, node) {
3906
4216
  switch (annotation.type) {
3907
4217
  case "draw":
3908
- case "arrow": {
4218
+ case "arrow":
4219
+ case "line": {
3909
4220
  const points = transformPoints(annotation.points, node);
3910
4221
  const scale = (Math.abs(node.scaleX()) + Math.abs(node.scaleY())) / 2;
3911
4222
  const strokeWidth = Math.max(1, annotation.strokeWidth * scale);
@@ -4034,11 +4345,12 @@ const TOOL_LABELS = {
4034
4345
  rectangle: t("Rectangle"),
4035
4346
  ellipse: t("Ellipse"),
4036
4347
  arrow: t("Arrow"),
4348
+ line: t("Line"),
4037
4349
  sticker: t("Sticker"),
4038
4350
  redact: t("Redact")
4039
4351
  };
4040
4352
  function attachPointerTools(tool, deps) {
4041
- if (!["draw", "rectangle", "ellipse", "arrow", "text", "sticker", "redact"].includes(tool)) {
4353
+ if (!["draw", "rectangle", "ellipse", "arrow", "line", "text", "sticker", "redact"].includes(tool)) {
4042
4354
  return () => {
4043
4355
  };
4044
4356
  }
@@ -4056,7 +4368,7 @@ function attachPointerTools(tool, deps) {
4056
4368
  previewNode = null;
4057
4369
  return;
4058
4370
  }
4059
- if (previewNode !== null && (active.type === "draw" || active.type === "arrow")) {
4371
+ if (previewNode !== null && (active.type === "draw" || active.type === "arrow" || active.type === "line")) {
4060
4372
  previewNode.points(active.points);
4061
4373
  return;
4062
4374
  }
@@ -4087,7 +4399,8 @@ function attachPointerTools(tool, deps) {
4087
4399
  active = { id: newId(), type: "draw", points: [point.x, point.y], color: options.color, strokeWidth: options.strokeWidth };
4088
4400
  break;
4089
4401
  case "arrow":
4090
- active = { id: newId(), type: "arrow", points: [point.x, point.y, point.x, point.y], color: options.color, strokeWidth: options.strokeWidth };
4402
+ case "line":
4403
+ active = { id: newId(), type: tool, points: [point.x, point.y, point.x, point.y], color: options.color, strokeWidth: options.strokeWidth };
4091
4404
  break;
4092
4405
  case "rectangle":
4093
4406
  case "ellipse":
@@ -4134,6 +4447,7 @@ function attachPointerTools(tool, deps) {
4134
4447
  active.points.push(point.x, point.y);
4135
4448
  break;
4136
4449
  case "arrow":
4450
+ case "line":
4137
4451
  active.points = [start.x, start.y, point.x, point.y];
4138
4452
  break;
4139
4453
  case "rectangle":
@@ -4197,7 +4511,8 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
4197
4511
  src: {},
4198
4512
  label: {},
4199
4513
  exportOptions: {},
4200
- initialState: {}
4514
+ initialState: {},
4515
+ saving: { type: Boolean }
4201
4516
  },
4202
4517
  emits: ["save", "cancel", "error", "change"],
4203
4518
  setup(__props, { expose: __expose, emit: __emit }) {
@@ -4237,7 +4552,7 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
4237
4552
  return "grab";
4238
4553
  }
4239
4554
  const tool = context.activeTool.value;
4240
- if (["draw", "rectangle", "ellipse", "arrow", "text", "sticker", "redact"].includes(tool)) {
4555
+ if (["draw", "rectangle", "ellipse", "arrow", "line", "text", "sticker", "redact"].includes(tool)) {
4241
4556
  return "crosshair";
4242
4557
  }
4243
4558
  return "default";
@@ -4281,7 +4596,7 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
4281
4596
  viewOptions: () => viewOptions.value,
4282
4597
  oriented: () => orientedCanvas.value
4283
4598
  });
4284
- const { exportImage, save: onSave } = useExportImage({
4599
+ const { exporting, exportImage, save: onSave } = useExportImage({
4285
4600
  oriented: () => orientedCanvas.value,
4286
4601
  getState: () => context.state.value,
4287
4602
  source: () => props.src instanceof Blob ? props.src : null,
@@ -4663,9 +4978,10 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
4663
4978
  vue.createVNode(EditorTopBar, {
4664
4979
  class: "image-editor__topbar",
4665
4980
  loaded: loaded.value,
4981
+ saving: vue.unref(exporting) || __props.saving === true,
4666
4982
  onSave: vue.unref(onSave),
4667
4983
  onCancel: _cache[2] || (_cache[2] = ($event) => emit("cancel"))
4668
- }, null, 8, ["loaded", "onSave"]),
4984
+ }, null, 8, ["loaded", "saving", "onSave"]),
4669
4985
  vue.createVNode(EditorPanel, {
4670
4986
  class: vue.normalizeClass(vue.unref(context).activeMode.value === "filter" ? "image-editor__strip" : "image-editor__controls"),
4671
4987
  loaded: loaded.value,
@@ -4682,7 +4998,7 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
4682
4998
  };
4683
4999
  }
4684
5000
  });
4685
- const ImageEditor = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-e3346ccd"]]);
5001
+ const ImageEditor = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-5225c517"]]);
4686
5002
  exports.ImageEditor = ImageEditor;
4687
5003
  exports.createInitialState = createInitialState;
4688
5004
  exports.isPristine = isPristine;