apexify.js 4.7.95 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/ai/ApexAI.d.ts.map +1 -1
- package/dist/cjs/ai/ApexAI.js +42 -23
- package/dist/cjs/ai/ApexAI.js.map +1 -1
- package/dist/cjs/ai/ApexModules.d.ts.map +1 -1
- package/dist/cjs/ai/ApexModules.js +35 -21
- package/dist/cjs/ai/ApexModules.js.map +1 -1
- package/dist/cjs/ai/buttons/tools.js +8 -8
- package/dist/cjs/ai/buttons/tools.js.map +1 -1
- package/dist/cjs/ai/functions/tokenizer.d.ts +10 -0
- package/dist/cjs/ai/functions/tokenizer.d.ts.map +1 -0
- package/dist/cjs/ai/functions/tokenizer.js +64 -0
- package/dist/cjs/ai/functions/tokenizer.js.map +1 -0
- package/dist/cjs/ai/modals/electronHub/chatmodels.d.ts.map +1 -0
- package/dist/cjs/ai/{modals-chat → modals}/electronHub/chatmodels.js +8 -11
- package/dist/cjs/ai/modals/electronHub/chatmodels.js.map +1 -0
- package/dist/cjs/ai/modals/electronHub/imageModels.d.ts.map +1 -0
- package/dist/cjs/ai/{modals-chat → modals}/electronHub/imageModels.js +9 -5
- package/dist/cjs/ai/modals/electronHub/imageModels.js.map +1 -0
- package/dist/cjs/ai/modals/electronHub/songModels.d.ts.map +1 -0
- package/dist/cjs/ai/modals/electronHub/songModels.js.map +1 -0
- package/dist/cjs/ai/modals/electronHub/speechModels.d.ts.map +1 -0
- package/dist/cjs/ai/{modals-chat → modals}/electronHub/speechModels.js +4 -1
- package/dist/cjs/ai/modals/electronHub/speechModels.js.map +1 -0
- package/dist/cjs/ai/modals/electronHub/videoModels.d.ts.map +1 -0
- package/dist/cjs/ai/modals/electronHub/videoModels.js.map +1 -0
- package/dist/cjs/ai/modals/groq/chatgroq.d.ts.map +1 -0
- package/dist/cjs/ai/modals/groq/chatgroq.js.map +1 -0
- package/dist/cjs/ai/modals/groq/imageAnalyzer.d.ts.map +1 -0
- package/dist/cjs/ai/modals/groq/imageAnalyzer.js.map +1 -0
- package/dist/cjs/ai/modals/groq/whisper.d.ts.map +1 -0
- package/dist/cjs/ai/{modals-chat → modals}/groq/whisper.js +34 -33
- package/dist/cjs/ai/modals/groq/whisper.js.map +1 -0
- package/dist/cjs/ai/modals/hercai/chatModels.d.ts.map +1 -0
- package/dist/cjs/ai/modals/hercai/chatModels.js.map +1 -0
- package/dist/cjs/ai/utils.d.ts +1 -1
- package/dist/cjs/ai/utils.d.ts.map +1 -1
- package/dist/cjs/ai/utils.js +1 -1
- package/dist/cjs/ai/utils.js.map +1 -1
- package/dist/cjs/canvas/ApexPainter.d.ts +20 -15
- package/dist/cjs/canvas/ApexPainter.d.ts.map +1 -1
- package/dist/cjs/canvas/ApexPainter.js +143 -23
- package/dist/cjs/canvas/ApexPainter.js.map +1 -1
- package/dist/cjs/canvas/utils/Image/imageProperties.d.ts.map +1 -1
- package/dist/cjs/canvas/utils/Image/imageProperties.js +47 -24
- package/dist/cjs/canvas/utils/Image/imageProperties.js.map +1 -1
- package/dist/cjs/canvas/utils/types.d.ts +21 -12
- package/dist/cjs/canvas/utils/types.d.ts.map +1 -1
- package/dist/cjs/canvas/utils/types.js.map +1 -1
- package/dist/cjs/canvas/utils/utils.d.ts +2 -2
- package/dist/cjs/canvas/utils/utils.d.ts.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/ai/ApexAI.d.ts.map +1 -1
- package/dist/esm/ai/ApexAI.js +42 -23
- package/dist/esm/ai/ApexAI.js.map +1 -1
- package/dist/esm/ai/ApexModules.d.ts.map +1 -1
- package/dist/esm/ai/ApexModules.js +35 -21
- package/dist/esm/ai/ApexModules.js.map +1 -1
- package/dist/esm/ai/buttons/tools.js +8 -8
- package/dist/esm/ai/buttons/tools.js.map +1 -1
- package/dist/esm/ai/functions/tokenizer.d.ts +10 -0
- package/dist/esm/ai/functions/tokenizer.d.ts.map +1 -0
- package/dist/esm/ai/functions/tokenizer.js +64 -0
- package/dist/esm/ai/functions/tokenizer.js.map +1 -0
- package/dist/esm/ai/modals/electronHub/chatmodels.d.ts.map +1 -0
- package/dist/esm/ai/{modals-chat → modals}/electronHub/chatmodels.js +8 -11
- package/dist/esm/ai/modals/electronHub/chatmodels.js.map +1 -0
- package/dist/esm/ai/modals/electronHub/imageModels.d.ts.map +1 -0
- package/dist/esm/ai/{modals-chat → modals}/electronHub/imageModels.js +9 -5
- package/dist/esm/ai/modals/electronHub/imageModels.js.map +1 -0
- package/dist/esm/ai/modals/electronHub/songModels.d.ts.map +1 -0
- package/dist/esm/ai/modals/electronHub/songModels.js.map +1 -0
- package/dist/esm/ai/modals/electronHub/speechModels.d.ts.map +1 -0
- package/dist/esm/ai/{modals-chat → modals}/electronHub/speechModels.js +4 -1
- package/dist/esm/ai/modals/electronHub/speechModels.js.map +1 -0
- package/dist/esm/ai/modals/electronHub/videoModels.d.ts.map +1 -0
- package/dist/esm/ai/modals/electronHub/videoModels.js.map +1 -0
- package/dist/esm/ai/modals/groq/chatgroq.d.ts.map +1 -0
- package/dist/esm/ai/modals/groq/chatgroq.js.map +1 -0
- package/dist/esm/ai/modals/groq/imageAnalyzer.d.ts.map +1 -0
- package/dist/esm/ai/modals/groq/imageAnalyzer.js.map +1 -0
- package/dist/esm/ai/modals/groq/whisper.d.ts.map +1 -0
- package/dist/esm/ai/{modals-chat → modals}/groq/whisper.js +34 -33
- package/dist/esm/ai/modals/groq/whisper.js.map +1 -0
- package/dist/esm/ai/modals/hercai/chatModels.d.ts.map +1 -0
- package/dist/esm/ai/modals/hercai/chatModels.js.map +1 -0
- package/dist/esm/ai/utils.d.ts +1 -1
- package/dist/esm/ai/utils.d.ts.map +1 -1
- package/dist/esm/ai/utils.js +1 -1
- package/dist/esm/ai/utils.js.map +1 -1
- package/dist/esm/canvas/ApexPainter.d.ts +20 -15
- package/dist/esm/canvas/ApexPainter.d.ts.map +1 -1
- package/dist/esm/canvas/ApexPainter.js +143 -23
- package/dist/esm/canvas/ApexPainter.js.map +1 -1
- package/dist/esm/canvas/utils/Image/imageProperties.d.ts.map +1 -1
- package/dist/esm/canvas/utils/Image/imageProperties.js +47 -24
- package/dist/esm/canvas/utils/Image/imageProperties.js.map +1 -1
- package/dist/esm/canvas/utils/types.d.ts +21 -12
- package/dist/esm/canvas/utils/types.d.ts.map +1 -1
- package/dist/esm/canvas/utils/types.js.map +1 -1
- package/dist/esm/canvas/utils/utils.d.ts +2 -2
- package/dist/esm/canvas/utils/utils.d.ts.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/lib/ai/ApexAI.ts +69 -48
- package/lib/ai/ApexModules.ts +45 -33
- package/lib/ai/buttons/tools.ts +8 -8
- package/lib/ai/functions/tokenizer.ts +69 -0
- package/lib/ai/modals/electronHub/chatmodels.ts +57 -0
- package/lib/ai/{modals-chat → modals}/electronHub/imageModels.ts +17 -13
- package/lib/ai/{modals-chat → modals}/electronHub/speechModels.ts +5 -1
- package/lib/ai/modals/groq/whisper.ts +114 -0
- package/lib/ai/utils.ts +1 -1
- package/lib/canvas/ApexPainter.ts +214 -45
- package/lib/canvas/utils/Image/imageProperties.ts +67 -24
- package/lib/canvas/utils/types.ts +22 -14
- package/lib/canvas/utils/utils.ts +4 -2
- package/package.json +2 -2
- package/dist/cjs/ai/modals-chat/electronHub/chatmodels.d.ts.map +0 -1
- package/dist/cjs/ai/modals-chat/electronHub/chatmodels.js.map +0 -1
- package/dist/cjs/ai/modals-chat/electronHub/imageModels.d.ts.map +0 -1
- package/dist/cjs/ai/modals-chat/electronHub/imageModels.js.map +0 -1
- package/dist/cjs/ai/modals-chat/electronHub/songModels.d.ts.map +0 -1
- package/dist/cjs/ai/modals-chat/electronHub/songModels.js.map +0 -1
- package/dist/cjs/ai/modals-chat/electronHub/speechModels.d.ts.map +0 -1
- package/dist/cjs/ai/modals-chat/electronHub/speechModels.js.map +0 -1
- package/dist/cjs/ai/modals-chat/electronHub/videoModels.d.ts.map +0 -1
- package/dist/cjs/ai/modals-chat/electronHub/videoModels.js.map +0 -1
- package/dist/cjs/ai/modals-chat/groq/chatgroq.d.ts.map +0 -1
- package/dist/cjs/ai/modals-chat/groq/chatgroq.js.map +0 -1
- package/dist/cjs/ai/modals-chat/groq/imageAnalyzer.d.ts.map +0 -1
- package/dist/cjs/ai/modals-chat/groq/imageAnalyzer.js.map +0 -1
- package/dist/cjs/ai/modals-chat/groq/whisper.d.ts.map +0 -1
- package/dist/cjs/ai/modals-chat/groq/whisper.js.map +0 -1
- package/dist/cjs/ai/modals-chat/hercai/chatModels.d.ts.map +0 -1
- package/dist/cjs/ai/modals-chat/hercai/chatModels.js.map +0 -1
- package/dist/esm/ai/modals-chat/electronHub/chatmodels.d.ts.map +0 -1
- package/dist/esm/ai/modals-chat/electronHub/chatmodels.js.map +0 -1
- package/dist/esm/ai/modals-chat/electronHub/imageModels.d.ts.map +0 -1
- package/dist/esm/ai/modals-chat/electronHub/imageModels.js.map +0 -1
- package/dist/esm/ai/modals-chat/electronHub/songModels.d.ts.map +0 -1
- package/dist/esm/ai/modals-chat/electronHub/songModels.js.map +0 -1
- package/dist/esm/ai/modals-chat/electronHub/speechModels.d.ts.map +0 -1
- package/dist/esm/ai/modals-chat/electronHub/speechModels.js.map +0 -1
- package/dist/esm/ai/modals-chat/electronHub/videoModels.d.ts.map +0 -1
- package/dist/esm/ai/modals-chat/electronHub/videoModels.js.map +0 -1
- package/dist/esm/ai/modals-chat/groq/chatgroq.d.ts.map +0 -1
- package/dist/esm/ai/modals-chat/groq/chatgroq.js.map +0 -1
- package/dist/esm/ai/modals-chat/groq/imageAnalyzer.d.ts.map +0 -1
- package/dist/esm/ai/modals-chat/groq/imageAnalyzer.js.map +0 -1
- package/dist/esm/ai/modals-chat/groq/whisper.d.ts.map +0 -1
- package/dist/esm/ai/modals-chat/groq/whisper.js.map +0 -1
- package/dist/esm/ai/modals-chat/hercai/chatModels.d.ts.map +0 -1
- package/dist/esm/ai/modals-chat/hercai/chatModels.js.map +0 -1
- package/lib/ai/modals-chat/electronHub/chatmodels.ts +0 -64
- package/lib/ai/modals-chat/groq/whisper.ts +0 -113
- /package/dist/cjs/ai/{modals-chat → modals}/electronHub/chatmodels.d.ts +0 -0
- /package/dist/cjs/ai/{modals-chat → modals}/electronHub/imageModels.d.ts +0 -0
- /package/dist/cjs/ai/{modals-chat → modals}/electronHub/songModels.d.ts +0 -0
- /package/dist/cjs/ai/{modals-chat → modals}/electronHub/songModels.js +0 -0
- /package/dist/cjs/ai/{modals-chat → modals}/electronHub/speechModels.d.ts +0 -0
- /package/dist/cjs/ai/{modals-chat → modals}/electronHub/videoModels.d.ts +0 -0
- /package/dist/cjs/ai/{modals-chat → modals}/electronHub/videoModels.js +0 -0
- /package/dist/cjs/ai/{modals-chat → modals}/groq/chatgroq.d.ts +0 -0
- /package/dist/cjs/ai/{modals-chat → modals}/groq/chatgroq.js +0 -0
- /package/dist/cjs/ai/{modals-chat → modals}/groq/imageAnalyzer.d.ts +0 -0
- /package/dist/cjs/ai/{modals-chat → modals}/groq/imageAnalyzer.js +0 -0
- /package/dist/cjs/ai/{modals-chat → modals}/groq/whisper.d.ts +0 -0
- /package/dist/cjs/ai/{modals-chat → modals}/hercai/chatModels.d.ts +0 -0
- /package/dist/cjs/ai/{modals-chat → modals}/hercai/chatModels.js +0 -0
- /package/dist/esm/ai/{modals-chat → modals}/electronHub/chatmodels.d.ts +0 -0
- /package/dist/esm/ai/{modals-chat → modals}/electronHub/imageModels.d.ts +0 -0
- /package/dist/esm/ai/{modals-chat → modals}/electronHub/songModels.d.ts +0 -0
- /package/dist/esm/ai/{modals-chat → modals}/electronHub/songModels.js +0 -0
- /package/dist/esm/ai/{modals-chat → modals}/electronHub/speechModels.d.ts +0 -0
- /package/dist/esm/ai/{modals-chat → modals}/electronHub/videoModels.d.ts +0 -0
- /package/dist/esm/ai/{modals-chat → modals}/electronHub/videoModels.js +0 -0
- /package/dist/esm/ai/{modals-chat → modals}/groq/chatgroq.d.ts +0 -0
- /package/dist/esm/ai/{modals-chat → modals}/groq/chatgroq.js +0 -0
- /package/dist/esm/ai/{modals-chat → modals}/groq/imageAnalyzer.d.ts +0 -0
- /package/dist/esm/ai/{modals-chat → modals}/groq/imageAnalyzer.js +0 -0
- /package/dist/esm/ai/{modals-chat → modals}/groq/whisper.d.ts +0 -0
- /package/dist/esm/ai/{modals-chat → modals}/hercai/chatModels.d.ts +0 -0
- /package/dist/esm/ai/{modals-chat → modals}/hercai/chatModels.js +0 -0
- /package/lib/ai/{modals-chat → modals}/electronHub/songModels.ts +0 -0
- /package/lib/ai/{modals-chat → modals}/electronHub/videoModels.ts +0 -0
- /package/lib/ai/{modals-chat → modals}/groq/chatgroq.ts +0 -0
- /package/lib/ai/{modals-chat → modals}/groq/imageAnalyzer.ts +0 -0
- /package/lib/ai/{modals-chat → modals}/hercai/chatModels.ts +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import Groq from "groq-sdk";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { URL } from "url";
|
|
5
|
+
import https from "https";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Gets the file size of a given path.
|
|
9
|
+
*/
|
|
10
|
+
function getFileSize(filePath: string): number {
|
|
11
|
+
return fs.statSync(filePath).size;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates a readable stream from a local file or a remote URL.
|
|
16
|
+
*/
|
|
17
|
+
async function createReadableStream(filepathOrUrl: string): Promise<{ stream: fs.ReadStream | null; tempFilePath?: string; error?: string }> {
|
|
18
|
+
const maxFileSizeBytes = 25 * 1024 * 1024; // 25MB limit
|
|
19
|
+
|
|
20
|
+
if (filepathOrUrl.startsWith("http")) {
|
|
21
|
+
const parsedUrl = new URL(filepathOrUrl);
|
|
22
|
+
const fileExtension = path.extname(parsedUrl.pathname);
|
|
23
|
+
const tempFilePath = `temp-file-${Date.now()}${fileExtension}`;
|
|
24
|
+
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const file = fs.createWriteStream(tempFilePath);
|
|
27
|
+
const request = https.get(filepathOrUrl, (response) => {
|
|
28
|
+
let fileSize = 0;
|
|
29
|
+
|
|
30
|
+
response.on("data", (chunk) => {
|
|
31
|
+
fileSize += chunk.length;
|
|
32
|
+
if (fileSize > maxFileSizeBytes) {
|
|
33
|
+
request.destroy();
|
|
34
|
+
file.close();
|
|
35
|
+
fs.unlink(tempFilePath, () => resolve({ stream: null, error: "File size exceeds the limit (25MB)" }));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
response.pipe(file);
|
|
41
|
+
file.on("finish", () => {
|
|
42
|
+
file.close();
|
|
43
|
+
resolve({ stream: fs.createReadStream(tempFilePath), tempFilePath }); // ✅ Ensure fs.ReadStream
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
request.on("error", (err) => {
|
|
48
|
+
fs.unlink(tempFilePath, () => reject(err));
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
if (getFileSize(filepathOrUrl) > maxFileSizeBytes) {
|
|
53
|
+
return { stream: null, error: "File size exceeds the limit (25MB)" };
|
|
54
|
+
}
|
|
55
|
+
return { stream: fs.createReadStream(filepathOrUrl) }; // ✅ Ensure fs.ReadStream
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Transcribes audio using Groq Whisper API.
|
|
61
|
+
*/
|
|
62
|
+
export async function whisper(prompt: string, filepath: string, lang?: string, API_KEY?: string) {
|
|
63
|
+
try {
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
const groq = new Groq({
|
|
67
|
+
apiKey: API_KEY || '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',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
// Ensure filepath is properly resolved
|
|
72
|
+
const resolvedFilePath = filepath.startsWith("http") || path.isAbsolute(filepath)
|
|
73
|
+
? filepath
|
|
74
|
+
: path.join(process.cwd(), filepath);
|
|
75
|
+
|
|
76
|
+
const { stream: fileStream, tempFilePath, error } = await createReadableStream(resolvedFilePath);
|
|
77
|
+
|
|
78
|
+
if (error) return error;
|
|
79
|
+
if (!fileStream) return "Failed to create a readable file stream.";
|
|
80
|
+
|
|
81
|
+
// ✅ Ensure `fileStream` is a valid `fs.ReadStream`
|
|
82
|
+
const transcription = await groq.audio.transcriptions.create({
|
|
83
|
+
file: fileStream as fs.ReadStream, // ✅ Ensure correct type
|
|
84
|
+
model: "whisper-large-v3-turbo",
|
|
85
|
+
prompt: prompt,
|
|
86
|
+
temperature: 1,
|
|
87
|
+
language: lang || "eng",
|
|
88
|
+
response_format: "verbose_json",
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ✅ Delete the temp file if it was downloaded from a URL
|
|
92
|
+
if (tempFilePath && fs.existsSync(tempFilePath)) {
|
|
93
|
+
fs.unlinkSync(tempFilePath);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return transcription.text;
|
|
97
|
+
} catch (err: any) {
|
|
98
|
+
if (err?.status) {
|
|
99
|
+
switch (err.status) {
|
|
100
|
+
case 400:
|
|
101
|
+
return "Bad request, try again after a minute.";
|
|
102
|
+
case 429:
|
|
103
|
+
return "Rate limit exceeded, try again later or provide your own API key.";
|
|
104
|
+
case 401:
|
|
105
|
+
return "Invalid API key provided.";
|
|
106
|
+
default:
|
|
107
|
+
console.error(err);
|
|
108
|
+
return "An unknown error occurred.";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
console.error(err);
|
|
112
|
+
return "An unknown error occurred.";
|
|
113
|
+
}
|
|
114
|
+
}
|
package/lib/ai/utils.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from "./functions/readFiles";
|
|
2
2
|
import { ApexChat, ApexImagine, ApexListener, ApexVideo, ApexText2Speech, resetHistory } from "./ApexModules";
|
|
3
3
|
import { typeWriter } from "./functions/typeWriter" ;
|
|
4
|
-
import { groqAnalyzer } from "./modals
|
|
4
|
+
import { groqAnalyzer } from "./modals/groq/imageAnalyzer";
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
export {
|
|
@@ -3,13 +3,13 @@ import GIFEncoder from "gifencoder";
|
|
|
3
3
|
import ffmpeg from 'fluent-ffmpeg';
|
|
4
4
|
import { PassThrough} from "stream";
|
|
5
5
|
import axios from 'axios';
|
|
6
|
-
import fs from "fs";
|
|
6
|
+
import fs, { PathLike } from "fs";
|
|
7
7
|
import path from "path";
|
|
8
8
|
import { OutputFormat, CanvasConfig, TextObject, ImageProperties, GIFOptions, GIFResults, CustomOptions, cropOptions,
|
|
9
9
|
drawBackgroundGradient, drawBackgroundColor, customBackground, customLines, applyRotation, applyStroke,
|
|
10
10
|
applyShadow, imageRadius, drawShape, drawText, converter, resizingImg, applyColorFilters, imgEffects,verticalBarChart, pieChart,
|
|
11
11
|
lineChart, cropInner, cropOuter, bgRemoval, detectColors, removeColor, dataURL, base64, arrayBuffer, blob, url, GradientConfig, Frame,
|
|
12
|
-
PatternOptions, ExtractFramesOptions, backgroundRadius, applyZoom, ResizeOptions, applyPerspective
|
|
12
|
+
PatternOptions, ExtractFramesOptions, backgroundRadius, applyZoom, ResizeOptions, applyPerspective, MaskOptions, BlendOptions
|
|
13
13
|
} from "./utils/utils";
|
|
14
14
|
|
|
15
15
|
interface CanvasResults {
|
|
@@ -114,6 +114,7 @@ export class ApexPainter {
|
|
|
114
114
|
|
|
115
115
|
const canvas = createCanvas(baseImage.width, baseImage.height);
|
|
116
116
|
const ctx = canvas.getContext('2d') as SKRSContext2D | null;
|
|
117
|
+
|
|
117
118
|
if (!ctx) {
|
|
118
119
|
throw new Error('Unable to get 2D rendering context from canvas');
|
|
119
120
|
}
|
|
@@ -164,7 +165,6 @@ export class ApexPainter {
|
|
|
164
165
|
);
|
|
165
166
|
}
|
|
166
167
|
|
|
167
|
-
const size = { width: existingImage.width, height: existingImage.height };
|
|
168
168
|
drawText(ctx, mergedTextOptions);
|
|
169
169
|
}
|
|
170
170
|
|
|
@@ -336,24 +336,24 @@ async createGIF(gifFrames: { background: string; duration: number }[], options:
|
|
|
336
336
|
return resizingImg(resizeOptions)
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
-
async imgConverter(
|
|
340
|
-
return converter(
|
|
339
|
+
async imgConverter(source: string, newExtension: string) {
|
|
340
|
+
return converter(source, newExtension)
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
-
async
|
|
344
|
-
return imgEffects(
|
|
343
|
+
async effects(source: string, filters: any[]) {
|
|
344
|
+
return imgEffects(source, filters)
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
-
async colorsFilter(
|
|
348
|
-
return applyColorFilters(
|
|
347
|
+
async colorsFilter(source: string, filterColor: any, opacity?: number) {
|
|
348
|
+
return applyColorFilters(source, filterColor, opacity)
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
async colorAnalysis(
|
|
352
|
-
return detectColors(
|
|
351
|
+
async colorAnalysis(source: string) {
|
|
352
|
+
return detectColors(source)
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
-
async colorsRemover(
|
|
356
|
-
return removeColor(
|
|
355
|
+
async colorsRemover(source: string, colorToRemove: { red: number, green: number, blue: number }) {
|
|
356
|
+
return removeColor(source, colorToRemove)
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
async removeBackground(imageURL: string, apiKey: string) {
|
|
@@ -362,43 +362,52 @@ async createGIF(gifFrames: { background: string; duration: number }[], options:
|
|
|
362
362
|
|
|
363
363
|
async blend(
|
|
364
364
|
layers: {
|
|
365
|
-
image: string | Buffer;
|
|
366
|
-
blendMode: 'source-over' | 'source-in' | 'source-out' | 'source-atop' |
|
|
367
|
-
'destination-over' | 'destination-in' | 'destination-out' |
|
|
368
|
-
'destination-atop' | 'lighter' | 'copy' | 'xor' |
|
|
369
|
-
'multiply' | 'screen' | 'overlay' | 'darken' |
|
|
370
|
-
'lighten' | 'color-dodge' | 'color-burn' |
|
|
371
|
-
'hard-light' | 'soft-light' | 'difference' | 'exclusion' |
|
|
365
|
+
image: string | Buffer;
|
|
366
|
+
blendMode: 'source-over' | 'source-in' | 'source-out' | 'source-atop' |
|
|
367
|
+
'destination-over' | 'destination-in' | 'destination-out' |
|
|
368
|
+
'destination-atop' | 'lighter' | 'copy' | 'xor' |
|
|
369
|
+
'multiply' | 'screen' | 'overlay' | 'darken' |
|
|
370
|
+
'lighten' | 'color-dodge' | 'color-burn' |
|
|
371
|
+
'hard-light' | 'soft-light' | 'difference' | 'exclusion' |
|
|
372
372
|
'hue' | 'saturation' | 'color' | 'luminosity';
|
|
373
|
-
position?: { x: number; y: number };
|
|
374
|
-
opacity?: number;
|
|
373
|
+
position?: { x: number; y: number };
|
|
374
|
+
opacity?: number;
|
|
375
375
|
}[],
|
|
376
|
-
baseImageBuffer: Buffer
|
|
376
|
+
baseImageBuffer: Buffer,
|
|
377
|
+
defaultBlendMode: 'source-over' | 'source-in' | 'source-out' | 'source-atop' |
|
|
378
|
+
'destination-over' | 'destination-in' | 'destination-out' |
|
|
379
|
+
'destination-atop' | 'lighter' | 'copy' | 'xor' |
|
|
380
|
+
'multiply' | 'screen' | 'overlay' | 'darken' |
|
|
381
|
+
'lighten' | 'color-dodge' | 'color-burn' |
|
|
382
|
+
'hard-light' | 'soft-light' | 'difference' | 'exclusion' |
|
|
383
|
+
'hue' | 'saturation' | 'color' | 'luminosity' = 'source-over'
|
|
377
384
|
): Promise<Buffer> {
|
|
378
385
|
try {
|
|
379
386
|
const baseImage = await loadImage(baseImageBuffer);
|
|
380
387
|
const canvas = createCanvas(baseImage.width, baseImage.height);
|
|
381
388
|
const ctx = canvas.getContext('2d');
|
|
382
389
|
|
|
390
|
+
ctx.globalCompositeOperation = defaultBlendMode;
|
|
383
391
|
ctx.drawImage(baseImage, 0, 0);
|
|
384
392
|
|
|
385
393
|
for (const layer of layers) {
|
|
386
394
|
const layerImage = await loadImage(layer.image);
|
|
387
|
-
ctx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1.0;
|
|
395
|
+
ctx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1.0;
|
|
388
396
|
|
|
389
397
|
ctx.globalCompositeOperation = layer.blendMode;
|
|
390
398
|
ctx.drawImage(layerImage, layer.position?.x || 0, layer.position?.y || 0);
|
|
391
399
|
}
|
|
392
400
|
|
|
393
401
|
ctx.globalAlpha = 1.0;
|
|
394
|
-
ctx.globalCompositeOperation =
|
|
402
|
+
ctx.globalCompositeOperation = defaultBlendMode; // Reset to user-defined default
|
|
395
403
|
|
|
396
404
|
return canvas.toBuffer('image/png');
|
|
397
405
|
} catch (error) {
|
|
398
406
|
console.error('Error creating layered composition:', error);
|
|
399
407
|
throw new Error('Failed to create layered composition');
|
|
400
408
|
}
|
|
401
|
-
}
|
|
409
|
+
}
|
|
410
|
+
|
|
402
411
|
|
|
403
412
|
async createChart(data: any, type: { chartType: string, chartNumber: number}) {
|
|
404
413
|
|
|
@@ -489,13 +498,9 @@ async createGIF(gifFrames: { background: string; duration: number }[], options:
|
|
|
489
498
|
const shapeNames = ['circle', 'square', 'triangle', 'pentagon', 'hexagon', 'heptagon', 'octagon', 'star', 'kite', 'arrow', 'star', 'oval', 'heart', 'diamond', 'trapezoid', 'cloud'];
|
|
490
499
|
const isShape = shapeNames.includes(source.toLowerCase());
|
|
491
500
|
|
|
492
|
-
|
|
493
|
-
ctx.globalAlpha = opacity;
|
|
494
|
-
}
|
|
495
|
-
|
|
501
|
+
|
|
496
502
|
if (isShape) {
|
|
497
|
-
drawShape(ctx, { source, x, y, width, height, rotation, borderRadius, stroke, shadow, isFilled, color, gradient, borderPosition, filling });
|
|
498
|
-
ctx.globalAlpha = 1.0;
|
|
503
|
+
drawShape(ctx, { source, x, y, width, height, opacity, rotation, borderRadius, stroke, shadow, isFilled, color, gradient, borderPosition, filling });
|
|
499
504
|
return;
|
|
500
505
|
}
|
|
501
506
|
|
|
@@ -526,6 +531,10 @@ async createGIF(gifFrames: { background: string; duration: number }[], options:
|
|
|
526
531
|
ctx.scale(scale, scale);
|
|
527
532
|
}
|
|
528
533
|
|
|
534
|
+
if (opacity !== undefined) {
|
|
535
|
+
ctx.globalAlpha = opacity;
|
|
536
|
+
}
|
|
537
|
+
|
|
529
538
|
if (blur > 0) {
|
|
530
539
|
ctx.filter = `blur(${blur}px)`;
|
|
531
540
|
}
|
|
@@ -538,10 +547,14 @@ async createGIF(gifFrames: { background: string; duration: number }[], options:
|
|
|
538
547
|
}
|
|
539
548
|
|
|
540
549
|
imageRadius(ctx, loadedImage, x, y, width, height, borderRadius, borderPosition);
|
|
550
|
+
ctx.globalAlpha = 1.0;
|
|
551
|
+
|
|
552
|
+
if (stroke?.opacity) ctx.globalAlpha = stroke.opacity as number;
|
|
553
|
+
|
|
541
554
|
applyStroke(ctx, stroke, x, y, width, height, undefined);
|
|
542
|
-
|
|
543
|
-
ctx.restore();
|
|
544
555
|
ctx.globalAlpha = 1.0;
|
|
556
|
+
|
|
557
|
+
ctx.restore();
|
|
545
558
|
}
|
|
546
559
|
|
|
547
560
|
|
|
@@ -623,7 +636,7 @@ async extractFrames(videoSource: string | Buffer, options: ExtractFramesOptions)
|
|
|
623
636
|
* @param {Object} options - Options to customize the pattern.
|
|
624
637
|
* @returns {Promise<Buffer>} - The adjusted image buffer.
|
|
625
638
|
*/
|
|
626
|
-
async
|
|
639
|
+
async patterns(buffer:Buffer, options: PatternOptions) {
|
|
627
640
|
|
|
628
641
|
const img = await loadImage(buffer);
|
|
629
642
|
const canvas = createCanvas(img.width, img.height);
|
|
@@ -663,6 +676,15 @@ async setPatternBackground(buffer:Buffer, options: PatternOptions) {
|
|
|
663
676
|
case 'custom':
|
|
664
677
|
await this.drawCustomPattern(ctx, width, height, options, x, y);
|
|
665
678
|
break;
|
|
679
|
+
case 'noise':
|
|
680
|
+
await this.drawNoisePattern(ctx, width, height, x, y);
|
|
681
|
+
break;
|
|
682
|
+
case 'waves':
|
|
683
|
+
await this.drawWavePattern(ctx, width, height, options, x, y);
|
|
684
|
+
break;
|
|
685
|
+
case 'diagonal-checkerboard':
|
|
686
|
+
await this.drawDiagonalCheckerboardPattern(ctx, width, height, options, x, y);
|
|
687
|
+
break;
|
|
666
688
|
default:
|
|
667
689
|
throw new Error('Invalid pattern type specified.');
|
|
668
690
|
}
|
|
@@ -682,7 +704,7 @@ async setPatternBackground(buffer:Buffer, options: PatternOptions) {
|
|
|
682
704
|
* @param x The x offset of the area.
|
|
683
705
|
* @param y The y offset of the area.
|
|
684
706
|
*/
|
|
685
|
-
async fillWithGradient(
|
|
707
|
+
private async fillWithGradient(
|
|
686
708
|
ctx: SKRSContext2D,
|
|
687
709
|
width: number,
|
|
688
710
|
height: number,
|
|
@@ -727,7 +749,7 @@ async setPatternBackground(buffer:Buffer, options: PatternOptions) {
|
|
|
727
749
|
* @param x The x offset of the area.
|
|
728
750
|
* @param y The y offset of the area.
|
|
729
751
|
*/
|
|
730
|
-
async
|
|
752
|
+
private async drawDotsPattern(
|
|
731
753
|
ctx: SKRSContext2D,
|
|
732
754
|
width: number,
|
|
733
755
|
height: number,
|
|
@@ -759,7 +781,7 @@ async setPatternBackground(buffer:Buffer, options: PatternOptions) {
|
|
|
759
781
|
* @param x The x offset of the area.
|
|
760
782
|
* @param y The y offset of the area.
|
|
761
783
|
*/
|
|
762
|
-
async drawStripesPattern(
|
|
784
|
+
private async drawStripesPattern(
|
|
763
785
|
ctx: SKRSContext2D,
|
|
764
786
|
width: number,
|
|
765
787
|
height: number,
|
|
@@ -795,7 +817,7 @@ async setPatternBackground(buffer:Buffer, options: PatternOptions) {
|
|
|
795
817
|
* @param x The x offset of the area.
|
|
796
818
|
* @param y The y offset of the area.
|
|
797
819
|
*/
|
|
798
|
-
|
|
820
|
+
private async drawGridPattern(
|
|
799
821
|
ctx: SKRSContext2D,
|
|
800
822
|
width: number,
|
|
801
823
|
height: number,
|
|
@@ -833,7 +855,7 @@ async setPatternBackground(buffer:Buffer, options: PatternOptions) {
|
|
|
833
855
|
* @param x The x offset of the area.
|
|
834
856
|
* @param y The y offset of the area.
|
|
835
857
|
*/
|
|
836
|
-
async drawCheckerboardPattern(
|
|
858
|
+
private async drawCheckerboardPattern(
|
|
837
859
|
ctx: SKRSContext2D,
|
|
838
860
|
width: number,
|
|
839
861
|
height: number,
|
|
@@ -844,7 +866,7 @@ async setPatternBackground(buffer:Buffer, options: PatternOptions) {
|
|
|
844
866
|
const color1 = options.color || 'black';
|
|
845
867
|
const color2 = options.secondaryColor || 'white';
|
|
846
868
|
const size = options.size || 20;
|
|
847
|
-
|
|
869
|
+
|
|
848
870
|
for (let posY = y; posY < y + height; posY += size) {
|
|
849
871
|
for (let posX = x; posX < x + width; posX += size) {
|
|
850
872
|
ctx.fillStyle = (Math.floor(posX / size) + Math.floor(posY / size)) % 2 === 0 ? color1 : color2;
|
|
@@ -862,7 +884,7 @@ async setPatternBackground(buffer:Buffer, options: PatternOptions) {
|
|
|
862
884
|
* @param x The x offset of the area.
|
|
863
885
|
* @param y The y offset of the area.
|
|
864
886
|
*/
|
|
865
|
-
async drawCustomPattern(
|
|
887
|
+
private async drawCustomPattern(
|
|
866
888
|
ctx: SKRSContext2D,
|
|
867
889
|
width: number,
|
|
868
890
|
height: number,
|
|
@@ -881,7 +903,152 @@ async setPatternBackground(buffer:Buffer, options: PatternOptions) {
|
|
|
881
903
|
ctx.fillRect(0, 0, width, height);
|
|
882
904
|
ctx.resetTransform();
|
|
883
905
|
}
|
|
906
|
+
|
|
907
|
+
private async drawWavePattern(ctx: SKRSContext2D, width: number, height: number, options: PatternOptions, x = 0, y = 0) {
|
|
908
|
+
const color = options.color || 'black';
|
|
909
|
+
const waveHeight = options.size || 10;
|
|
910
|
+
const frequency = options.spacing || 20;
|
|
911
|
+
|
|
912
|
+
ctx.strokeStyle = color;
|
|
913
|
+
ctx.lineWidth = 2;
|
|
914
|
+
|
|
915
|
+
for (let posY = y; posY < y + height; posY += frequency) {
|
|
916
|
+
ctx.beginPath();
|
|
917
|
+
for (let posX = x; posX < x + width; posX += 5) {
|
|
918
|
+
const waveY = posY + Math.sin(posX / frequency) * waveHeight;
|
|
919
|
+
ctx.lineTo(posX, waveY);
|
|
920
|
+
}
|
|
921
|
+
ctx.stroke();
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
private async drawNoisePattern(ctx: SKRSContext2D, width: number, height: number, x = 0, y = 0) {
|
|
927
|
+
const imageData = ctx.createImageData(width, height);
|
|
928
|
+
const data = imageData.data;
|
|
929
|
+
|
|
930
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
931
|
+
const gray = Math.random() * 255;
|
|
932
|
+
data[i] = gray;
|
|
933
|
+
data[i + 1] = gray;
|
|
934
|
+
data[i + 2] = gray;
|
|
935
|
+
data[i + 3] = Math.random() * 255;
|
|
936
|
+
}
|
|
884
937
|
|
|
938
|
+
ctx.putImageData(imageData, x, y);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
private async drawDiagonalCheckerboardPattern(ctx: SKRSContext2D, width: number, height: number, options: PatternOptions, x = 0, y = 0) {
|
|
943
|
+
const color1 = options.color || 'black';
|
|
944
|
+
const color2 = options.secondaryColor || 'white';
|
|
945
|
+
const size = options.size || 20;
|
|
946
|
+
|
|
947
|
+
ctx.save();
|
|
948
|
+
ctx.translate(x + width / 2, y + height / 2);
|
|
949
|
+
ctx.rotate(Math.PI / 4);
|
|
950
|
+
ctx.translate(-width / 2, -height / 2);
|
|
951
|
+
|
|
952
|
+
for (let posY = 0; posY < height; posY += size) {
|
|
953
|
+
for (let posX = 0; posX < width; posX += size) {
|
|
954
|
+
ctx.fillStyle = (Math.floor(posX / size) + Math.floor(posY / size)) % 2 === 0 ? color1 : color2;
|
|
955
|
+
ctx.fillRect(posX, posY, size, size);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
ctx.restore();
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
async masking(
|
|
964
|
+
source: string | Buffer | PathLike | Uint8Array,
|
|
965
|
+
maskSource: string | Buffer | PathLike | Uint8Array,
|
|
966
|
+
options: MaskOptions = { type: "alpha" }
|
|
967
|
+
): Promise<Buffer> {
|
|
968
|
+
const img = await loadImage(source);
|
|
969
|
+
const mask = await loadImage(maskSource);
|
|
970
|
+
|
|
971
|
+
const canvas = createCanvas(img.width, img.height);
|
|
972
|
+
const ctx = canvas.getContext("2d") as SKRSContext2D;
|
|
973
|
+
|
|
974
|
+
ctx.drawImage(img, 0, 0, img.width, img.height);
|
|
975
|
+
|
|
976
|
+
const maskCanvas = createCanvas(img.width, img.height);
|
|
977
|
+
const maskCtx = maskCanvas.getContext("2d") as SKRSContext2D;
|
|
978
|
+
maskCtx.drawImage(mask, 0, 0, img.width, img.height);
|
|
979
|
+
|
|
980
|
+
const maskData = maskCtx.getImageData(0, 0, img.width, img.height);
|
|
981
|
+
const imgData = ctx.getImageData(0, 0, img.width, img.height);
|
|
982
|
+
|
|
983
|
+
for (let i = 0; i < maskData.data.length; i += 4) {
|
|
984
|
+
let alphaValue = 255;
|
|
985
|
+
|
|
986
|
+
if (options.type === "grayscale") {
|
|
987
|
+
const grayscale = maskData.data[i] * 0.3 + maskData.data[i + 1] * 0.59 + maskData.data[i + 2] * 0.11;
|
|
988
|
+
alphaValue = grayscale >= (options.threshold ?? 128) ? 255 : 0;
|
|
989
|
+
} else if (options.type === "alpha") {
|
|
990
|
+
alphaValue = maskData.data[i + 3];
|
|
991
|
+
} else if (options.type === "color" && options.colorKey) {
|
|
992
|
+
const colorMatch =
|
|
993
|
+
maskData.data[i] === parseInt(options.colorKey.slice(1, 3), 16) &&
|
|
994
|
+
maskData.data[i + 1] === parseInt(options.colorKey.slice(3, 5), 16) &&
|
|
995
|
+
maskData.data[i + 2] === parseInt(options.colorKey.slice(5, 7), 16);
|
|
996
|
+
alphaValue = colorMatch ? 0 : 255;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
if (options.invert) alphaValue = 255 - alphaValue;
|
|
1000
|
+
|
|
1001
|
+
imgData.data[i + 3] = alphaValue;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
ctx.putImageData(imgData, 0, 0);
|
|
1005
|
+
|
|
1006
|
+
return canvas.toBuffer("image/png");
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
async gradientBlend(
|
|
1010
|
+
source: string | Buffer | PathLike | Uint8Array,
|
|
1011
|
+
options: BlendOptions
|
|
1012
|
+
): Promise<Buffer> {
|
|
1013
|
+
const img = await loadImage(source);
|
|
1014
|
+
const canvas = createCanvas(img.width, img.height);
|
|
1015
|
+
const ctx = canvas.getContext("2d") as SKRSContext2D;
|
|
1016
|
+
|
|
1017
|
+
ctx.drawImage(img, 0, 0, img.width, img.height);
|
|
1018
|
+
|
|
1019
|
+
let gradient: CanvasGradient;
|
|
1020
|
+
if (options.type === "linear") {
|
|
1021
|
+
const angle = options.angle ?? 0;
|
|
1022
|
+
const radians = (angle * Math.PI) / 180;
|
|
1023
|
+
const x1 = img.width / 2 - (Math.cos(radians) * img.width) / 2;
|
|
1024
|
+
const y1 = img.height / 2 - (Math.sin(radians) * img.height) / 2;
|
|
1025
|
+
const x2 = img.width / 2 + (Math.cos(radians) * img.width) / 2;
|
|
1026
|
+
const y2 = img.height / 2 + (Math.sin(radians) * img.height) / 2;
|
|
1027
|
+
gradient = ctx.createLinearGradient(x1, y1, x2, y2);
|
|
1028
|
+
} else if (options.type === "radial") {
|
|
1029
|
+
gradient = ctx.createRadialGradient(
|
|
1030
|
+
img.width / 2, img.height / 2, 0, img.width / 2, img.height / 2, Math.max(img.width, img.height)
|
|
1031
|
+
);
|
|
1032
|
+
} else {
|
|
1033
|
+
gradient = ctx.createConicGradient(Math.PI, img.width / 2, img.height / 2);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
options.colors.forEach(({ stop, color }) => gradient.addColorStop(stop, color));
|
|
1037
|
+
ctx.fillStyle = gradient;
|
|
1038
|
+
|
|
1039
|
+
ctx.globalCompositeOperation = options.blendMode ?? "multiply";
|
|
1040
|
+
ctx.fillRect(0, 0, img.width, img.height);
|
|
1041
|
+
|
|
1042
|
+
if (options.maskSource) {
|
|
1043
|
+
const mask = await loadImage(options.maskSource);
|
|
1044
|
+
ctx.globalCompositeOperation = "destination-in";
|
|
1045
|
+
ctx.drawImage(mask, 0, 0, img.width, img.height);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1049
|
+
|
|
1050
|
+
return canvas.toBuffer("image/png");
|
|
1051
|
+
}
|
|
885
1052
|
|
|
886
1053
|
async animate(
|
|
887
1054
|
frames: Frame[],
|
|
@@ -967,7 +1134,7 @@ async animate(
|
|
|
967
1134
|
}
|
|
968
1135
|
|
|
969
1136
|
if (frame.pattern) {
|
|
970
|
-
const patternImage = await loadImage(frame.pattern.
|
|
1137
|
+
const patternImage = await loadImage(frame.pattern.source);
|
|
971
1138
|
const pattern = ctx.createPattern(patternImage, frame.pattern.repeat || 'repeat');
|
|
972
1139
|
fillStyle = pattern;
|
|
973
1140
|
}
|
|
@@ -981,8 +1148,8 @@ async animate(
|
|
|
981
1148
|
ctx.fillRect(0, 0, width, height);
|
|
982
1149
|
}
|
|
983
1150
|
|
|
984
|
-
if (frame.
|
|
985
|
-
const image = await loadImage(frame.
|
|
1151
|
+
if (frame.source) {
|
|
1152
|
+
const image = await loadImage(frame.source);
|
|
986
1153
|
ctx.globalCompositeOperation = frame.blendMode || 'source-over';
|
|
987
1154
|
ctx.drawImage(image, 0, 0, width, height);
|
|
988
1155
|
}
|
|
@@ -1019,6 +1186,8 @@ async animate(
|
|
|
1019
1186
|
return options?.gif ? undefined : buffers;
|
|
1020
1187
|
}
|
|
1021
1188
|
|
|
1189
|
+
|
|
1190
|
+
|
|
1022
1191
|
public validHex(hexColor: string): any {
|
|
1023
1192
|
const hexPattern = /^#[0-9a-fA-F]{6}$/;
|
|
1024
1193
|
if (!hexPattern.test(hexColor)) {
|