@syncfusion/ej2-image-editor 30.2.4 → 31.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/ej2-image-editor.umd.min.js +2 -2
  2. package/dist/ej2-image-editor.umd.min.js.map +1 -1
  3. package/dist/es6/ej2-image-editor.es2015.js +3 -3
  4. package/dist/es6/ej2-image-editor.es2015.js.map +1 -1
  5. package/dist/es6/ej2-image-editor.es5.js +3 -3
  6. package/dist/es6/ej2-image-editor.es5.js.map +1 -1
  7. package/dist/global/ej2-image-editor.min.js +2 -2
  8. package/dist/global/ej2-image-editor.min.js.map +1 -1
  9. package/dist/global/index.d.ts +1 -1
  10. package/dist/ts/image-editor/action/crop.d.ts +44 -0
  11. package/dist/ts/image-editor/action/crop.ts +867 -0
  12. package/dist/ts/image-editor/action/draw.d.ts +187 -0
  13. package/dist/ts/image-editor/action/draw.ts +4924 -0
  14. package/dist/ts/image-editor/action/export.d.ts +29 -0
  15. package/dist/ts/image-editor/action/export.ts +509 -0
  16. package/dist/ts/image-editor/action/filter.d.ts +48 -0
  17. package/dist/ts/image-editor/action/filter.ts +872 -0
  18. package/dist/ts/image-editor/action/freehand-draw.d.ts +68 -0
  19. package/dist/ts/image-editor/action/freehand-draw.ts +1135 -0
  20. package/dist/ts/image-editor/action/index.d.ts +9 -0
  21. package/dist/ts/image-editor/action/index.ts +9 -0
  22. package/dist/ts/image-editor/action/selection.d.ts +178 -0
  23. package/dist/ts/image-editor/action/selection.ts +5241 -0
  24. package/dist/ts/image-editor/action/shape.d.ts +130 -0
  25. package/dist/ts/image-editor/action/shape.ts +3917 -0
  26. package/dist/ts/image-editor/action/transform.d.ts +77 -0
  27. package/dist/ts/image-editor/action/transform.ts +2008 -0
  28. package/dist/ts/image-editor/action/undo-redo.d.ts +52 -0
  29. package/dist/ts/image-editor/action/undo-redo.ts +1169 -0
  30. package/dist/ts/image-editor/base/enum.d.ts +277 -0
  31. package/dist/ts/image-editor/base/enum.ts +288 -0
  32. package/dist/ts/image-editor/base/image-editor-model.d.ts +770 -0
  33. package/dist/ts/image-editor/base/image-editor.d.ts +1928 -0
  34. package/dist/ts/image-editor/base/image-editor.ts +5496 -0
  35. package/dist/ts/image-editor/base/index.d.ts +4 -0
  36. package/dist/ts/image-editor/base/index.ts +4 -0
  37. package/dist/ts/image-editor/base/interface.d.ts +1637 -0
  38. package/dist/ts/image-editor/base/interface.ts +1709 -0
  39. package/dist/ts/image-editor/index.d.ts +3 -0
  40. package/dist/ts/image-editor/index.ts +3 -0
  41. package/dist/ts/image-editor/renderer/index.d.ts +1 -0
  42. package/dist/ts/image-editor/renderer/index.ts +1 -0
  43. package/dist/ts/image-editor/renderer/toolbar.d.ts +171 -0
  44. package/dist/ts/image-editor/renderer/toolbar.ts +6356 -0
  45. package/dist/ts/index.d.ts +4 -0
  46. package/dist/ts/index.ts +4 -0
  47. package/package.json +47 -15
  48. package/src/image-editor/action/undo-redo.js +3 -3
