xpict 0.0.1
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 +1 -0
- package/dev-scripts/generate-exports.cjs +57 -0
- package/dev-scripts/index.ts +149 -0
- package/dev-scripts/prepare-package-json.js +31 -0
- package/dist/cjs/actions.js +232 -0
- package/dist/cjs/constants.js +26 -0
- package/dist/cjs/index.js +77 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/utils/clone-image.util.ts.js +12 -0
- package/dist/cjs/utils/color.util.js +21 -0
- package/dist/cjs/utils/create-image.util.js +18 -0
- package/dist/cjs/utils/index.js +14 -0
- package/dist/cjs/utils/open-image.util.js +10 -0
- package/dist/esm/actions.js +214 -0
- package/dist/esm/constants.js +20 -0
- package/dist/esm/index.js +71 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/utils/clone-image.util.ts.js +7 -0
- package/dist/esm/utils/color.util.js +17 -0
- package/dist/esm/utils/create-image.util.js +13 -0
- package/dist/esm/utils/index.js +4 -0
- package/dist/esm/utils/open-image.util.js +5 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/dist/tsconfig.types.tsbuildinfo +1 -0
- package/dist/types/actions.d.ts +77 -0
- package/dist/types/constants.d.ts +19 -0
- package/dist/types/index.d.ts +31 -0
- package/dist/types/utils/clone-image.util.ts.d.ts +4 -0
- package/dist/types/utils/color.util.d.ts +4 -0
- package/dist/types/utils/create-image.util.d.ts +12 -0
- package/dist/types/utils/index.d.ts +4 -0
- package/dist/types/utils/open-image.util.d.ts +3 -0
- package/fonts/Curse Casual.ttf +0 -0
- package/fonts/Poppins-Black.ttf +0 -0
- package/fonts/Poppins-BlackItalic.ttf +0 -0
- package/fonts/Poppins-Bold.ttf +0 -0
- package/fonts/Poppins-BoldItalic.ttf +0 -0
- package/fonts/Poppins-ExtraBold.ttf +0 -0
- package/fonts/Poppins-ExtraBoldItalic.ttf +0 -0
- package/fonts/Poppins-ExtraLight.ttf +0 -0
- package/fonts/Poppins-ExtraLightItalic.ttf +0 -0
- package/fonts/Poppins-Italic.ttf +0 -0
- package/fonts/Poppins-Light.ttf +0 -0
- package/fonts/Poppins-LightItalic.ttf +0 -0
- package/fonts/Poppins-Medium.ttf +0 -0
- package/fonts/Poppins-MediumItalic.ttf +0 -0
- package/fonts/Poppins-Regular.ttf +0 -0
- package/fonts/Poppins-SemiBold.ttf +0 -0
- package/fonts/Poppins-SemiBoldItalic.ttf +0 -0
- package/fonts/Poppins-Thin.ttf +0 -0
- package/fonts/Poppins-ThinItalic.ttf +0 -0
- package/package.json +78 -0
- package/src/actions.ts +390 -0
- package/src/constants.ts +30 -0
- package/src/index.ts +124 -0
- package/src/utils/clone-image.util.ts.ts +11 -0
- package/src/utils/color.util.ts +25 -0
- package/src/utils/create-image.util.ts +34 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/open-image.util.ts +9 -0
- package/tsconfig.cjs.json +7 -0
- package/tsconfig.esm.json +7 -0
- package/tsconfig.json +15 -0
- package/tsconfig.types.json +8 -0
package/src/actions.ts
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { createCanvas, registerFont } from "canvas"
|
|
2
|
+
import sharp from "sharp"
|
|
3
|
+
|
|
4
|
+
import { colors, insertTextDefaultOptions } from "./constants"
|
|
5
|
+
import { Image } from "."
|
|
6
|
+
|
|
7
|
+
function toGrayScale(grayscale: boolean = true) {
|
|
8
|
+
return (image: Image) => image.grayscale(grayscale)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type CropImageOptions = sharp.Region
|
|
12
|
+
|
|
13
|
+
function cropImage(options: CropImageOptions) {
|
|
14
|
+
return (image: Image) => image.extract(options)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type ResizeImageOptions = {
|
|
18
|
+
width: number
|
|
19
|
+
height: number
|
|
20
|
+
fit: keyof sharp.FitEnum
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function resizeImage({
|
|
24
|
+
width,
|
|
25
|
+
height,
|
|
26
|
+
fit = "inside"
|
|
27
|
+
}: ResizeImageOptions) {
|
|
28
|
+
return (image: Image) => image.resize(
|
|
29
|
+
width,
|
|
30
|
+
height,
|
|
31
|
+
{ fit: fit }
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function rotateImage(angle: number) {
|
|
36
|
+
return (image: Image) => image.rotate(angle)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function blurImage(sigma: number = 1) {
|
|
40
|
+
return (image: Image) => image.blur(sigma)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function modulateSaturation(saturation: number) {
|
|
44
|
+
return (image: Image) => image.modulate({ saturation })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function modulateBrightness(brightness: number) {
|
|
48
|
+
return (image: Image) => image.modulate({ brightness })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function invertColors() {
|
|
52
|
+
return (image: Image) => image.negate()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type AddBorderOptions = {
|
|
56
|
+
size: number
|
|
57
|
+
color?: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function addBorder({
|
|
61
|
+
size,
|
|
62
|
+
color = colors.black
|
|
63
|
+
}: AddBorderOptions) {
|
|
64
|
+
return (image: Image) =>
|
|
65
|
+
image.extend({
|
|
66
|
+
top: size,
|
|
67
|
+
bottom: size,
|
|
68
|
+
left: size,
|
|
69
|
+
right: size,
|
|
70
|
+
background: color
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function adjustContrast(contrast: number) {
|
|
75
|
+
return (image: Image) => {
|
|
76
|
+
const factor = (259 * (contrast + 255)) / (255 * (259 - contrast))
|
|
77
|
+
return image.linear(factor, -(128 * factor) + 128)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function modulateOpacity(opacity: number) {
|
|
82
|
+
return (image: Image) => image.flatten({ background: { alpha: opacity } })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function flipImage() {
|
|
86
|
+
return (image: Image) => image.flip()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function flopImage() {
|
|
90
|
+
return (image: Image) => image.flop()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type FontOptions = {
|
|
94
|
+
size: number
|
|
95
|
+
color?: string
|
|
96
|
+
name?: string
|
|
97
|
+
filePath?: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type TextAnchor = "top-left" | "top-center" | "top-right" | "middle-left" | "middle-center" | "middle-right" | "bottom-left" | "bottom-center" | "bottom-right"
|
|
101
|
+
|
|
102
|
+
export type Stroke = {
|
|
103
|
+
fill: string
|
|
104
|
+
width: number
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type InsertTextOptions = {
|
|
108
|
+
text: string
|
|
109
|
+
font: FontOptions
|
|
110
|
+
x: number
|
|
111
|
+
y: number
|
|
112
|
+
backgroundColor?: string
|
|
113
|
+
anchor?: TextAnchor
|
|
114
|
+
stroke?: Stroke
|
|
115
|
+
rotation?: number
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export type AnchorOffsets = Record<TextAnchor, { x: number, y: number }>
|
|
119
|
+
|
|
120
|
+
function insertText({
|
|
121
|
+
text,
|
|
122
|
+
font,
|
|
123
|
+
x,
|
|
124
|
+
y,
|
|
125
|
+
backgroundColor = insertTextDefaultOptions.backgroundColor,
|
|
126
|
+
anchor = insertTextDefaultOptions.anchor as TextAnchor,
|
|
127
|
+
stroke,
|
|
128
|
+
rotation = insertTextDefaultOptions.rotation
|
|
129
|
+
}: InsertTextOptions) {
|
|
130
|
+
return async (image: Image) => {
|
|
131
|
+
const imageMetadata = await image.metadata()
|
|
132
|
+
const width = imageMetadata.width!
|
|
133
|
+
const height = imageMetadata.height!
|
|
134
|
+
|
|
135
|
+
const canvas = createCanvas(width, height)
|
|
136
|
+
const context = canvas.getContext("2d")
|
|
137
|
+
|
|
138
|
+
if (backgroundColor !== "transparent") {
|
|
139
|
+
context.fillStyle = backgroundColor
|
|
140
|
+
context.fillRect(0, 0, width, height)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (font.filePath) {
|
|
144
|
+
registerFont(
|
|
145
|
+
font.filePath,
|
|
146
|
+
{ family: font.name! }
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
context.font = `${font.size}px ${font.name ?? insertTextDefaultOptions.font.name}`
|
|
151
|
+
context.fillStyle = font.color ?? colors.black
|
|
152
|
+
|
|
153
|
+
const textMetrics = context.measureText(text)
|
|
154
|
+
const textWidth = textMetrics.width
|
|
155
|
+
const textHeight = font.size
|
|
156
|
+
|
|
157
|
+
const anchorOffsets: AnchorOffsets = {
|
|
158
|
+
"top-left": {
|
|
159
|
+
x: 0,
|
|
160
|
+
y: 0
|
|
161
|
+
},
|
|
162
|
+
"top-center": {
|
|
163
|
+
x: -textWidth / 2,
|
|
164
|
+
y: 0
|
|
165
|
+
},
|
|
166
|
+
"top-right": {
|
|
167
|
+
x: -textWidth,
|
|
168
|
+
y: 0
|
|
169
|
+
},
|
|
170
|
+
"middle-left": {
|
|
171
|
+
x: 0,
|
|
172
|
+
y: -textHeight / 2
|
|
173
|
+
},
|
|
174
|
+
"middle-center": {
|
|
175
|
+
x: -textWidth / 2,
|
|
176
|
+
y: -textHeight / 2
|
|
177
|
+
},
|
|
178
|
+
"middle-right": {
|
|
179
|
+
x: -textWidth,
|
|
180
|
+
y: -textHeight / 2
|
|
181
|
+
},
|
|
182
|
+
"bottom-left": {
|
|
183
|
+
x: 0,
|
|
184
|
+
y: -textHeight
|
|
185
|
+
},
|
|
186
|
+
"bottom-center": {
|
|
187
|
+
x: -textWidth / 2,
|
|
188
|
+
y: -textHeight
|
|
189
|
+
},
|
|
190
|
+
"bottom-right": {
|
|
191
|
+
x: -textWidth,
|
|
192
|
+
y: -textHeight
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const {
|
|
197
|
+
x: offsetX,
|
|
198
|
+
y: offsetY
|
|
199
|
+
} = anchorOffsets[anchor] || { x: 0, y: 0 }
|
|
200
|
+
|
|
201
|
+
const adjustedX = x + offsetX
|
|
202
|
+
const adjustedY = y + offsetY
|
|
203
|
+
const adjustedRotation = rotation ?? 0
|
|
204
|
+
|
|
205
|
+
context.save()
|
|
206
|
+
context.translate(adjustedX, adjustedY)
|
|
207
|
+
context.rotate((adjustedRotation * Math.PI) / 180)
|
|
208
|
+
|
|
209
|
+
if (stroke) {
|
|
210
|
+
context.strokeStyle = stroke.fill
|
|
211
|
+
context.lineWidth = stroke.width
|
|
212
|
+
context.lineJoin = "round"
|
|
213
|
+
context.strokeText(text, 0, 0)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
context.fillStyle = font.color ?? colors.black
|
|
217
|
+
context.fillText(text, 0, 0)
|
|
218
|
+
|
|
219
|
+
context.restore()
|
|
220
|
+
|
|
221
|
+
const textBuffer = canvas.toBuffer()
|
|
222
|
+
|
|
223
|
+
return image.composite([
|
|
224
|
+
{
|
|
225
|
+
input: textBuffer,
|
|
226
|
+
top: 0,
|
|
227
|
+
left: 0
|
|
228
|
+
}
|
|
229
|
+
])
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export type InsertCircleOptions = {
|
|
234
|
+
x: number
|
|
235
|
+
y: number
|
|
236
|
+
radius: number
|
|
237
|
+
fill: string
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function insertCircle({
|
|
241
|
+
x,
|
|
242
|
+
y,
|
|
243
|
+
radius,
|
|
244
|
+
fill
|
|
245
|
+
}: InsertCircleOptions) {
|
|
246
|
+
return async (image: Image) => {
|
|
247
|
+
const imageMetadata = await image.metadata()
|
|
248
|
+
const width = imageMetadata.width!
|
|
249
|
+
const height = imageMetadata.height!
|
|
250
|
+
|
|
251
|
+
const canvas = createCanvas(width, height)
|
|
252
|
+
const context = canvas.getContext("2d")
|
|
253
|
+
|
|
254
|
+
context.beginPath()
|
|
255
|
+
context.arc(x, y, radius, 0, Math.PI * 2, true)
|
|
256
|
+
context.closePath()
|
|
257
|
+
context.fillStyle = fill
|
|
258
|
+
context.fill()
|
|
259
|
+
|
|
260
|
+
const circleBuffer = canvas.toBuffer()
|
|
261
|
+
|
|
262
|
+
return image.composite([
|
|
263
|
+
{
|
|
264
|
+
input: circleBuffer,
|
|
265
|
+
top: 0,
|
|
266
|
+
left: 0
|
|
267
|
+
}
|
|
268
|
+
])
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export type InsertRectangleOptions = {
|
|
273
|
+
x: number
|
|
274
|
+
y: number
|
|
275
|
+
width: number
|
|
276
|
+
height: number
|
|
277
|
+
fill: string
|
|
278
|
+
borderRadius?: number
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function insertRectangle({
|
|
282
|
+
x,
|
|
283
|
+
y,
|
|
284
|
+
width,
|
|
285
|
+
height,
|
|
286
|
+
fill,
|
|
287
|
+
borderRadius
|
|
288
|
+
}: InsertRectangleOptions) {
|
|
289
|
+
return async (image: Image) => {
|
|
290
|
+
const imageMetadata = await image.metadata()
|
|
291
|
+
const canvasWidth = imageMetadata.width!
|
|
292
|
+
const canvasHeight = imageMetadata.height!
|
|
293
|
+
|
|
294
|
+
const canvas = createCanvas(canvasWidth, canvasHeight)
|
|
295
|
+
const context = canvas.getContext("2d")
|
|
296
|
+
|
|
297
|
+
context.fillStyle = fill
|
|
298
|
+
|
|
299
|
+
if (borderRadius && borderRadius > 0) {
|
|
300
|
+
context.beginPath()
|
|
301
|
+
context.moveTo(x + borderRadius, y)
|
|
302
|
+
context.lineTo(x + width - borderRadius, y)
|
|
303
|
+
context.arcTo(x + width, y, x + width, y + height, borderRadius)
|
|
304
|
+
context.lineTo(x + width, y + height - borderRadius)
|
|
305
|
+
context.arcTo(x + width, y + height, x, y + height, borderRadius)
|
|
306
|
+
context.lineTo(x + borderRadius, y + height)
|
|
307
|
+
context.arcTo(x, y + height, x, y, borderRadius)
|
|
308
|
+
context.lineTo(x, y + borderRadius)
|
|
309
|
+
context.arcTo(x, y, x + width, y, borderRadius)
|
|
310
|
+
context.closePath()
|
|
311
|
+
context.fill()
|
|
312
|
+
} else {
|
|
313
|
+
context.fillRect(x, y, width, height)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const rectangleBuffer = canvas.toBuffer()
|
|
317
|
+
|
|
318
|
+
return image.composite([
|
|
319
|
+
{
|
|
320
|
+
input: rectangleBuffer,
|
|
321
|
+
top: 0,
|
|
322
|
+
left: 0
|
|
323
|
+
}
|
|
324
|
+
])
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export type InsertLineOptions = {
|
|
329
|
+
x1: number
|
|
330
|
+
y1: number
|
|
331
|
+
x2: number
|
|
332
|
+
y2: number
|
|
333
|
+
color: string
|
|
334
|
+
width: number
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function insertLine({
|
|
338
|
+
x1,
|
|
339
|
+
y1,
|
|
340
|
+
x2,
|
|
341
|
+
y2,
|
|
342
|
+
color,
|
|
343
|
+
width
|
|
344
|
+
}: InsertLineOptions) {
|
|
345
|
+
return async (image: Image) => {
|
|
346
|
+
const imageMetadata = await image.metadata()
|
|
347
|
+
const canvasWidth = imageMetadata.width!
|
|
348
|
+
const canvasHeight = imageMetadata.height!
|
|
349
|
+
|
|
350
|
+
const canvas = createCanvas(canvasWidth, canvasHeight)
|
|
351
|
+
const context = canvas.getContext("2d")
|
|
352
|
+
|
|
353
|
+
context.strokeStyle = color
|
|
354
|
+
context.lineWidth = width
|
|
355
|
+
context.beginPath()
|
|
356
|
+
context.moveTo(x1, y1)
|
|
357
|
+
context.lineTo(x2, y2)
|
|
358
|
+
context.stroke()
|
|
359
|
+
|
|
360
|
+
const lineBuffer = canvas.toBuffer()
|
|
361
|
+
|
|
362
|
+
return image.composite([
|
|
363
|
+
{
|
|
364
|
+
input: lineBuffer,
|
|
365
|
+
top: 0,
|
|
366
|
+
left: 0
|
|
367
|
+
}
|
|
368
|
+
])
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export {
|
|
373
|
+
toGrayScale,
|
|
374
|
+
cropImage,
|
|
375
|
+
resizeImage,
|
|
376
|
+
rotateImage,
|
|
377
|
+
blurImage,
|
|
378
|
+
modulateSaturation,
|
|
379
|
+
modulateBrightness,
|
|
380
|
+
invertColors,
|
|
381
|
+
addBorder,
|
|
382
|
+
adjustContrast,
|
|
383
|
+
modulateOpacity,
|
|
384
|
+
flipImage,
|
|
385
|
+
flopImage,
|
|
386
|
+
insertText,
|
|
387
|
+
insertCircle,
|
|
388
|
+
insertRectangle,
|
|
389
|
+
insertLine
|
|
390
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { rgba } from "./utils/color.util"
|
|
2
|
+
|
|
3
|
+
const colors = {
|
|
4
|
+
black: "#000000",
|
|
5
|
+
white: "#FFFFFF"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const defautltTemplateAcitonType = "beforeLayersProccess"
|
|
9
|
+
|
|
10
|
+
const createImageDefaultOptions = {
|
|
11
|
+
chnannels: 4,
|
|
12
|
+
fill: rgba(0, 0, 0, 0),
|
|
13
|
+
format: "png"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const insertTextDefaultOptions = {
|
|
17
|
+
font: {
|
|
18
|
+
name: "sans-serif",
|
|
19
|
+
},
|
|
20
|
+
anchor: "top-left",
|
|
21
|
+
backgroundColor: "transparent",
|
|
22
|
+
rotation: 0
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
colors,
|
|
27
|
+
defautltTemplateAcitonType,
|
|
28
|
+
createImageDefaultOptions,
|
|
29
|
+
insertTextDefaultOptions
|
|
30
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import sharp, { Sharp } from "sharp"
|
|
2
|
+
|
|
3
|
+
import { defautltTemplateAcitonType } from "./constants"
|
|
4
|
+
import cloneImage from "./utils/clone-image.util.ts"
|
|
5
|
+
|
|
6
|
+
export type Image = Sharp
|
|
7
|
+
|
|
8
|
+
export type ImageTemplateLayer = {
|
|
9
|
+
x: number
|
|
10
|
+
y: number
|
|
11
|
+
template: ImageTemplate
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type ImageTemplateActionFunction = (image: Image) => Image | Promise<Image>
|
|
15
|
+
|
|
16
|
+
export type ImageTemplateAction = ImageTemplateActionFunction | {
|
|
17
|
+
type: "beforeLayersProccess" | "afterLayersProccess"
|
|
18
|
+
func: ImageTemplateActionFunction
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type ImageTemplate = {
|
|
22
|
+
image: Image
|
|
23
|
+
actions?: ImageTemplateAction[]
|
|
24
|
+
layers?: ImageTemplateLayer[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ExtractActionsResult = {
|
|
28
|
+
before: ImageTemplateActionFunction[]
|
|
29
|
+
after: ImageTemplateActionFunction[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function extractActions(actions: ImageTemplateAction[] | undefined): ExtractActionsResult {
|
|
33
|
+
const actionsLayersProccess: ImageTemplateActionFunction[] = []
|
|
34
|
+
const actionsAfterLayersProccess: ImageTemplateActionFunction[] = []
|
|
35
|
+
|
|
36
|
+
if (!actions) {
|
|
37
|
+
actions = []
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const action of actions) {
|
|
41
|
+
const actionType = typeof action === "function" ? defautltTemplateAcitonType : action.type
|
|
42
|
+
const actionFunc = typeof action === "function" ? action : action.func
|
|
43
|
+
|
|
44
|
+
if (actionType === "beforeLayersProccess") {
|
|
45
|
+
actionsLayersProccess.push(actionFunc)
|
|
46
|
+
} else {
|
|
47
|
+
actionsAfterLayersProccess.push(actionFunc)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
before: actionsLayersProccess,
|
|
53
|
+
after: actionsAfterLayersProccess
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function processImageTemplate(template: ImageTemplate): Promise<Image> {
|
|
58
|
+
const actions = extractActions(template.actions)
|
|
59
|
+
let image = template.image
|
|
60
|
+
|
|
61
|
+
for (const action of actions.before) {
|
|
62
|
+
const imageResult = await action(image)
|
|
63
|
+
image = await cloneImage(imageResult)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (template.layers) {
|
|
67
|
+
const layerImages: sharp.OverlayOptions[] = []
|
|
68
|
+
|
|
69
|
+
for (const layer of template.layers) {
|
|
70
|
+
const currentImage = await processImageTemplate(layer.template)
|
|
71
|
+
const currentImageBuffer = await currentImage.png().toBuffer()
|
|
72
|
+
|
|
73
|
+
layerImages.push({
|
|
74
|
+
input: currentImageBuffer,
|
|
75
|
+
top: layer.y,
|
|
76
|
+
left: layer.x
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
image = image.composite(layerImages)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const action of actions.after) {
|
|
84
|
+
const imageResult = await action(image)
|
|
85
|
+
image = await cloneImage(imageResult)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return image
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function extendImageTemplate(
|
|
92
|
+
template: ImageTemplate,
|
|
93
|
+
extendTemplate: Omit<ImageTemplate, "image">
|
|
94
|
+
) {
|
|
95
|
+
const actions = []
|
|
96
|
+
const layers = []
|
|
97
|
+
|
|
98
|
+
if (template.layers) {
|
|
99
|
+
layers.push(...template.layers)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (extendTemplate.layers) {
|
|
103
|
+
layers.push(...extendTemplate.layers)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (template.actions) {
|
|
107
|
+
actions.push(...template.actions)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (extendTemplate.actions) {
|
|
111
|
+
actions.push(...extendTemplate.actions)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
image: template.image,
|
|
116
|
+
actions: actions,
|
|
117
|
+
layers: layers
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
processImageTemplate,
|
|
123
|
+
extendImageTemplate
|
|
124
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
function componentToHex(component: number): string {
|
|
2
|
+
const hex = component.toString(16)
|
|
3
|
+
return hex.padStart(2, "0")
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function rgb(red: number, green: number, blue: number): string {
|
|
7
|
+
return `#${componentToHex(red)}${componentToHex(green)}${componentToHex(blue)}`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function rgba(red: number, green: number, blue: number, alpha: number): string {
|
|
11
|
+
const alphaHex = componentToHex(Math.round(alpha * 255))
|
|
12
|
+
return `#${componentToHex(red)}${componentToHex(green)}${componentToHex(blue)}${alphaHex}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function hex(value: number) {
|
|
16
|
+
const hexadecimalValue = value.toString(16)
|
|
17
|
+
const fromatedHexadecimal = `#${hexadecimalValue.padStart(6, "0")}`
|
|
18
|
+
return fromatedHexadecimal
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
rgb,
|
|
23
|
+
rgba,
|
|
24
|
+
hex
|
|
25
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import sharp from "sharp"
|
|
2
|
+
|
|
3
|
+
import { createImageDefaultOptions } from "../constants"
|
|
4
|
+
|
|
5
|
+
export type ImageChannels = 3 | 4
|
|
6
|
+
|
|
7
|
+
export type ImageFormat = "png" | "jpeg" | "webp" | "avif" | "gif"
|
|
8
|
+
|
|
9
|
+
export type CreateImageOptions = {
|
|
10
|
+
width: number
|
|
11
|
+
height: number
|
|
12
|
+
fill?: sharp.Color
|
|
13
|
+
channels?: ImageChannels
|
|
14
|
+
format?: ImageFormat
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function createImage({
|
|
18
|
+
width,
|
|
19
|
+
height,
|
|
20
|
+
fill = createImageDefaultOptions.fill,
|
|
21
|
+
channels = createImageDefaultOptions.chnannels as ImageChannels,
|
|
22
|
+
format = createImageDefaultOptions.format as ImageFormat,
|
|
23
|
+
}: CreateImageOptions) {
|
|
24
|
+
return sharp({
|
|
25
|
+
create: {
|
|
26
|
+
width: width,
|
|
27
|
+
height: height,
|
|
28
|
+
channels: channels,
|
|
29
|
+
background: fill,
|
|
30
|
+
},
|
|
31
|
+
})[format]()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default createImage
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2021",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
"rootDir": "./src",
|
|
7
|
+
"moduleResolution": "node",
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"skipLibCheck": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*.ts"],
|
|
14
|
+
"exclude": ["node_modules"]
|
|
15
|
+
}
|