apexify.js 4.9.25 → 4.9.27
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/README.md +358 -47
- package/dist/cjs/Canvas/ApexPainter.d.ts +189 -0
- package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -0
- package/dist/{esm/canvas → cjs/Canvas}/ApexPainter.js +461 -352
- package/dist/cjs/Canvas/ApexPainter.js.map +1 -0
- package/dist/cjs/Canvas/utils/Background/bg.d.ts +43 -0
- package/dist/cjs/Canvas/utils/Background/bg.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Background/bg.js +228 -0
- package/dist/cjs/Canvas/utils/Background/bg.js.map +1 -0
- package/dist/cjs/{canvas → Canvas}/utils/Charts/charts.d.ts.map +1 -1
- package/dist/{esm/canvas → cjs/Canvas}/utils/Charts/charts.js.map +1 -1
- package/dist/cjs/{canvas → Canvas}/utils/Custom/customLines.d.ts.map +1 -1
- package/dist/{esm/canvas → cjs/Canvas}/utils/Custom/customLines.js +2 -2
- package/dist/cjs/Canvas/utils/Custom/customLines.js.map +1 -0
- package/dist/cjs/{canvas → Canvas}/utils/General/conversion.d.ts.map +1 -1
- package/dist/cjs/{canvas → Canvas}/utils/General/conversion.js.map +1 -1
- package/dist/cjs/{canvas → Canvas}/utils/General/general functions.d.ts.map +1 -1
- package/dist/{esm/canvas → cjs/Canvas}/utils/General/general functions.js.map +1 -1
- package/dist/cjs/Canvas/utils/Image/imageFilters.d.ts +11 -0
- package/dist/cjs/Canvas/utils/Image/imageFilters.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageFilters.js +307 -0
- package/dist/cjs/Canvas/utils/Image/imageFilters.js.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts +50 -0
- package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageProperties.js +271 -0
- package/dist/cjs/Canvas/utils/Image/imageProperties.js.map +1 -0
- package/dist/cjs/Canvas/utils/Image/professionalImageFilters.d.ts +11 -0
- package/dist/cjs/Canvas/utils/Image/professionalImageFilters.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Image/professionalImageFilters.js +351 -0
- package/dist/cjs/Canvas/utils/Image/professionalImageFilters.js.map +1 -0
- package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.d.ts +11 -0
- package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.js +215 -0
- package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.js.map +1 -0
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts +71 -0
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js +392 -0
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -0
- package/dist/cjs/Canvas/utils/Shapes/shapes.d.ts +29 -0
- package/dist/cjs/Canvas/utils/Shapes/shapes.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Shapes/shapes.js +334 -0
- package/dist/cjs/Canvas/utils/Shapes/shapes.js.map +1 -0
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts +127 -0
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js +365 -0
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -0
- package/dist/cjs/{canvas → Canvas}/utils/Texts/textProperties.d.ts.map +1 -1
- package/dist/{esm/canvas → cjs/Canvas}/utils/Texts/textProperties.js.map +1 -1
- package/dist/{esm/canvas → cjs/Canvas}/utils/types.d.ts +227 -131
- package/dist/cjs/Canvas/utils/types.d.ts.map +1 -0
- package/dist/cjs/{canvas → Canvas}/utils/types.js +0 -1
- package/dist/cjs/Canvas/utils/types.js.map +1 -0
- package/dist/cjs/Canvas/utils/utils.d.ts +22 -0
- package/dist/cjs/Canvas/utils/utils.d.ts.map +1 -0
- package/dist/{esm/canvas → cjs/Canvas}/utils/utils.js +17 -7
- package/dist/cjs/Canvas/utils/utils.js.map +1 -0
- package/dist/cjs/index.d.ts +6 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +8 -6
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +1 -1
- package/dist/cjs/utils.js +1 -1
- package/dist/esm/Canvas/ApexPainter.d.ts +189 -0
- package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -0
- package/dist/{cjs/canvas → esm/Canvas}/ApexPainter.js +461 -352
- package/dist/esm/Canvas/ApexPainter.js.map +1 -0
- package/dist/esm/Canvas/utils/Background/bg.d.ts +43 -0
- package/dist/esm/Canvas/utils/Background/bg.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Background/bg.js +228 -0
- package/dist/esm/Canvas/utils/Background/bg.js.map +1 -0
- package/dist/esm/{canvas → Canvas}/utils/Charts/charts.d.ts.map +1 -1
- package/dist/{cjs/canvas → esm/Canvas}/utils/Charts/charts.js.map +1 -1
- package/dist/esm/{canvas → Canvas}/utils/Custom/customLines.d.ts.map +1 -1
- package/dist/{cjs/canvas → esm/Canvas}/utils/Custom/customLines.js +2 -2
- package/dist/esm/Canvas/utils/Custom/customLines.js.map +1 -0
- package/dist/esm/{canvas → Canvas}/utils/General/conversion.d.ts.map +1 -1
- package/dist/esm/{canvas → Canvas}/utils/General/conversion.js.map +1 -1
- package/dist/esm/{canvas → Canvas}/utils/General/general functions.d.ts.map +1 -1
- package/dist/{cjs/canvas → esm/Canvas}/utils/General/general functions.js.map +1 -1
- package/dist/esm/Canvas/utils/Image/imageFilters.d.ts +11 -0
- package/dist/esm/Canvas/utils/Image/imageFilters.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageFilters.js +307 -0
- package/dist/esm/Canvas/utils/Image/imageFilters.js.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageProperties.d.ts +50 -0
- package/dist/esm/Canvas/utils/Image/imageProperties.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageProperties.js +271 -0
- package/dist/esm/Canvas/utils/Image/imageProperties.js.map +1 -0
- package/dist/esm/Canvas/utils/Image/professionalImageFilters.d.ts +11 -0
- package/dist/esm/Canvas/utils/Image/professionalImageFilters.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Image/professionalImageFilters.js +351 -0
- package/dist/esm/Canvas/utils/Image/professionalImageFilters.js.map +1 -0
- package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.d.ts +11 -0
- package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.js +215 -0
- package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.js.map +1 -0
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts +71 -0
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js +392 -0
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -0
- package/dist/esm/Canvas/utils/Shapes/shapes.d.ts +29 -0
- package/dist/esm/Canvas/utils/Shapes/shapes.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Shapes/shapes.js +334 -0
- package/dist/esm/Canvas/utils/Shapes/shapes.js.map +1 -0
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts +127 -0
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js +365 -0
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -0
- package/dist/esm/{canvas → Canvas}/utils/Texts/textProperties.d.ts.map +1 -1
- package/dist/{cjs/canvas → esm/Canvas}/utils/Texts/textProperties.js.map +1 -1
- package/dist/{cjs/canvas → esm/Canvas}/utils/types.d.ts +227 -131
- package/dist/esm/Canvas/utils/types.d.ts.map +1 -0
- package/dist/esm/{canvas → Canvas}/utils/types.js +0 -1
- package/dist/esm/Canvas/utils/types.js.map +1 -0
- package/dist/esm/Canvas/utils/utils.d.ts +22 -0
- package/dist/esm/Canvas/utils/utils.d.ts.map +1 -0
- package/dist/{cjs/canvas → esm/Canvas}/utils/utils.js +17 -7
- package/dist/esm/Canvas/utils/utils.js.map +1 -0
- package/dist/esm/index.d.ts +6 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +8 -6
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +1 -1
- package/dist/esm/utils.js +1 -1
- package/lib/{canvas → Canvas}/ApexPainter.ts +1325 -1218
- package/lib/Canvas/utils/Background/bg.ts +285 -0
- package/lib/{canvas → Canvas}/utils/Custom/customLines.ts +3 -3
- package/lib/Canvas/utils/Image/imageFilters.ts +356 -0
- package/lib/Canvas/utils/Image/imageProperties.ts +382 -0
- package/lib/Canvas/utils/Image/professionalImageFilters.ts +391 -0
- package/lib/Canvas/utils/Image/simpleProfessionalFilters.ts +229 -0
- package/lib/Canvas/utils/Patterns/enhancedPatternRenderer.ts +444 -0
- package/lib/Canvas/utils/Shapes/shapes.ts +528 -0
- package/lib/Canvas/utils/Texts/enhancedTextRenderer.ts +478 -0
- package/lib/{canvas → Canvas}/utils/types.ts +301 -117
- package/lib/{canvas → Canvas}/utils/utils.ts +85 -72
- package/lib/index.ts +8 -9
- package/lib/utils.ts +1 -1
- package/package.json +107 -191
- package/dist/cjs/canvas/ApexPainter.d.ts +0 -145
- package/dist/cjs/canvas/ApexPainter.d.ts.map +0 -1
- package/dist/cjs/canvas/ApexPainter.js.map +0 -1
- package/dist/cjs/canvas/utils/Background/bg.d.ts +0 -31
- package/dist/cjs/canvas/utils/Background/bg.d.ts.map +0 -1
- package/dist/cjs/canvas/utils/Background/bg.js +0 -161
- package/dist/cjs/canvas/utils/Background/bg.js.map +0 -1
- package/dist/cjs/canvas/utils/Custom/customLines.js.map +0 -1
- package/dist/cjs/canvas/utils/Image/imageProperties.d.ts +0 -115
- package/dist/cjs/canvas/utils/Image/imageProperties.d.ts.map +0 -1
- package/dist/cjs/canvas/utils/Image/imageProperties.js +0 -602
- package/dist/cjs/canvas/utils/Image/imageProperties.js.map +0 -1
- package/dist/cjs/canvas/utils/types.d.ts.map +0 -1
- package/dist/cjs/canvas/utils/types.js.map +0 -1
- package/dist/cjs/canvas/utils/utils.d.ts +0 -19
- package/dist/cjs/canvas/utils/utils.d.ts.map +0 -1
- package/dist/cjs/canvas/utils/utils.js.map +0 -1
- package/dist/esm/canvas/ApexPainter.d.ts +0 -145
- package/dist/esm/canvas/ApexPainter.d.ts.map +0 -1
- package/dist/esm/canvas/ApexPainter.js.map +0 -1
- package/dist/esm/canvas/utils/Background/bg.d.ts +0 -31
- package/dist/esm/canvas/utils/Background/bg.d.ts.map +0 -1
- package/dist/esm/canvas/utils/Background/bg.js +0 -161
- package/dist/esm/canvas/utils/Background/bg.js.map +0 -1
- package/dist/esm/canvas/utils/Custom/customLines.js.map +0 -1
- package/dist/esm/canvas/utils/Image/imageProperties.d.ts +0 -115
- package/dist/esm/canvas/utils/Image/imageProperties.d.ts.map +0 -1
- package/dist/esm/canvas/utils/Image/imageProperties.js +0 -602
- package/dist/esm/canvas/utils/Image/imageProperties.js.map +0 -1
- package/dist/esm/canvas/utils/types.d.ts.map +0 -1
- package/dist/esm/canvas/utils/types.js.map +0 -1
- package/dist/esm/canvas/utils/utils.d.ts +0 -19
- package/dist/esm/canvas/utils/utils.d.ts.map +0 -1
- package/dist/esm/canvas/utils/utils.js.map +0 -1
- package/lib/canvas/utils/Background/bg.ts +0 -211
- package/lib/canvas/utils/Image/imageProperties.ts +0 -835
- /package/dist/cjs/{canvas → Canvas}/utils/Charts/charts.d.ts +0 -0
- /package/dist/cjs/{canvas → Canvas}/utils/Charts/charts.js +0 -0
- /package/dist/cjs/{canvas → Canvas}/utils/Custom/customLines.d.ts +0 -0
- /package/dist/cjs/{canvas → Canvas}/utils/General/conversion.d.ts +0 -0
- /package/dist/cjs/{canvas → Canvas}/utils/General/conversion.js +0 -0
- /package/dist/cjs/{canvas → Canvas}/utils/General/general functions.d.ts +0 -0
- /package/dist/cjs/{canvas → Canvas}/utils/General/general functions.js +0 -0
- /package/dist/cjs/{canvas → Canvas}/utils/Texts/textProperties.d.ts +0 -0
- /package/dist/cjs/{canvas → Canvas}/utils/Texts/textProperties.js +0 -0
- /package/dist/esm/{canvas → Canvas}/utils/Charts/charts.d.ts +0 -0
- /package/dist/esm/{canvas → Canvas}/utils/Charts/charts.js +0 -0
- /package/dist/esm/{canvas → Canvas}/utils/Custom/customLines.d.ts +0 -0
- /package/dist/esm/{canvas → Canvas}/utils/General/conversion.d.ts +0 -0
- /package/dist/esm/{canvas → Canvas}/utils/General/conversion.js +0 -0
- /package/dist/esm/{canvas → Canvas}/utils/General/general functions.d.ts +0 -0
- /package/dist/esm/{canvas → Canvas}/utils/General/general functions.js +0 -0
- /package/dist/esm/{canvas → Canvas}/utils/Texts/textProperties.d.ts +0 -0
- /package/dist/esm/{canvas → Canvas}/utils/Texts/textProperties.js +0 -0
- /package/lib/{canvas → Canvas}/utils/Charts/charts.ts +0 -0
- /package/lib/{canvas → Canvas}/utils/General/conversion.ts +0 -0
- /package/lib/{canvas → Canvas}/utils/General/general functions.ts +0 -0
- /package/lib/{canvas → Canvas}/utils/Texts/textProperties.ts +0 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { createCanvas, loadImage, SKRSContext2D } from "@napi-rs/canvas";
|
|
2
|
+
import { CanvasConfig, gradient } from "../types";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
export type AlignMode =
|
|
6
|
+
| 'center' | 'top' | 'bottom' | 'left' | 'right'
|
|
7
|
+
| 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
8
|
+
|
|
9
|
+
export type FitMode = 'fill' | 'contain' | 'cover';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Draws a gradient background on the canvas with optional zoom support.
|
|
13
|
+
* @param ctx The canvas rendering context.
|
|
14
|
+
* @param canvas The canvas configuration object.
|
|
15
|
+
* @param zoom Optional zoom configuration.
|
|
16
|
+
*/
|
|
17
|
+
export async function drawBackgroundGradient(
|
|
18
|
+
ctx: SKRSContext2D,
|
|
19
|
+
canvas: CanvasConfig
|
|
20
|
+
) {
|
|
21
|
+
if (!canvas.gradientBg) return;
|
|
22
|
+
const width = canvas.width ?? 500;
|
|
23
|
+
const height = canvas.height ?? 500;
|
|
24
|
+
|
|
25
|
+
const grad = buildCanvasGradient(ctx, {
|
|
26
|
+
gradient: canvas.gradientBg,
|
|
27
|
+
width, height
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (canvas.blur) ctx.filter = `blur(${canvas.blur}px)`;
|
|
31
|
+
ctx.fillStyle = grad;
|
|
32
|
+
ctx.fillRect(0, 0, width, height);
|
|
33
|
+
ctx.filter = 'none';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Draws a solid background color on the canvas with optional zoom effect.
|
|
38
|
+
* @param ctx The canvas rendering context.
|
|
39
|
+
* @param canvas The canvas configuration object.
|
|
40
|
+
* @param zoom Optional zoom configuration.
|
|
41
|
+
*/
|
|
42
|
+
export async function drawBackgroundColor(
|
|
43
|
+
ctx: SKRSContext2D,
|
|
44
|
+
canvas: CanvasConfig
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
const W = canvas.width ?? 500;
|
|
47
|
+
const H = canvas.height ?? 500;
|
|
48
|
+
|
|
49
|
+
if (canvas.blur) ctx.filter = `blur(${canvas.blur}px)`;
|
|
50
|
+
if (canvas.colorBg !== 'transparent') {
|
|
51
|
+
ctx.fillStyle = (canvas.colorBg ?? '#000') as string;
|
|
52
|
+
ctx.fillRect(0, 0, W, H);
|
|
53
|
+
}
|
|
54
|
+
ctx.filter = 'none';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Draws a custom background image on the canvas with optional zoom functionality.
|
|
59
|
+
* @param ctx The canvas rendering context.
|
|
60
|
+
* @param canvas The canvas configuration object.
|
|
61
|
+
* @param zoom Optional zoom configuration.
|
|
62
|
+
*/
|
|
63
|
+
export async function customBackground(
|
|
64
|
+
ctx: SKRSContext2D,
|
|
65
|
+
canvas: CanvasConfig
|
|
66
|
+
): Promise<void> {
|
|
67
|
+
const cfg = canvas.customBg;
|
|
68
|
+
if (!cfg) return;
|
|
69
|
+
|
|
70
|
+
let imagePath = cfg.source;
|
|
71
|
+
if (!/^https?:\/\//i.test(imagePath)) {
|
|
72
|
+
imagePath = path.join(process.cwd(), imagePath);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const img = await loadImage(imagePath);
|
|
77
|
+
// Canvas size (createCanvas may have overridden via inherit)
|
|
78
|
+
const W = canvas.width ?? img.width;
|
|
79
|
+
const H = canvas.height ?? img.height;
|
|
80
|
+
|
|
81
|
+
if (canvas.blur) ctx.filter = `blur(${canvas.blur}px)`;
|
|
82
|
+
|
|
83
|
+
if (cfg.inherit) {
|
|
84
|
+
// Canvas was resized to image size in createCanvas; just draw 1:1
|
|
85
|
+
ctx.drawImage(img, 0, 0);
|
|
86
|
+
} else {
|
|
87
|
+
// scale by fit + align
|
|
88
|
+
const fit: FitMode = cfg.fit ?? 'fill';
|
|
89
|
+
let dx = 0, dy = 0, dw = W, dh = H;
|
|
90
|
+
|
|
91
|
+
if (fit === 'contain' || fit === 'cover') {
|
|
92
|
+
const s = fit === 'contain'
|
|
93
|
+
? Math.min(W / img.width, H / img.height)
|
|
94
|
+
: Math.max(W / img.width, H / img.height);
|
|
95
|
+
dw = img.width * s;
|
|
96
|
+
dh = img.height * s;
|
|
97
|
+
// alignment
|
|
98
|
+
const align: AlignMode = cfg.align ?? 'center';
|
|
99
|
+
({ dx, dy } = alignInto(W, H, dw, dh, align));
|
|
100
|
+
} else {
|
|
101
|
+
// 'fill' stretches image to exactly W x H (may distort)
|
|
102
|
+
dx = 0; dy = 0; dw = W; dh = H;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
ctx.drawImage(img, dx, dy, dw, dh);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
ctx.filter = 'none';
|
|
109
|
+
} catch (e: any) {
|
|
110
|
+
console.error('customBackground: failed to load', e?.message ?? e);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// helper to place the fitted rect inside canvas by alignment keyword
|
|
115
|
+
function alignInto(
|
|
116
|
+
W: number, H: number, w: number, h: number, align: AlignMode
|
|
117
|
+
): { dx: number; dy: number } {
|
|
118
|
+
const cx = (W - w) / 2;
|
|
119
|
+
const cy = (H - h) / 2;
|
|
120
|
+
switch (align) {
|
|
121
|
+
case 'top-left': return { dx: 0, dy: 0 };
|
|
122
|
+
case 'top': return { dx: cx, dy: 0 };
|
|
123
|
+
case 'top-right': return { dx: W-w, dy: 0 };
|
|
124
|
+
case 'left': return { dx: 0, dy: cy };
|
|
125
|
+
case 'center': return { dx: cx, dy: cy };
|
|
126
|
+
case 'right': return { dx: W-w, dy: cy };
|
|
127
|
+
case 'bottom-left': return { dx: 0, dy: H-h };
|
|
128
|
+
case 'bottom': return { dx: cx, dy: H-h };
|
|
129
|
+
case 'bottom-right': return { dx: W-w, dy: H-h };
|
|
130
|
+
default: return { dx: cx, dy: cy };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
export function buildPathbg(
|
|
137
|
+
ctx: SKRSContext2D,
|
|
138
|
+
x: number,
|
|
139
|
+
y: number,
|
|
140
|
+
width: number,
|
|
141
|
+
height: number,
|
|
142
|
+
borderRadius: number | "circular" = 0,
|
|
143
|
+
borderPosition: string = "all"
|
|
144
|
+
): void {
|
|
145
|
+
ctx.beginPath();
|
|
146
|
+
|
|
147
|
+
if (borderRadius === "circular") {
|
|
148
|
+
const r = Math.min(width, height) / 2;
|
|
149
|
+
ctx.arc(x + width / 2, y + height / 2, r, 0, 2 * Math.PI);
|
|
150
|
+
} else if (typeof borderRadius === "number" && borderRadius > 0) {
|
|
151
|
+
const br = Math.min(borderRadius, width / 2, height / 2);
|
|
152
|
+
const selected = new Set(borderPosition.toLowerCase().split(",").map(s => s.trim()));
|
|
153
|
+
|
|
154
|
+
const roundTL = selected.has("all") || selected.has("top-left") || (selected.has("top") && selected.has("left"));
|
|
155
|
+
const roundTR = selected.has("all") || selected.has("top-right") || (selected.has("top") && selected.has("right"));
|
|
156
|
+
const roundBR = selected.has("all") || selected.has("bottom-right") || (selected.has("bottom") && selected.has("right"));
|
|
157
|
+
const roundBL = selected.has("all") || selected.has("bottom-left") || (selected.has("bottom") && selected.has("left"));
|
|
158
|
+
|
|
159
|
+
const tl = roundTL ? br : 0;
|
|
160
|
+
const tr = roundTR ? br : 0;
|
|
161
|
+
const brR = roundBR ? br : 0;
|
|
162
|
+
const bl = roundBL ? br : 0;
|
|
163
|
+
|
|
164
|
+
ctx.moveTo(x + tl, y);
|
|
165
|
+
ctx.lineTo(x + width - tr, y);
|
|
166
|
+
if (tr) ctx.arcTo(x + width, y, x + width, y + tr, tr);
|
|
167
|
+
ctx.lineTo(x + width, y + height - brR);
|
|
168
|
+
if (brR) ctx.arcTo(x + width, y + height, x + width - brR, y + height, brR);
|
|
169
|
+
ctx.lineTo(x + bl, y + height);
|
|
170
|
+
if (bl) ctx.arcTo(x, y + height, x, y + height - bl, bl);
|
|
171
|
+
ctx.lineTo(x, y + tl);
|
|
172
|
+
if (tl) ctx.arcTo(x, y, x + tl, y, tl);
|
|
173
|
+
} else {
|
|
174
|
+
ctx.rect(x, y, width, height);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
ctx.closePath();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
export function applyNoise(ctx: SKRSContext2D, width: number, height: number, intensity = 0.05) {
|
|
183
|
+
const noiseCanvas = createCanvas(width, height);
|
|
184
|
+
const nctx = noiseCanvas.getContext("2d");
|
|
185
|
+
const imageData = nctx.createImageData(width, height);
|
|
186
|
+
for (let i = 0; i < imageData.data.length; i += 4) {
|
|
187
|
+
const v = (Math.random() * 255) | 0;
|
|
188
|
+
imageData.data[i] = v;
|
|
189
|
+
imageData.data[i+1] = v;
|
|
190
|
+
imageData.data[i+2] = v;
|
|
191
|
+
imageData.data[i+3] = 255 * intensity;
|
|
192
|
+
}
|
|
193
|
+
nctx.putImageData(imageData, 0, 0);
|
|
194
|
+
ctx.drawImage(noiseCanvas, 0, 0);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export async function drawPattern(
|
|
198
|
+
ctx: SKRSContext2D,
|
|
199
|
+
{ source, repeat = "repeat", opacity = 1 }: { source: string; repeat?: 'repeat'|'repeat-x'|'repeat-y'|'no-repeat'; opacity?: number },
|
|
200
|
+
width: number,
|
|
201
|
+
height: number
|
|
202
|
+
) {
|
|
203
|
+
const img = await loadImage(source);
|
|
204
|
+
const pattern = ctx.createPattern(img, repeat);
|
|
205
|
+
if (!pattern) return;
|
|
206
|
+
ctx.save();
|
|
207
|
+
ctx.globalAlpha = opacity;
|
|
208
|
+
ctx.fillStyle = pattern as any;
|
|
209
|
+
ctx.fillRect(0, 0, width, height);
|
|
210
|
+
ctx.restore();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function rotatePoint(
|
|
214
|
+
x: number, y: number,
|
|
215
|
+
pivotX: number, pivotY: number,
|
|
216
|
+
deg: number
|
|
217
|
+
): [number, number] {
|
|
218
|
+
if (!deg) return [x, y];
|
|
219
|
+
const a = (deg * Math.PI) / 180;
|
|
220
|
+
const dx = x - pivotX, dy = y - pivotY;
|
|
221
|
+
const rx = dx * Math.cos(a) - dy * Math.sin(a);
|
|
222
|
+
const ry = dx * Math.sin(a) + dy * Math.cos(a);
|
|
223
|
+
return [pivotX + rx, pivotY + ry];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function applyCanvasZoom(
|
|
227
|
+
ctx: SKRSContext2D,
|
|
228
|
+
width: number,
|
|
229
|
+
height: number,
|
|
230
|
+
zoom?: { scale?: number; centerX?: number; centerY?: number }
|
|
231
|
+
) {
|
|
232
|
+
if (!zoom) return;
|
|
233
|
+
|
|
234
|
+
const scale = zoom.scale ?? 1;
|
|
235
|
+
if (scale === 1) return; // nothing to do
|
|
236
|
+
|
|
237
|
+
const cx = zoom.centerX ?? width / 2;
|
|
238
|
+
const cy = zoom.centerY ?? height / 2;
|
|
239
|
+
|
|
240
|
+
ctx.translate(cx, cy);
|
|
241
|
+
ctx.scale(scale, scale);
|
|
242
|
+
ctx.translate(-cx, -cy);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
export function buildCanvasGradient(
|
|
247
|
+
ctx: SKRSContext2D,
|
|
248
|
+
cfg: { gradient: gradient; width: number; height: number }
|
|
249
|
+
): CanvasGradient {
|
|
250
|
+
const { gradient, width, height } = cfg;
|
|
251
|
+
|
|
252
|
+
if (gradient.type === 'linear') {
|
|
253
|
+
const {
|
|
254
|
+
startX = 0, startY = 0,
|
|
255
|
+
endX = width, endY = 0,
|
|
256
|
+
rotate = 0,
|
|
257
|
+
pivotX = width/2, pivotY = height/2,
|
|
258
|
+
colors
|
|
259
|
+
} = gradient;
|
|
260
|
+
|
|
261
|
+
const [sx, sy] = rotatePoint(startX, startY, pivotX, pivotY, rotate);
|
|
262
|
+
const [ex, ey] = rotatePoint(endX, endY, pivotX, pivotY, rotate);
|
|
263
|
+
|
|
264
|
+
const g = ctx.createLinearGradient(sx, sy, ex, ey);
|
|
265
|
+
for (const { stop, color } of colors) g.addColorStop(stop, color);
|
|
266
|
+
return g;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// radial
|
|
270
|
+
const {
|
|
271
|
+
startX = width/2, startY = height/2, startRadius = 0,
|
|
272
|
+
endX = width/2, endY = height/2, endRadius = Math.max(width, height)/2,
|
|
273
|
+
rotate = 0,
|
|
274
|
+
pivotX = width/2, pivotY = height/2,
|
|
275
|
+
colors
|
|
276
|
+
} = gradient;
|
|
277
|
+
|
|
278
|
+
// If centers differ, rotation will rotate both centers around pivot.
|
|
279
|
+
const [sx, sy] = rotatePoint(startX, startY, pivotX, pivotY, rotate);
|
|
280
|
+
const [ex, ey] = rotatePoint(endX, endY, pivotX, pivotY, rotate);
|
|
281
|
+
|
|
282
|
+
const g = ctx.createRadialGradient(sx, sy, startRadius, ex, ey, endRadius);
|
|
283
|
+
for (const { stop, color } of colors) g.addColorStop(stop, color);
|
|
284
|
+
return g;
|
|
285
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createGradientFill } from "../Image/imageProperties";
|
|
2
2
|
import { CustomOptions } from "../types";
|
|
3
3
|
|
|
4
4
|
export function customLines(ctx: any, options: CustomOptions[]) {
|
|
@@ -40,7 +40,7 @@ export function customLines(ctx: any, options: CustomOptions[]) {
|
|
|
40
40
|
ctx.lineWidth = appliedStyle?.width || 1;
|
|
41
41
|
|
|
42
42
|
if (appliedStyle?.gradient) {
|
|
43
|
-
ctx.strokeStyle =
|
|
43
|
+
ctx.strokeStyle = createGradientFill(ctx, appliedStyle.gradient, { x: start.x, y: start.y, w: endCoordinates.x - start.x, h: endCoordinates.y - start.y });
|
|
44
44
|
} else {
|
|
45
45
|
ctx.strokeStyle = appliedStyle?.color || 'black';
|
|
46
46
|
}
|
|
@@ -104,7 +104,7 @@ function applyStroke(ctx: any, style: any, start: any, end: any) {
|
|
|
104
104
|
const prevLineCap = ctx.lineCap;
|
|
105
105
|
|
|
106
106
|
if (gradient) {
|
|
107
|
-
ctx.strokeStyle =
|
|
107
|
+
ctx.strokeStyle = createGradientFill(ctx, gradient, { x: start.x, y: start.y, w: end.x - start.x, h: end.y - start.y });
|
|
108
108
|
} else {
|
|
109
109
|
ctx.strokeStyle = color || prevStrokeStyle;
|
|
110
110
|
}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { SKRSContext2D } from '@napi-rs/canvas';
|
|
2
|
+
import { ImageFilter } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Applies image filters to a canvas context
|
|
6
|
+
* @param ctx Canvas 2D context
|
|
7
|
+
* @param filters Array of filters to apply
|
|
8
|
+
* @param width Canvas width
|
|
9
|
+
* @param height Canvas height
|
|
10
|
+
*/
|
|
11
|
+
export function applyImageFilters(
|
|
12
|
+
ctx: SKRSContext2D,
|
|
13
|
+
filters: ImageFilter[],
|
|
14
|
+
width: number,
|
|
15
|
+
height: number
|
|
16
|
+
): void {
|
|
17
|
+
if (!filters || filters.length === 0) return;
|
|
18
|
+
|
|
19
|
+
ctx.save();
|
|
20
|
+
|
|
21
|
+
for (const filter of filters) {
|
|
22
|
+
switch (filter.type) {
|
|
23
|
+
case 'gaussianBlur':
|
|
24
|
+
applyGaussianBlur(ctx, filter.intensity || 0);
|
|
25
|
+
break;
|
|
26
|
+
case 'motionBlur':
|
|
27
|
+
applyMotionBlur(ctx, filter.intensity || 0, filter.angle || 0);
|
|
28
|
+
break;
|
|
29
|
+
case 'radialBlur':
|
|
30
|
+
applyRadialBlur(ctx, filter.intensity || 0, filter.centerX || width/2, filter.centerY || height/2);
|
|
31
|
+
break;
|
|
32
|
+
case 'sharpen':
|
|
33
|
+
applySharpen(ctx, filter.intensity || 0);
|
|
34
|
+
break;
|
|
35
|
+
case 'noise':
|
|
36
|
+
applyNoise(ctx, filter.intensity || 0.1);
|
|
37
|
+
break;
|
|
38
|
+
case 'grain':
|
|
39
|
+
applyGrain(ctx, filter.intensity || 0.05);
|
|
40
|
+
break;
|
|
41
|
+
case 'edgeDetection':
|
|
42
|
+
applyEdgeDetection(ctx, filter.intensity || 1);
|
|
43
|
+
break;
|
|
44
|
+
case 'emboss':
|
|
45
|
+
applyEmboss(ctx, filter.intensity || 1);
|
|
46
|
+
break;
|
|
47
|
+
case 'invert':
|
|
48
|
+
applyInvert(ctx);
|
|
49
|
+
break;
|
|
50
|
+
case 'grayscale':
|
|
51
|
+
applyGrayscale(ctx);
|
|
52
|
+
break;
|
|
53
|
+
case 'sepia':
|
|
54
|
+
applySepia(ctx);
|
|
55
|
+
break;
|
|
56
|
+
case 'pixelate':
|
|
57
|
+
applyPixelate(ctx, filter.size || 10);
|
|
58
|
+
break;
|
|
59
|
+
case 'brightness':
|
|
60
|
+
applyBrightness(ctx, filter.value || 0);
|
|
61
|
+
break;
|
|
62
|
+
case 'contrast':
|
|
63
|
+
applyContrast(ctx, filter.value || 0);
|
|
64
|
+
break;
|
|
65
|
+
case 'saturation':
|
|
66
|
+
applySaturation(ctx, filter.value || 0);
|
|
67
|
+
break;
|
|
68
|
+
case 'hueShift':
|
|
69
|
+
applyHueShift(ctx, filter.value || 0);
|
|
70
|
+
break;
|
|
71
|
+
case 'posterize':
|
|
72
|
+
applyPosterize(ctx, filter.levels || 4);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
ctx.restore();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Individual filter implementations
|
|
81
|
+
function applyGaussianBlur(ctx: SKRSContext2D, intensity: number): void {
|
|
82
|
+
if (intensity > 0) {
|
|
83
|
+
ctx.filter = `blur(${intensity}px)`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function applyMotionBlur(ctx: SKRSContext2D, intensity: number, angle: number): void {
|
|
88
|
+
if (intensity > 0) {
|
|
89
|
+
// Motion blur is approximated with directional blur
|
|
90
|
+
const radians = (angle * Math.PI) / 180;
|
|
91
|
+
const blurX = Math.cos(radians) * intensity;
|
|
92
|
+
const blurY = Math.sin(radians) * intensity;
|
|
93
|
+
ctx.filter = `blur(${Math.abs(blurX)}px ${Math.abs(blurY)}px)`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function applyRadialBlur(ctx: SKRSContext2D, intensity: number, centerX: number, centerY: number): void {
|
|
98
|
+
if (intensity > 0) {
|
|
99
|
+
// Radial blur is approximated with multiple directional blurs
|
|
100
|
+
ctx.filter = `blur(${intensity}px)`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function applySharpen(ctx: SKRSContext2D, intensity: number): void {
|
|
105
|
+
if (intensity > 0) {
|
|
106
|
+
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
107
|
+
const data = imageData.data;
|
|
108
|
+
const width = ctx.canvas.width;
|
|
109
|
+
const height = ctx.canvas.height;
|
|
110
|
+
|
|
111
|
+
// Create a copy for the sharpening kernel
|
|
112
|
+
const originalData = new Uint8ClampedArray(data);
|
|
113
|
+
|
|
114
|
+
// Apply sharpening kernel
|
|
115
|
+
for (let y = 1; y < height - 1; y++) {
|
|
116
|
+
for (let x = 1; x < width - 1; x++) {
|
|
117
|
+
const idx = (y * width + x) * 4;
|
|
118
|
+
|
|
119
|
+
// Sharpening kernel: [[0,-1,0],[-1,5,-1],[0,-1,0]]
|
|
120
|
+
let r = 0, g = 0, b = 0;
|
|
121
|
+
|
|
122
|
+
for (let ky = -1; ky <= 1; ky++) {
|
|
123
|
+
for (let kx = -1; kx <= 1; kx++) {
|
|
124
|
+
const kidx = ((y + ky) * width + (x + kx)) * 4;
|
|
125
|
+
const kernelValue = (ky === 0 && kx === 0) ? 5 : -1;
|
|
126
|
+
|
|
127
|
+
r += originalData[kidx] * kernelValue;
|
|
128
|
+
g += originalData[kidx + 1] * kernelValue;
|
|
129
|
+
b += originalData[kidx + 2] * kernelValue;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Apply intensity
|
|
134
|
+
data[idx] = Math.max(0, Math.min(255, originalData[idx] + (r - originalData[idx]) * intensity));
|
|
135
|
+
data[idx + 1] = Math.max(0, Math.min(255, originalData[idx + 1] + (g - originalData[idx + 1]) * intensity));
|
|
136
|
+
data[idx + 2] = Math.max(0, Math.min(255, originalData[idx + 2] + (b - originalData[idx + 2]) * intensity));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
ctx.putImageData(imageData, 0, 0);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function applyNoise(ctx: SKRSContext2D, intensity: number): void {
|
|
145
|
+
if (intensity > 0) {
|
|
146
|
+
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
147
|
+
const data = imageData.data;
|
|
148
|
+
|
|
149
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
150
|
+
const noise = (Math.random() - 0.5) * intensity * 255;
|
|
151
|
+
data[i] = Math.max(0, Math.min(255, data[i] + noise)); // R
|
|
152
|
+
data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + noise)); // G
|
|
153
|
+
data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + noise)); // B
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
ctx.putImageData(imageData, 0, 0);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function applyGrain(ctx: SKRSContext2D, intensity: number): void {
|
|
161
|
+
if (intensity > 0) {
|
|
162
|
+
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
163
|
+
const data = imageData.data;
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
166
|
+
const grain = (Math.random() - 0.5) * intensity * 100;
|
|
167
|
+
data[i] = Math.max(0, Math.min(255, data[i] + grain)); // R
|
|
168
|
+
data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + grain)); // G
|
|
169
|
+
data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + grain)); // B
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
ctx.putImageData(imageData, 0, 0);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function applyEdgeDetection(ctx: SKRSContext2D, intensity: number): void {
|
|
177
|
+
if (intensity > 0) {
|
|
178
|
+
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
179
|
+
const data = imageData.data;
|
|
180
|
+
const width = ctx.canvas.width;
|
|
181
|
+
const height = ctx.canvas.height;
|
|
182
|
+
|
|
183
|
+
// Create a copy for the edge detection kernel
|
|
184
|
+
const originalData = new Uint8ClampedArray(data);
|
|
185
|
+
|
|
186
|
+
// Apply Sobel edge detection kernel
|
|
187
|
+
for (let y = 1; y < height - 1; y++) {
|
|
188
|
+
for (let x = 1; x < width - 1; x++) {
|
|
189
|
+
const idx = (y * width + x) * 4;
|
|
190
|
+
|
|
191
|
+
// Sobel X kernel: [[-1,0,1],[-2,0,2],[-1,0,1]]
|
|
192
|
+
// Sobel Y kernel: [[-1,-2,-1],[0,0,0],[1,2,1]]
|
|
193
|
+
let gx = 0, gy = 0;
|
|
194
|
+
|
|
195
|
+
for (let ky = -1; ky <= 1; ky++) {
|
|
196
|
+
for (let kx = -1; kx <= 1; kx++) {
|
|
197
|
+
const kidx = ((y + ky) * width + (x + kx)) * 4;
|
|
198
|
+
const gray = (originalData[kidx] + originalData[kidx + 1] + originalData[kidx + 2]) / 3;
|
|
199
|
+
|
|
200
|
+
const sobelX = (kx === -1) ? -1 : (kx === 0) ? 0 : 1;
|
|
201
|
+
const sobelY = (ky === -1) ? -1 : (ky === 0) ? 0 : 1;
|
|
202
|
+
|
|
203
|
+
gx += gray * sobelX;
|
|
204
|
+
gy += gray * sobelY;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const magnitude = Math.sqrt(gx * gx + gy * gy) * intensity;
|
|
209
|
+
const edgeValue = Math.min(255, magnitude);
|
|
210
|
+
|
|
211
|
+
data[idx] = edgeValue; // R
|
|
212
|
+
data[idx + 1] = edgeValue; // G
|
|
213
|
+
data[idx + 2] = edgeValue; // B
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
ctx.putImageData(imageData, 0, 0);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function applyEmboss(ctx: SKRSContext2D, intensity: number): void {
|
|
222
|
+
if (intensity > 0) {
|
|
223
|
+
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
224
|
+
const data = imageData.data;
|
|
225
|
+
const width = ctx.canvas.width;
|
|
226
|
+
const height = ctx.canvas.height;
|
|
227
|
+
|
|
228
|
+
// Create a copy for the emboss kernel
|
|
229
|
+
const originalData = new Uint8ClampedArray(data);
|
|
230
|
+
|
|
231
|
+
// Apply emboss kernel
|
|
232
|
+
for (let y = 1; y < height - 1; y++) {
|
|
233
|
+
for (let x = 1; x < width - 1; x++) {
|
|
234
|
+
const idx = (y * width + x) * 4;
|
|
235
|
+
|
|
236
|
+
// Emboss kernel: [[-2,-1,0],[-1,1,1],[0,1,2]]
|
|
237
|
+
let r = 0, g = 0, b = 0;
|
|
238
|
+
|
|
239
|
+
for (let ky = -1; ky <= 1; ky++) {
|
|
240
|
+
for (let kx = -1; kx <= 1; kx++) {
|
|
241
|
+
const kidx = ((y + ky) * width + (x + kx)) * 4;
|
|
242
|
+
let kernelValue = 0;
|
|
243
|
+
|
|
244
|
+
if (ky === -1 && kx === -1) kernelValue = -2;
|
|
245
|
+
else if (ky === -1 && kx === 0) kernelValue = -1;
|
|
246
|
+
else if (ky === -1 && kx === 1) kernelValue = 0;
|
|
247
|
+
else if (ky === 0 && kx === -1) kernelValue = -1;
|
|
248
|
+
else if (ky === 0 && kx === 0) kernelValue = 1;
|
|
249
|
+
else if (ky === 0 && kx === 1) kernelValue = 1;
|
|
250
|
+
else if (ky === 1 && kx === -1) kernelValue = 0;
|
|
251
|
+
else if (ky === 1 && kx === 0) kernelValue = 1;
|
|
252
|
+
else if (ky === 1 && kx === 1) kernelValue = 2;
|
|
253
|
+
|
|
254
|
+
r += originalData[kidx] * kernelValue;
|
|
255
|
+
g += originalData[kidx + 1] * kernelValue;
|
|
256
|
+
b += originalData[kidx + 2] * kernelValue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Apply intensity and add 128 for emboss effect
|
|
261
|
+
data[idx] = Math.max(0, Math.min(255, 128 + r * intensity));
|
|
262
|
+
data[idx + 1] = Math.max(0, Math.min(255, 128 + g * intensity));
|
|
263
|
+
data[idx + 2] = Math.max(0, Math.min(255, 128 + b * intensity));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
ctx.putImageData(imageData, 0, 0);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function applyInvert(ctx: SKRSContext2D): void {
|
|
272
|
+
ctx.filter = 'invert(100%)';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function applyGrayscale(ctx: SKRSContext2D): void {
|
|
276
|
+
ctx.filter = 'grayscale(100%)';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function applySepia(ctx: SKRSContext2D): void {
|
|
280
|
+
ctx.filter = 'sepia(100%)';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function applyPixelate(ctx: SKRSContext2D, size: number): void {
|
|
284
|
+
if (size > 1) {
|
|
285
|
+
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
286
|
+
const data = imageData.data;
|
|
287
|
+
const width = ctx.canvas.width;
|
|
288
|
+
const height = ctx.canvas.height;
|
|
289
|
+
|
|
290
|
+
// Create pixelated version
|
|
291
|
+
for (let y = 0; y < height; y += size) {
|
|
292
|
+
for (let x = 0; x < width; x += size) {
|
|
293
|
+
// Get average color of the block
|
|
294
|
+
let r = 0, g = 0, b = 0, count = 0;
|
|
295
|
+
|
|
296
|
+
for (let dy = 0; dy < size && y + dy < height; dy++) {
|
|
297
|
+
for (let dx = 0; dx < size && x + dx < width; dx++) {
|
|
298
|
+
const idx = ((y + dy) * width + (x + dx)) * 4;
|
|
299
|
+
r += data[idx];
|
|
300
|
+
g += data[idx + 1];
|
|
301
|
+
b += data[idx + 2];
|
|
302
|
+
count++;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
r = Math.round(r / count);
|
|
307
|
+
g = Math.round(g / count);
|
|
308
|
+
b = Math.round(b / count);
|
|
309
|
+
|
|
310
|
+
// Apply the average color to the entire block
|
|
311
|
+
for (let dy = 0; dy < size && y + dy < height; dy++) {
|
|
312
|
+
for (let dx = 0; dx < size && x + dx < width; dx++) {
|
|
313
|
+
const idx = ((y + dy) * width + (x + dx)) * 4;
|
|
314
|
+
data[idx] = r;
|
|
315
|
+
data[idx + 1] = g;
|
|
316
|
+
data[idx + 2] = b;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
ctx.putImageData(imageData, 0, 0);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function applyBrightness(ctx: SKRSContext2D, value: number): void {
|
|
327
|
+
ctx.filter = `brightness(${100 + value}%)`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function applyContrast(ctx: SKRSContext2D, value: number): void {
|
|
331
|
+
ctx.filter = `contrast(${100 + value}%)`;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function applySaturation(ctx: SKRSContext2D, value: number): void {
|
|
335
|
+
ctx.filter = `saturate(${100 + value}%)`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function applyHueShift(ctx: SKRSContext2D, value: number): void {
|
|
339
|
+
ctx.filter = `hue-rotate(${value}deg)`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function applyPosterize(ctx: SKRSContext2D, levels: number): void {
|
|
343
|
+
if (levels > 1) {
|
|
344
|
+
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
345
|
+
const data = imageData.data;
|
|
346
|
+
const step = 255 / (levels - 1);
|
|
347
|
+
|
|
348
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
349
|
+
data[i] = Math.round(data[i] / step) * step; // R
|
|
350
|
+
data[i + 1] = Math.round(data[i + 1] / step) * step; // G
|
|
351
|
+
data[i + 2] = Math.round(data[i + 2] / step) * step; // B
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
ctx.putImageData(imageData, 0, 0);
|
|
355
|
+
}
|
|
356
|
+
}
|