@@ -0,0 +1,872 @@
1
+ import { ImageEditor, ImageFinetuneOption, CurrentObject, SelectionPoint, Point, ActivePoint, Adjustment, FinetuneSettingsModel } from '../index';
2
+ import { isNullOrUndefined, extend } from '@syncfusion/ej2-base';
3
+ import { FrameValue } from '../base';
4
+
5
+ export class Filter {
6
+ private parent: ImageEditor;
7
+ private lowerContext: CanvasRenderingContext2D;
8
+ private adjustmentLevel: Adjustment = {brightness: 0, contrast: 0, hue: 0, opacity: 100, saturation: 0, blur: 0,
9
+ exposure: 0, transparency: 100, sharpen: false, bw: false}; // for toolbar slider value
10
+ private tempAdjustmentLevel: Adjustment = {brightness: 0, contrast: 0, hue: 0, opacity: 100, saturation: 0, blur: 0,
11
+ exposure: 0, transparency: 100, sharpen: false, bw: false}; // for temp toolbar slider value
12
+ private adjustmentValue: string = ''; // for internal slider value
13
+ private isBrightnessAdjusted: boolean = false;
14
+ private bevelFilter: string = 'none';
15
+ private tempAdjVal: Adjustment = {brightness: 0, contrast: 0, hue: 0, opacity: 100, saturation: 0, blur: 0,
16
+ exposure: 0, transparency: 100, sharpen: false, bw: false};
17
+ private tempFilVal: string = '';
18
+
19
+ constructor(parent: ImageEditor) {
20
+ this.parent = parent;
21
+ this.addEventListener();
22
+ }
23
+
24
+ public destroy(): void {
25
+ if (this.parent.isDestroyed) { return; }
26
+ this.removeEventListener();
27
+ }
28
+
29
+ private addEventListener(): void {
30
+ this.parent.on('filter', this.filter, this);
31
+ this.parent.on('destroyed', this.destroy, this);
32
+ }
33
+
34
+ private removeEventListener(): void {
35
+ this.parent.off('filter', this.filter);
36
+ this.parent.off('destroyed', this.destroy);
37
+ }
38
+
39
+ private filter(args?: { onPropertyChange: boolean, prop: string, value?: object }): void {
40
+ this.updatePrivateVariables();
41
+ switch (args.prop) {
42
+ case 'finetuneImage':
43
+ this.finetuneImage(args.value['option'] as ImageFinetuneOption, args.value['value'] as number);
44
+ break;
45
+ case 'applyImageFilter':
46
+ this.setFilter(args.value['option'] as string);
47
+ break;
48
+ case 'update-finetunes':
49
+ this.updateFinetunes();
50
+ break;
51
+ case 'set-adjustment':
52
+ this.setAdjustment(args.value['operation'] as string);
53
+ break;
54
+ case 'initFilter':
55
+ this.initFilter();
56
+ break;
57
+ case 'setCurrAdjValue':
58
+ this.setCurrAdjValue(args.value['type'] as string, args.value['value'] as number);
59
+ break;
60
+ case 'updateAdj':
61
+ this.updateAdj(args.value['type'] as string, args.value['value'] as number, args.value['isPreview'],
62
+ args.value['ctx']);
63
+ break;
64
+ case 'getCurrentObj':
65
+ this.getCurrentObj(args.value['object']);
66
+ break;
67
+ case 'getAdjustmentLevel':
68
+ if (isNullOrUndefined(this.parent.activeObj.opacity)) {
69
+ this.adjustmentLevel.transparency = 100;
70
+ } else {
71
+ this.adjustmentLevel.transparency = this.parent.activeObj.opacity * 100;
72
+ }
73
+ args.value['obj']['adjustmentLevel'] = this.adjustmentLevel;
74
+ break;
75
+ case 'setAdjustmentLevel':
76
+ this.adjustmentLevel = args.value['adjustmentLevel'];
77
+ break;
78
+ case 'getTempAdjustmentLevel':
79
+ args.value['obj']['tempAdjustmentLevel'] = this.tempAdjustmentLevel;
80
+ break;
81
+ case 'setTempAdjustmentLevel':
82
+ this.tempAdjustmentLevel = args.value['tempAdjustmentLevel'];
83
+ break;
84
+ case 'setAdjustmentValue':
85
+ this.adjustmentValue = args.value['adjustmentValue'];
86
+ break;
87
+ case 'setBrightnessAdjusted':
88
+ this.isBrightnessAdjusted = args.value['isBrightnessAdjusted'];
89
+ if (this.parent.currentFilter.split('_') && this.parent.currentFilter.split('_')[1] === 'cold') {
90
+ this.isBrightnessAdjusted = false;
91
+ }
92
+ break;
93
+ case 'getBevelFilter':
94
+ args.value['obj']['bevelFilter'] = this.bevelFilter;
95
+ break;
96
+ case 'setBevelFilter':
97
+ this.bevelFilter = args.value['bevelFilter'];
98
+ break;
99
+ case 'setTempAdjVal':
100
+ this.tempAdjVal = extend({}, this.adjustmentLevel, {}, true) as Adjustment;
101
+ break;
102
+ case 'setTempFilVal':
103
+ this.tempFilVal = this.parent.currentFilter;
104
+ break;
105
+ case 'reset':
106
+ this.reset();
107
+ break;
108
+ case 'apply-filter':
109
+ this.applyFilter(args.value['context'] as CanvasRenderingContext2D);
110
+ break;
111
+ }
112
+ }
113
+
114
+ private updatePrivateVariables(): void {
115
+ const parent: ImageEditor = this.parent;
116
+ if (parent.lowerCanvas) {this.lowerContext = parent.lowerCanvas.getContext('2d'); }
117
+ }
118
+
119
+ public getModuleName(): string {
120
+ return 'filter';
121
+ }
122
+
123
+ private reset(): void {
124
+ this.adjustmentLevel = {brightness: 0, contrast: 0, hue: 0, opacity: 100, saturation: 0,
125
+ blur: 0, exposure: 0, transparency: 100, sharpen: false, bw: false};
126
+ this.tempAdjustmentLevel = {brightness: 0, contrast: 0, hue: 0, opacity: 100, saturation: 0,
127
+ blur: 0, exposure: 0, transparency: 100, sharpen: false, bw: false};
128
+ this.adjustmentValue = this.parent.getDefaultFilter();
129
+ this.isBrightnessAdjusted = false; this.bevelFilter = 'none'; this.tempFilVal = '';
130
+ this.tempAdjVal = {brightness: 0, contrast: 0, hue: 0, opacity: 100, saturation: 0,
131
+ blur: 0, exposure: 0, transparency: 100, sharpen: false, bw: false};
132
+ }
133
+
134
+ private updateFinetunes(): void {
135
+ const parent: ImageEditor = this.parent;
136
+ const fs: FinetuneSettingsModel = parent.finetuneSettings;
137
+ if (fs) {
138
+ const propertiesToSet: string[] = ['brightness', 'contrast', 'hue', 'saturation', 'exposure', 'opacity', 'blur'];
139
+ propertiesToSet.forEach((property: string) => {
140
+ if (fs[property as string]) {
141
+ this.adjustmentLevel[property as string] = fs[property as string].defaultValue;
142
+ this.tempAdjustmentLevel[property as string] = fs[property as string].defaultValue;
143
+ }
144
+ });
145
+ parent.notify('draw', { prop: 'isInitialLoading', onPropertyChange: false, value: { isInitialLoading: true } });
146
+ }
147
+ }
148
+
149
+ private initFilter(): void {
150
+ this.setFilterAdj('brightness', this.adjustmentLevel.brightness);
151
+ this.setFilterAdj('contrast', this.adjustmentLevel.contrast);
152
+ this.setFilterAdj('hue', this.adjustmentLevel.hue);
153
+ this.setFilterAdj('saturation', this.adjustmentLevel.saturation);
154
+ this.setFilterAdj('exposure', this.adjustmentLevel.exposure);
155
+ this.setFilterAdj('opacity', this.adjustmentLevel.opacity);
156
+ this.setFilterAdj('blur', this.adjustmentLevel.blur);
157
+ }
158
+
159
+ private updateAdj(type: string, value: number, isPreview?: boolean, ctx?: CanvasRenderingContext2D): void {
160
+ const parent: ImageEditor = this.parent;
161
+ this.lowerContext.clearRect(0, 0, parent.lowerCanvas.width, parent.lowerCanvas.height);
162
+ let splitWords: string[] = this.lowerContext.filter.split(' ');
163
+ let values: string[] = [];
164
+ const brightness: number = this.getFilterValue(this.adjustmentLevel.brightness);
165
+ let saturate: number; let bright: number; let saturatePercent: number; let contrast: number;
166
+ let saturatePercentage: number;
167
+ switch (type) {
168
+ case 'brightness':
169
+ value = this.getFilterValue(this.adjustmentLevel.exposure) + (value * 0.005);
170
+ splitWords[0] = 'brightness(' + value + ')';
171
+ if (this.adjustmentLevel.brightness !== 0) {
172
+ value = (this.adjustmentLevel.opacity / 100) - (this.adjustmentLevel.opacity * 0.3) / 100;
173
+ splitWords[4] = 'opacity(' + value + ')';
174
+ }
175
+ else {
176
+ value = this.adjustmentLevel.opacity / 100;
177
+ splitWords[4] = 'opacity(' + value + ')';
178
+ }
179
+ this.adjustmentValue = splitWords.join(' ');
180
+ break;
181
+ case 'contrast':
182
+ splitWords[1] = 'contrast(' + value + '%)';
183
+ this.adjustmentValue = splitWords.join(' ');
184
+ break;
185
+ case 'hue':
186
+ splitWords[2] = 'hue-rotate(' + value + 'deg)';
187
+ this.adjustmentValue = splitWords.join(' ');
188
+ break;
189
+ case 'saturation':
190
+ splitWords[3] = 'saturate(' + value + '%)';
191
+ this.adjustmentValue = splitWords.join(' ');
192
+ break;
193
+ case 'opacity':
194
+ if (parseFloat(splitWords[0].split('(')[1]) !== 1) {
195
+ value -= 0.2;
196
+ }
197
+ if (value < 0) {value = 0; }
198
+ splitWords[4] = 'opacity(' + value + ')';
199
+ this.adjustmentValue = splitWords.join(' ');
200
+ break;
201
+ case 'blur':
202
+ splitWords[5] = 'blur(' + value + 'px)';
203
+ this.adjustmentValue = splitWords.join(' ');
204
+ break;
205
+ case 'exposure':
206
+ if (value > 1) {
207
+ value -= 1; value += brightness;
208
+ } else if (value < 1) {
209
+ value = 1 - value; value = brightness - value;
210
+ }
211
+ splitWords[0] = 'brightness(' + value + ')';
212
+ this.adjustmentValue = splitWords.join(' ');
213
+ break;
214
+ case 'chrome':
215
+ saturate = this.getSaturationFilterValue(this.adjustmentLevel.saturation);
216
+ saturate *= 100;
217
+ value = saturate + (saturate * 0.4);
218
+ splitWords[3] = 'saturate(' + value + '%)';
219
+ values = this.adjustmentValue.split(' ');
220
+ splitWords[0] = values[0];
221
+ splitWords[1] = values[1];
222
+ splitWords[2] = values[2];
223
+ splitWords[4] = values[4];
224
+ splitWords[5] = values[5];
225
+ splitWords[6] = 'sepia(0%)';
226
+ splitWords[7] = 'grayscale(0%)';
227
+ splitWords[8] = 'invert(0%)';
228
+ break;
229
+ case 'cold':
230
+ // Adjusting Brightness
231
+ bright = this.getFilterValue(this.adjustmentLevel.brightness);
232
+ bright *= 100;
233
+ value = bright * 0.9;
234
+ value *= 0.01;
235
+ splitWords[0] = 'brightness(' + value + ')';
236
+ // Adjusting Contrast
237
+ contrast = this.getFilterValue(this.adjustmentLevel.contrast);
238
+ contrast *= 100;
239
+ value = contrast + (contrast * 0.5);
240
+ splitWords[1] = 'contrast(' + value + '%)';
241
+ // Adjusting Saturation
242
+ saturatePercentage = this.getSaturationFilterValue(this.adjustmentLevel.saturation);
243
+ saturatePercentage *= 100;
244
+ value = saturatePercentage;
245
+ splitWords[3] = 'saturate(' + value + '%)';
246
+ values = this.adjustmentValue.split(' ');
247
+ splitWords[2] = values[2];
248
+ splitWords[4] = values[4];
249
+ splitWords[5] = values[5];
250
+ splitWords[6] = 'sepia(0%)';
251
+ splitWords[7] = 'grayscale(0%)';
252
+ splitWords[8] = 'invert(0%)';
253
+ break;
254
+ case 'warm':
255
+ saturatePercent = this.getSaturationFilterValue(this.adjustmentLevel.saturation);
256
+ saturatePercent *= 100;
257
+ value = saturatePercent + (saturatePercent * 0.4);
258
+ splitWords[3] = 'saturate(' + value + '%)';
259
+ splitWords[6] = 'sepia(25%)';
260
+ values = this.adjustmentValue.split(' ');
261
+ splitWords[0] = values[0];
262
+ splitWords[1] = values[1];
263
+ splitWords[2] = values[2];
264
+ splitWords[4] = values[4];
265
+ splitWords[5] = values[5];
266
+ splitWords[7] = 'grayscale(0%)';
267
+ splitWords[8] = 'invert(0%)';
268
+ break;
269
+ case 'grayscale':
270
+ splitWords[7] = 'grayscale(100%)';
271
+ values = this.adjustmentValue.split(' ');
272
+ splitWords[0] = values[0];
273
+ splitWords[1] = values[1];
274
+ splitWords[2] = values[2];
275
+ splitWords[3] = values[3];
276
+ splitWords[4] = values[4];
277
+ splitWords[5] = values[5];
278
+ splitWords[6] = 'sepia(0%)';
279
+ splitWords[8] = 'invert(0%)';
280
+ break;
281
+ case 'sepia':
282
+ splitWords[6] = 'sepia(100%)';
283
+ values = this.adjustmentValue.split(' ');
284
+ splitWords[0] = values[0];
285
+ splitWords[1] = values[1];
286
+ splitWords[2] = values[2];
287
+ splitWords[3] = values[3];
288
+ splitWords[4] = values[4];
289
+ splitWords[5] = values[5];
290
+ splitWords[7] = 'grayscale(0%)';
291
+ splitWords[8] = 'invert(0%)';
292
+ break;
293
+ case 'invert':
294
+ splitWords[8] = 'invert(100%)';
295
+ values = this.adjustmentValue.split(' ');
296
+ splitWords[0] = values[0];
297
+ splitWords[1] = values[1];
298
+ splitWords[2] = values[2];
299
+ splitWords[3] = values[3];
300
+ splitWords[4] = values[4];
301
+ splitWords[5] = values[5];
302
+ splitWords[6] = 'sepia(0%)';
303
+ splitWords[7] = 'grayscale(0%)';
304
+ break;
305
+ }
306
+ if (type !== 'sharpen' && type !== 'blackandwhite') {
307
+ if (isNullOrUndefined(isPreview)) {
308
+ if (type === 'default') {
309
+ splitWords = this.getDefaultCurrentFilter(splitWords);
310
+ }
311
+ this.lowerContext.filter = splitWords.join(' ');
312
+ }
313
+ splitWords = this.setTempFilterValue(brightness, isPreview, splitWords, type);
314
+ parent.notify('draw', { prop: 'setRotateZoom', onPropertyChange: false, value: {isRotateZoom: true }});
315
+ parent.notify('draw', { prop: 'updateCurrTransState', onPropertyChange: false,
316
+ value: {type: 'initial', isPreventDestination: null, isRotatePan: null} });
317
+ let tempFilter: string;
318
+ if (parent.frameObj.type === 'bevel') {
319
+ tempFilter = this.lowerContext.filter;
320
+ this.bevelFilter = tempFilter;
321
+ }
322
+ if (parent.transform.degree === 0 && parent.rotateFlipColl.length > 0) {
323
+ parent.img.destLeft += parent.panPoint.totalPannedPoint.x;
324
+ parent.img.destTop += parent.panPoint.totalPannedPoint.y;
325
+ }
326
+ parent.img.destLeft += parent.panPoint.totalPannedInternalPoint.x;
327
+ parent.img.destTop += parent.panPoint.totalPannedInternalPoint.y;
328
+ if (parent.transform.degree === 0) {
329
+ parent.notify('transform', { prop: 'setDestPointsForFlipState', onPropertyChange: false });
330
+ }
331
+ parent.notify('draw', { prop: 'drawImage', onPropertyChange: false});
332
+ parent.notify('draw', { prop: 'updateCurrTransState', onPropertyChange: false,
333
+ value: {type: 'reverse', isPreventDestination: null, isRotatePan: null} });
334
+ parent.notify('draw', { prop: 'setRotateZoom', onPropertyChange: false, value: {isRotateZoom: false }});
335
+ if (parent.transform.degree === 0 && parent.rotateFlipColl.length > 0) {
336
+ parent.img.destLeft += parent.panPoint.totalPannedPoint.x;
337
+ parent.img.destTop += parent.panPoint.totalPannedPoint.y;
338
+ }
339
+ splitWords = this.setTempFilterValue(brightness, isPreview, splitWords, type);
340
+ if (isNullOrUndefined(isPreview)) {this.lowerContext.filter = splitWords.join(' '); }
341
+ parent.initialAdjustmentValue = splitWords.join(' ');
342
+ tempFilter = this.lowerContext.filter;
343
+ this.lowerContext.filter = 'brightness(' + 1 + ') ' + 'contrast(' + 100 + '%) ' + 'hue-rotate(' + 0 + 'deg) ' +
344
+ 'saturate(' + 100 + '%) ' + 'opacity(' + 1 + ') ' + 'blur(' + 0 + 'px) ' + 'sepia(0%) ' + 'grayscale(0%) ' + 'invert(0%)';
345
+ this.bevelFilter = tempFilter;
346
+ parent.notify('shape', { prop: 'drawAnnotations', onPropertyChange: false,
347
+ value: {ctx: this.lowerContext, shape: 'iterate', pen: 'iterate', isPreventApply: null }});
348
+ this.lowerContext.filter = tempFilter;
349
+ parent.notify('draw', { prop: 'clearOuterCanvas', onPropertyChange: false, value: {context: this.lowerContext}});
350
+ if ((parent.currSelectionPoint && parent.currSelectionPoint.shape === 'crop-circle') || parent.isCircleCrop) {
351
+ parent.notify('crop', { prop: 'cropCircle', onPropertyChange: false,
352
+ value: {context: this.lowerContext, isSave: null, isFlip: null}});
353
+ }
354
+ this.isBrightnessAdjusted = brightness !== 1;
355
+ }
356
+ const filter: string = splitWords.join(' ');
357
+ if (ctx) {
358
+ ctx.filter = filter;
359
+ }
360
+ }
361
+
362
+ private setTempFilterValue(brightness: number, isPreview: boolean, splitWords: string[], type: string): string[] {
363
+ if (isPreview) {
364
+ if (type === 'default') {
365
+ splitWords = this.getDefaultCurrentFilter(splitWords);
366
+ } else if (brightness !== 1) {
367
+ const tempSplitWords: string[] = this.lowerContext.filter.split(' ');
368
+ tempSplitWords[4] = splitWords[4];
369
+ this.lowerContext.filter = tempSplitWords.join(' ');
370
+ }
371
+ }
372
+ return splitWords;
373
+ }
374
+
375
+ private getDefaultCurrentFilter(splitWords: string[]): string[] {
376
+ const values: string[] = this.adjustmentValue.split(' ');
377
+ splitWords = [ values[0], values[1], values[2], values[3], values[4], values[5], 'sepia(0%)', 'grayscale(0%)', 'invert(0%)' ];
378
+ return splitWords;
379
+ }
380
+
381
+ private getFilterValue(value: number): number {
382
+ return (value === 0) ? 1 : 1 + ((value * 0.5) / 100);
383
+ }
384
+
385
+ private getSaturationFilterValue(value: number): number {
386
+ return value === 0 ? 1 : 1 + (value / 100);
387
+ }
388
+
389
+ private setFilterAdj(type: string, value: number): void {
390
+ const parent: ImageEditor = this.parent;
391
+ parent.notify('freehand-draw', { prop: 'apply-pen-draw', onPropertyChange: false });
392
+ this.adjustmentLevel[`${type}`] = value;
393
+ switch (type) {
394
+ case 'contrast':
395
+ case 'exposure':
396
+ value = this.getFilterValue(value);
397
+ if (type === 'contrast') {value *= 100; }
398
+ break;
399
+ case 'hue':
400
+ value *= 3;
401
+ break;
402
+ case 'saturation':
403
+ value = this.getSaturationFilterValue(value) * 100;
404
+ break;
405
+ case 'opacity':
406
+ if (value < 10) {
407
+ value += 1;
408
+ }
409
+ value /= 100;
410
+ break;
411
+ case 'blur':
412
+ if (value !== 0) {
413
+ value /= 20;
414
+ // Since 0.5 is not working in blur we consider from 1
415
+ value += 0.5;
416
+ }
417
+ break;
418
+ }
419
+ const prevCropObj: CurrentObject = extend({}, parent.cropObj, {}, true) as CurrentObject;
420
+ const prevObj: CurrentObject = this.getCurrentObj();
421
+ prevObj.objColl = extend([], parent.objColl, [], true) as SelectionPoint[];
422
+ prevObj.pointColl = extend([], parent.pointColl, [], true) as Point[];
423
+ prevObj.afterCropActions = extend([], parent.afterCropActions, [], true) as string[];
424
+ const selPointCollObj: Object = { selPointColl: null };
425
+ parent.notify('freehand-draw', { prop: 'getSelPointColl', onPropertyChange: false, value: { obj: selPointCollObj } });
426
+ prevObj.selPointColl = extend([], selPointCollObj['selPointColl'], [], true) as Point[];
427
+ this.updateAdj(type, value);
428
+ parent.notify('undo-redo', { prop: 'updateUndoRedoColl', onPropertyChange: false, value: { operation: type, previousObj: prevObj,
429
+ previousObjColl: prevObj.objColl, previousPointColl: prevObj.pointColl, previousSelPointColl: prevObj.selPointColl,
430
+ previousCropObj: prevCropObj, previousText: null, currentText: null, previousFilter: null, isCircleCrop: null
431
+ }});
432
+ }
433
+
434
+ private setFilter(type: string): void {
435
+ const parent: ImageEditor = this.parent;
436
+ type = type.toLowerCase();
437
+ parent.notify('freehand-draw', { prop: 'apply-pen-draw', onPropertyChange: false});
438
+ const obj: Object = {currentFilter: this.parent.currentFilter };
439
+ const prevFilter: string = obj['currentFilter'];
440
+ const prevCropObj: CurrentObject = extend({}, parent.cropObj, {}, true) as CurrentObject;
441
+ const prevObj: CurrentObject = this.getCurrentObj();
442
+ prevObj.objColl = extend([], parent.objColl, [], true) as SelectionPoint[];
443
+ prevObj.pointColl = extend([], parent.pointColl, [], true) as Point[];
444
+ prevObj.afterCropActions = extend([], parent.afterCropActions, [], true) as string[];
445
+ const selPointCollObj: Object = {selPointColl: null };
446
+ parent.notify('freehand-draw', { prop: 'getSelPointColl', onPropertyChange: false, value: {obj: selPointCollObj }});
447
+ prevObj.selPointColl = extend([], selPointCollObj['selPointColl'], [], true) as Point[];
448
+ this.updateAdj(type, null);
449
+ parent.notify('draw', { prop: 'setImageEdited', onPropertyChange: false });
450
+ parent.notify('undo-redo', { prop: 'updateUndoRedoColl', onPropertyChange: false,
451
+ value: {operation: type, previousObj: prevObj, previousObjColl: prevObj.objColl, previousPointColl: prevObj.pointColl,
452
+ previousSelPointColl: prevObj.selPointColl, previousCropObj: prevCropObj, previousText: null,
453
+ currentText: null, previousFilter: prevFilter, isCircleCrop: null}});
454
+ }
455
+
456
+ private setAdjustment(type: string): void {
457
+ const splitWords: string[] = this.lowerContext.filter.split(' ');
458
+ let value: number; let valueArr: string[];
459
+ switch (type) {
460
+ case 'brightness':
461
+ valueArr = splitWords[0].split('(');
462
+ value = parseFloat(valueArr[1].split(')')[0]);
463
+ this.adjustmentLevel.brightness = this.setFilterValue(value);
464
+ break;
465
+ case 'contrast':
466
+ valueArr = splitWords[1].split('(');
467
+ value = parseFloat(valueArr[1].split(')')[0]);
468
+ value /= 100;
469
+ this.adjustmentLevel.contrast = this.setFilterValue(value);
470
+ break;
471
+ case 'hue':
472
+ valueArr = splitWords[2].split('(');
473
+ value = parseFloat(valueArr[1].split(')')[0]);
474
+ value /= 3;
475
+ this.adjustmentLevel.hue = value;
476
+ break;
477
+ case 'saturation':
478
+ valueArr = splitWords[3].split('(');
479
+ value = parseFloat(valueArr[1].split(')')[0]);
480
+ value /= 100;
481
+ this.adjustmentLevel.saturation = this.setSaturationFilterValue(value);
482
+ break;
483
+ case 'opacity':
484
+ valueArr = splitWords[4].split('(');
485
+ value = parseFloat(valueArr[1].split(')')[0]);
486
+ if (value === 0.45) {value = 40; }
487
+ else if (value === 0.40) {value = 30; }
488
+ else if (value === 0.35) {value = 20; }
489
+ else if (value === 0.30) {value = 10; }
490
+ else if (value === 0.25) { value = 0; }
491
+ else {value *= 100; }
492
+ this.adjustmentLevel.opacity = value;
493
+ break;
494
+ case 'blur':
495
+ valueArr = splitWords[5].split('(');
496
+ value = parseFloat(valueArr[1].split(')')[0]);
497
+ value *= 20;
498
+ this.adjustmentLevel.blur = value;
499
+ break;
500
+ case 'exposure':
501
+ valueArr = splitWords[0].split('(');
502
+ value = parseFloat(valueArr[1].split(')')[0]);
503
+ this.adjustmentLevel.exposure = this.setFilterValue(value);
504
+ break;
505
+ }
506
+ }
507
+
508
+ private setFilterValue(value: number): number {
509
+ return Math.round((value === 1) ? 0 : ((value - 1) * 100) / 0.5);
510
+ }
511
+
512
+ private setSaturationFilterValue(value: number): number {
513
+ return Math.round((value === 1) ? 0 : (value - 1) * 100);
514
+ }
515
+
516
+ private finetuneImage(finetuneOption: ImageFinetuneOption, value: number): void {
517
+ const parent: ImageEditor = this.parent;
518
+ if (!parent.disabled && parent.isImageLoaded) {
519
+ switch (finetuneOption.toLowerCase()) {
520
+ case 'brightness':
521
+ this.setFilterAdj('brightness', value);
522
+ break;
523
+ case 'contrast':
524
+ this.setFilterAdj('contrast', value);
525
+ break;
526
+ case 'hue':
527
+ this.setFilterAdj('hue', value);
528
+ break;
529
+ case 'saturation':
530
+ this.setFilterAdj('saturation', value);
531
+ break;
532
+ case 'opacity':
533
+ this.setFilterAdj('opacity', value);
534
+ break;
535
+ case 'blur':
536
+ this.setFilterAdj('blur', value);
537
+ break;
538
+ case 'exposure':
539
+ this.setFilterAdj('exposure', value);
540
+ break;
541
+ }
542
+ this.parent.canvasFilter = this.lowerContext.filter;
543
+ parent.notify('undo-redo', {prop: 'updateCurrUrc', value: {type: 'ok' }});
544
+ }
545
+ }
546
+
547
+ private setCurrAdjValue(type: string, value: number): void {
548
+ const parent: ImageEditor = this.parent;
549
+ this.parent.notify('draw', { prop: 'setImageEdited', onPropertyChange: false });
550
+ switch (type) {
551
+ case 'brightness':
552
+ this.setFilterAdj('brightness', value);
553
+ break;
554
+ case 'contrast':
555
+ this.setFilterAdj('contrast', value);
556
+ break;
557
+ case 'hue':
558
+ this.setFilterAdj('hue', value);
559
+ break;
560
+ case 'saturation':
561
+ this.setFilterAdj('saturation', value);
562
+ break;
563
+ case 'opacity':
564
+ this.setFilterAdj('opacity', value);
565
+ break;
566
+ case 'blur':
567
+ this.setFilterAdj('blur', value);
568
+ break;
569
+ case 'exposure':
570
+ this.setFilterAdj('exposure', value);
571
+ break;
572
+ }
573
+ parent.isFinetuneBtnClick = true;
574
+ parent.curFinetuneObjEvent = { finetune: parent.toPascalCase(type) as ImageFinetuneOption, value: value };
575
+ }
576
+
577
+ private getCurrentObj(dummyObj?: Object): CurrentObject {
578
+ const parent: ImageEditor = this.parent;
579
+ const tempFlipPanPointObj: Object = {point: null };
580
+ parent.notify('crop', {prop: 'getTempFlipPanPoint', value: {obj: tempFlipPanPointObj }});
581
+ const zoomObj: Object = {previousZoomValue: null };
582
+ parent.notify('transform', {prop: 'getPreviousZoomValue', value: {obj: zoomObj }});
583
+ const straightenObj: Object = {zoomFactor: null };
584
+ parent.notify('draw', {prop: 'getStraightenInitZoom', value: {obj: straightenObj }});
585
+ const bgObj: Object = { color: null };
586
+ parent.notify('draw', { prop: 'getImageBackgroundColor', value: {obj: bgObj }});
587
+ const obj: CurrentObject = {cropZoom: 0, defaultZoom: 0, totalPannedPoint: {x: 0, y: 0}, totalPannedClientPoint: {x: 0, y: 0},
588
+ totalPannedInternalPoint: {x: 0, y: 0}, tempFlipPanPoint: {x: 0, y: 0}, activeObj: {} as SelectionPoint,
589
+ rotateFlipColl: [], degree: 0, currFlipState: '', zoomFactor: 0, previousZoomValue : 0, straighten: 0,
590
+ destPoints: {startX: 0, startY: 0, width: 0, height: 0} as ActivePoint, frame: 'none',
591
+ srcPoints: {startX: 0, startY: 0, width: 0, height: 0} as ActivePoint, filter : '', isBrightAdjust: this.isBrightnessAdjusted,
592
+ aspectWidth: null, aspectHeight: null, straightenZoom: 0, adjustmentLevel: extend({}, this.tempAdjVal, {}, true) as Adjustment,
593
+ currentFilter: this.tempFilVal, imageSource: '', bgColor: '' };
594
+ obj.cropZoom = parent.transform.cropZoomFactor; obj.defaultZoom = parent.transform.defaultZoomFactor;
595
+ obj.zoomFactor = parent.zoomSettings.zoomFactor; obj.previousZoomValue = zoomObj['previousZoomValue'];
596
+ obj.straightenZoom = straightenObj['zoomFactor'];
597
+ obj.totalPannedPoint = extend({}, parent.panPoint.totalPannedPoint, {}, true) as Point;
598
+ obj.totalPannedClientPoint = extend({}, parent.panPoint.totalPannedClientPoint, {}, true) as Point;
599
+ obj.totalPannedInternalPoint = extend({}, parent.panPoint.totalPannedInternalPoint, {}, true) as Point;
600
+ obj.tempFlipPanPoint = extend({}, tempFlipPanPointObj['point'], {}, true) as Point;
601
+ obj.activeObj = extend({}, parent.activeObj, {}, true) as SelectionPoint;
602
+ obj.rotateFlipColl = extend([], parent.rotateFlipColl, [], true) as string[] | number[];
603
+ obj.degree = parent.transform.degree; obj.straighten = parent.cropObj.straighten;
604
+ obj.currFlipState = parent.transform.currFlipState;
605
+ obj.destPoints = {startX: parent.img.destLeft, startY: parent.img.destTop, endX: 0, endY: 0,
606
+ width: parent.img.destWidth, height: parent.img.destHeight};
607
+ obj.srcPoints = {startX: parent.img.srcLeft, startY: parent.img.srcTop, endX: 0, endY: 0,
608
+ width: parent.img.srcWidth, height: parent.img.srcHeight};
609
+ obj.filter = this.lowerContext.filter; obj.aspectWidth = parent.aspectWidth; obj.aspectHeight = parent.aspectHeight;
610
+ obj.frame = parent.frameObj.type;
611
+ obj.frameObj = extend({}, parent.frameObj) as FrameValue;
612
+ obj.imageSource = parent.baseImg.src;
613
+ obj.bgColor = bgObj['color'];
614
+ if (dummyObj) {dummyObj['currObj'] = obj; }
615
+ return obj;
616
+ }
617
+
618
+ /* Filter safari related codes */
619
+ private getValFromPercentage(percentage: string): number {
620
+ let val: number = parseFloat(percentage);
621
+ // check for percentages and divide by a hundred
622
+ if (/%\s*?$/i.test(percentage)) {
623
+ val /= 100;
624
+ }
625
+ return val;
626
+ }
627
+
628
+ private getValFromLength(length: string): number {
629
+ return parseFloat(length);
630
+ }
631
+
632
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
633
+ private parseFilterString(filterString: string): any[] {
634
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
635
+ let filterArray: any[] = [];
636
+ if (filterString && filterString !== 'none') {
637
+ filterArray = filterString.split(' ').map((filter: string) => {
638
+ const [name, value] = filter.match(/([a-z-]+)\(([^)]+)\)/).slice(1, 3);
639
+ return { filter: name, value: value };
640
+ });
641
+ }
642
+ return filterArray;
643
+ }
644
+
645
+ private applyFilter(context: CanvasRenderingContext2D): void {
646
+ const { height, width } = context.canvas;
647
+ let imageData: ImageData = context.getImageData(0, 0, width, height);
648
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
649
+ const filterArray: any[] = this.parseFilterString(context.filter);
650
+ for (let i: number = 0, len: number = filterArray.length; i < len; i++) {
651
+ switch (filterArray[i as number].filter) {
652
+ case 'blur':
653
+ imageData = this.blur(context, imageData, filterArray[i as number].value);
654
+ break;
655
+ case 'brightness':
656
+ imageData = this.brightness(imageData, filterArray[i as number].value);
657
+ break;
658
+ case 'contrast':
659
+ imageData = this.contrast(imageData, filterArray[i as number].value);
660
+ break;
661
+ case 'grayscale':
662
+ imageData = this.grayscale(imageData, filterArray[i as number].value);
663
+ break;
664
+ case 'hue-rotate':
665
+ imageData = this.hueRotate(imageData, filterArray[i as number].value);
666
+ break;
667
+ case 'invert':
668
+ imageData = this.invert(imageData, filterArray[i as number].value);
669
+ break;
670
+ case 'opacity':
671
+ imageData = this.opacity(imageData, filterArray[i as number].value);
672
+ break;
673
+ case 'saturate':
674
+ imageData = this.saturate(context, imageData, filterArray[i as number].value);
675
+ break;
676
+ case 'sepia':
677
+ imageData = this.sepia(imageData, filterArray[i as number].value);
678
+ break;
679
+ }
680
+ }
681
+ context.putImageData(imageData, 0, 0);
682
+ }
683
+
684
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
685
+ private blur(context: CanvasRenderingContext2D, imageData: ImageData, radius: any = '0'): ImageData {
686
+ let blurRadius: number = this.getValFromLength(radius); blurRadius = Math.floor(blurRadius);
687
+ if (blurRadius <= 0) {
688
+ return imageData;
689
+ }
690
+ const { height, width } = context.canvas;
691
+ const { data } = imageData;
692
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
693
+ const blurredData: any = new Uint8ClampedArray(data.length);
694
+ for (let y: number = 0; y < height; y++) {
695
+ for (let x: number = 0; x < width; x++) {
696
+ let r: number = 0; let g: number = 0; let b: number = 0; let a: number = 0; let count: number = 0;
697
+ for (let dy: number = -blurRadius; dy <= blurRadius; dy++) {
698
+ for (let dx: number = -blurRadius; dx <= blurRadius; dx++) {
699
+ const nx: number = x + dx;
700
+ const ny: number = y + dy;
701
+ if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
702
+ const offset: number = (ny * width + nx) * 4;
703
+ r += data[offset as number];
704
+ g += data[offset + 1];
705
+ b += data[offset + 2];
706
+ a += data[offset + 3];
707
+ count++;
708
+ }
709
+ }
710
+ }
711
+ const i: number = (y * width + x) * 4;
712
+ blurredData[i as number] = r / count;
713
+ blurredData[i + 1] = g / count;
714
+ blurredData[i + 2] = b / count;
715
+ blurredData[i + 3] = a / count;
716
+ }
717
+ }
718
+ for (let i: number = 0; i < data.length; i++) {
719
+ data[i as number] = blurredData[i as number];
720
+ }
721
+ return imageData;
722
+ }
723
+
724
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
725
+ private brightness(imageData: ImageData, percentage: any = '1'): ImageData {
726
+ const factor: number = this.getValFromPercentage(percentage);
727
+ if (factor !== 1) {
728
+ const { data } = imageData;
729
+ const { length } = data;
730
+ for (let i: number = 0; i < length; i += 4) {
731
+ data[i + 0] *= factor; data[i + 1] *= factor; data[i + 2] *= factor;
732
+ }
733
+ }
734
+ return imageData;
735
+ }
736
+
737
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
738
+ private contrast(imageData: ImageData, percentage: any = '1'): ImageData {
739
+ const factor: number = this.getValFromPercentage(percentage);
740
+ if (factor !== 1) {
741
+ const { data } = imageData;
742
+ const { length } = data;
743
+ for (let i: number = 0; i < length; i += 4) {
744
+ data[i + 0] = ((data[i + 0] / 255 - 0.5) * factor + 0.5) * 255;
745
+ data[i + 1] = ((data[i + 1] / 255 - 0.5) * factor + 0.5) * 255;
746
+ data[i + 2] = ((data[i + 2] / 255 - 0.5) * factor + 0.5) * 255;
747
+ }
748
+ }
749
+ return imageData;
750
+ }
751
+
752
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
753
+ private grayscale(imageData: ImageData, percentage: any = '0'): ImageData {
754
+ const factor: number = this.getValFromPercentage(percentage);
755
+ if (factor > 0) {
756
+ const { data } = imageData;
757
+ const { length } = data;
758
+ for (let i: number = 0; i < length; i += 4) {
759
+ const r: number = data[i as number]; const g: number = data[i + 1]; const b: number = data[i + 2];
760
+ // Calculate the grayscale value using the luminosity method
761
+ const gray: number = 0.299 * r + 0.587 * g + 0.114 * b;
762
+ // Blend the original color with the grayscale value based on the percentage
763
+ data[i as number] = r * (1 - factor) + gray * factor;
764
+ data[i + 1] = g * (1 - factor) + gray * factor;
765
+ data[i + 2] = b * (1 - factor) + gray * factor;
766
+ }
767
+ }
768
+ return imageData;
769
+ }
770
+
771
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
772
+ private hueRotate(imageData: ImageData, rotation: any = '0deg'): ImageData {
773
+ const { data } = imageData;
774
+ const angle: number = parseFloat(rotation) * (Math.PI / 180);
775
+ if (angle > 0) {
776
+ const cosA: number = Math.cos(angle);
777
+ const sinA: number = Math.sin(angle);
778
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
779
+ const matrix: any = [
780
+ 0.213 + cosA * 0.787 - sinA * 0.213, 0.715 - cosA * 0.715 - sinA * 0.715, 0.072 - cosA * 0.072 + sinA * 0.928,
781
+ 0.213 - cosA * 0.213 + sinA * 0.143, 0.715 + cosA * 0.285 + sinA * 0.140, 0.072 - cosA * 0.072 - sinA * 0.283,
782
+ 0.213 - cosA * 0.213 - sinA * 0.787, 0.715 - cosA * 0.715 + sinA * 0.715, 0.072 + cosA * 0.928 + sinA * 0.072
783
+ ];
784
+ for (let i: number = 0; i < data.length; i += 4) {
785
+ const r: number = data[i as number];
786
+ const g: number = data[i + 1];
787
+ const b: number = data[i + 2];
788
+
789
+ // Apply the hue rotation matrix
790
+ data[i as number] = matrix[0] * r + matrix[1] * g + matrix[2] * b;
791
+ data[i + 1] = matrix[3] * r + matrix[4] * g + matrix[5] * b;
792
+ data[i + 2] = matrix[6] * r + matrix[7] * g + matrix[8] * b;
793
+ }
794
+ }
795
+ return imageData;
796
+ }
797
+
798
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
799
+ private invert(imageData: ImageData, percentage: any = '0'): ImageData {
800
+ const factor: number = this.getValFromPercentage(percentage);
801
+ if (factor > 0) {
802
+ const { data } = imageData;
803
+ const { length } = data;
804
+ for (let i: number = 0; i < length; i += 4) {
805
+ data[i + 0] = Math.abs(data[i + 0] - 255 * factor);
806
+ data[i + 1] = Math.abs(data[i + 1] - 255 * factor);
807
+ data[i + 2] = Math.abs(data[i + 2] - 255 * factor);
808
+ }
809
+ }
810
+ return imageData;
811
+ }
812
+
813
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
814
+ private opacity(imageData: ImageData, percentage: any = '0'): ImageData {
815
+ const factor: number = this.getValFromPercentage(percentage);
816
+ if (factor >= 0) {
817
+ const { data } = imageData;
818
+ const { length } = data;
819
+ for (let i: number = 3; i < length; i += 4) {
820
+ data[i as number] *= factor;
821
+ }
822
+ }
823
+ return imageData;
824
+ }
825
+
826
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
827
+ private saturate(context: CanvasRenderingContext2D, imageData: ImageData, percentage: any = '0'): ImageData {
828
+ const factor: number = this.getValFromPercentage(percentage);
829
+ if (factor !== 1) {
830
+ const {width, height} = context.canvas;
831
+ const { data } = imageData;
832
+ const lumR: number = (1 - factor) * 0.3086; const lumG: number = (1 - factor) * 0.6094;
833
+ const lumB: number = (1 - factor) * 0.082;
834
+ // tslint:disable-next-line no-bitwise
835
+ const shiftW: number = width << 2;
836
+ for (let j: number = 0; j < height; j++) {
837
+ const offset: number = j * shiftW;
838
+ for (let i: number = 0; i < width; i++) {
839
+ // tslint:disable-next-line no-bitwise
840
+ const pos: number = offset + (i << 2);
841
+ const r: number = data[pos + 0]; const g: number = data[pos + 1]; const b: number = data[pos + 2];
842
+ data[pos + 0] = (lumR + factor) * r + lumG * g + lumB * b;
843
+ data[pos + 1] = lumR * r + (lumG + factor) * g + lumB * b;
844
+ data[pos + 2] = lumR * r + lumG * g + (lumB + factor) * b;
845
+ }
846
+ }
847
+ }
848
+ return imageData;
849
+ }
850
+
851
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
852
+ private sepia(imageData: ImageData, percentage: any = '0'): ImageData {
853
+ let factor: number = this.getValFromPercentage(percentage);
854
+ if (factor > 1) {
855
+ factor = 1;
856
+ }
857
+ if (factor > 0) {
858
+ const { data } = imageData;
859
+ const { length } = data;
860
+ for (let i: number = 0; i < length; i += 4) {
861
+ const r: number = data[i + 0]; const g: number = data[i + 1]; const b: number = data[i + 2];
862
+ data[i + 0] =
863
+ (0.393 * r + 0.769 * g + 0.189 * b) * factor + r * (1 - factor);
864
+ data[i + 1] =
865
+ (0.349 * r + 0.686 * g + 0.168 * b) * factor + g * (1 - factor);
866
+ data[i + 2] =
867
+ (0.272 * r + 0.534 * g + 0.131 * b) * factor + b * (1 - factor);
868
+ }
869
+ }
870
+ return imageData;
871
+ }
872
+ }