@autumnsgrove/gossamer 0.1.0 → 0.2.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/LICENSE +21 -0
- package/dist/animation.js +165 -0
- package/dist/animation.test.js +204 -0
- package/dist/characters.d.ts.map +1 -1
- package/dist/characters.js +176 -0
- package/dist/characters.test.js +115 -0
- package/dist/colors.d.ts +312 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +199 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +79 -1308
- package/dist/index.test.js +92 -0
- package/dist/patterns.d.ts +119 -2
- package/dist/patterns.d.ts.map +1 -1
- package/dist/patterns.js +539 -0
- package/dist/patterns.test.js +223 -0
- package/dist/renderer.d.ts +27 -0
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +362 -0
- package/dist/svelte/GossamerBorder.svelte.d.ts +56 -1
- package/dist/svelte/GossamerBorder.svelte.d.ts.map +1 -0
- package/{src → dist}/svelte/GossamerClouds.svelte +6 -6
- package/dist/svelte/GossamerClouds.svelte.d.ts +31 -1
- package/dist/svelte/GossamerClouds.svelte.d.ts.map +1 -0
- package/dist/svelte/GossamerImage.svelte.d.ts +28 -1
- package/dist/svelte/GossamerImage.svelte.d.ts.map +1 -0
- package/dist/svelte/GossamerOverlay.svelte.d.ts +32 -1
- package/dist/svelte/GossamerOverlay.svelte.d.ts.map +1 -0
- package/dist/svelte/GossamerText.svelte.d.ts +29 -1
- package/dist/svelte/GossamerText.svelte.d.ts.map +1 -0
- package/dist/svelte/index.js +31 -3649
- package/dist/svelte/presets.d.ts +4 -2
- package/dist/svelte/presets.js +161 -0
- package/dist/utils/canvas.js +139 -0
- package/dist/utils/image.js +195 -0
- package/dist/utils/performance.js +205 -0
- package/package.json +20 -15
- package/dist/index.js.map +0 -1
- package/dist/style.css +0 -124
- package/dist/svelte/index.js.map +0 -1
- package/src/animation.test.ts +0 -254
- package/src/animation.ts +0 -243
- package/src/characters.test.ts +0 -148
- package/src/characters.ts +0 -164
- package/src/index.test.ts +0 -115
- package/src/index.ts +0 -203
- package/src/patterns.test.ts +0 -273
- package/src/patterns.ts +0 -316
- package/src/renderer.ts +0 -309
- package/src/svelte/index.ts +0 -75
- package/src/svelte/presets.ts +0 -174
- package/src/utils/canvas.ts +0 -210
- package/src/utils/image.ts +0 -275
- package/src/utils/performance.ts +0 -282
- /package/{src → dist}/svelte/GossamerBorder.svelte +0 -0
- /package/{src → dist}/svelte/GossamerImage.svelte +0 -0
- /package/{src → dist}/svelte/GossamerOverlay.svelte +0 -0
- /package/{src → dist}/svelte/GossamerText.svelte +0 -0
package/src/utils/canvas.ts
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canvas Utilities
|
|
3
|
-
*
|
|
4
|
-
* Helper functions for canvas creation, setup, and manipulation.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Options for canvas creation
|
|
9
|
-
*/
|
|
10
|
-
export interface CanvasOptions {
|
|
11
|
-
/** Canvas width in pixels */
|
|
12
|
-
width?: number;
|
|
13
|
-
/** Canvas height in pixels */
|
|
14
|
-
height?: number;
|
|
15
|
-
/** Whether to use high DPI scaling */
|
|
16
|
-
highDPI?: boolean;
|
|
17
|
-
/** CSS class to add to canvas */
|
|
18
|
-
className?: string;
|
|
19
|
-
/** Inline styles to apply */
|
|
20
|
-
style?: Partial<CSSStyleDeclaration>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Create a canvas element with optimal settings
|
|
25
|
-
*/
|
|
26
|
-
export function createCanvas(options: CanvasOptions = {}): HTMLCanvasElement {
|
|
27
|
-
const { width = 300, height = 150, highDPI = true, className, style } = options;
|
|
28
|
-
|
|
29
|
-
const canvas = document.createElement('canvas');
|
|
30
|
-
|
|
31
|
-
if (highDPI) {
|
|
32
|
-
const dpr = window.devicePixelRatio || 1;
|
|
33
|
-
canvas.width = width * dpr;
|
|
34
|
-
canvas.height = height * dpr;
|
|
35
|
-
canvas.style.width = `${width}px`;
|
|
36
|
-
canvas.style.height = `${height}px`;
|
|
37
|
-
|
|
38
|
-
const ctx = canvas.getContext('2d');
|
|
39
|
-
if (ctx) {
|
|
40
|
-
ctx.scale(dpr, dpr);
|
|
41
|
-
}
|
|
42
|
-
} else {
|
|
43
|
-
canvas.width = width;
|
|
44
|
-
canvas.height = height;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (className) {
|
|
48
|
-
canvas.className = className;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (style) {
|
|
52
|
-
Object.assign(canvas.style, style);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return canvas;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Get the device pixel ratio
|
|
60
|
-
*/
|
|
61
|
-
export function getDevicePixelRatio(): number {
|
|
62
|
-
return typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Resize canvas to match container dimensions
|
|
67
|
-
*/
|
|
68
|
-
export function resizeCanvasToContainer(
|
|
69
|
-
canvas: HTMLCanvasElement,
|
|
70
|
-
container: HTMLElement,
|
|
71
|
-
highDPI: boolean = true
|
|
72
|
-
): { width: number; height: number } {
|
|
73
|
-
const rect = container.getBoundingClientRect();
|
|
74
|
-
const dpr = highDPI ? getDevicePixelRatio() : 1;
|
|
75
|
-
|
|
76
|
-
const width = rect.width;
|
|
77
|
-
const height = rect.height;
|
|
78
|
-
|
|
79
|
-
canvas.width = width * dpr;
|
|
80
|
-
canvas.height = height * dpr;
|
|
81
|
-
canvas.style.width = `${width}px`;
|
|
82
|
-
canvas.style.height = `${height}px`;
|
|
83
|
-
|
|
84
|
-
if (highDPI) {
|
|
85
|
-
const ctx = canvas.getContext('2d');
|
|
86
|
-
if (ctx) {
|
|
87
|
-
ctx.scale(dpr, dpr);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return { width, height };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Create an offscreen canvas for buffer rendering
|
|
96
|
-
*/
|
|
97
|
-
export function createOffscreenCanvas(width: number, height: number): HTMLCanvasElement | OffscreenCanvas {
|
|
98
|
-
if (typeof OffscreenCanvas !== 'undefined') {
|
|
99
|
-
return new OffscreenCanvas(width, height);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Fallback for environments without OffscreenCanvas
|
|
103
|
-
const canvas = document.createElement('canvas');
|
|
104
|
-
canvas.width = width;
|
|
105
|
-
canvas.height = height;
|
|
106
|
-
return canvas;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Clear a canvas
|
|
111
|
-
*/
|
|
112
|
-
export function clearCanvas(
|
|
113
|
-
ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
|
|
114
|
-
width: number,
|
|
115
|
-
height: number,
|
|
116
|
-
backgroundColor?: string
|
|
117
|
-
): void {
|
|
118
|
-
if (backgroundColor) {
|
|
119
|
-
ctx.fillStyle = backgroundColor;
|
|
120
|
-
ctx.fillRect(0, 0, width, height);
|
|
121
|
-
} else {
|
|
122
|
-
ctx.clearRect(0, 0, width, height);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Get image data from a canvas region
|
|
128
|
-
*/
|
|
129
|
-
export function getImageData(
|
|
130
|
-
ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
|
|
131
|
-
x: number = 0,
|
|
132
|
-
y: number = 0,
|
|
133
|
-
width?: number,
|
|
134
|
-
height?: number
|
|
135
|
-
): ImageData {
|
|
136
|
-
const canvas = ctx.canvas;
|
|
137
|
-
const w = width ?? canvas.width;
|
|
138
|
-
const h = height ?? canvas.height;
|
|
139
|
-
|
|
140
|
-
return ctx.getImageData(x, y, w, h);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Set optimal rendering context settings
|
|
145
|
-
*/
|
|
146
|
-
export function optimizeContext(ctx: CanvasRenderingContext2D): void {
|
|
147
|
-
// Disable image smoothing for crisp ASCII rendering
|
|
148
|
-
ctx.imageSmoothingEnabled = false;
|
|
149
|
-
|
|
150
|
-
// Use source-over for standard compositing
|
|
151
|
-
ctx.globalCompositeOperation = 'source-over';
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Set text rendering options for ASCII display
|
|
156
|
-
*/
|
|
157
|
-
export function setupTextRendering(
|
|
158
|
-
ctx: CanvasRenderingContext2D,
|
|
159
|
-
fontSize: number,
|
|
160
|
-
fontFamily: string = 'monospace',
|
|
161
|
-
color: string = '#ffffff'
|
|
162
|
-
): void {
|
|
163
|
-
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
164
|
-
ctx.textBaseline = 'top';
|
|
165
|
-
ctx.textAlign = 'left';
|
|
166
|
-
ctx.fillStyle = color;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Measure text width for a given font configuration
|
|
171
|
-
*/
|
|
172
|
-
export function measureTextWidth(
|
|
173
|
-
ctx: CanvasRenderingContext2D,
|
|
174
|
-
text: string,
|
|
175
|
-
fontSize: number,
|
|
176
|
-
fontFamily: string = 'monospace'
|
|
177
|
-
): number {
|
|
178
|
-
const originalFont = ctx.font;
|
|
179
|
-
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
180
|
-
const metrics = ctx.measureText(text);
|
|
181
|
-
ctx.font = originalFont;
|
|
182
|
-
return metrics.width;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Calculate optimal cell size for a given canvas and desired columns
|
|
187
|
-
*/
|
|
188
|
-
export function calculateCellSize(
|
|
189
|
-
canvasWidth: number,
|
|
190
|
-
canvasHeight: number,
|
|
191
|
-
targetCols: number
|
|
192
|
-
): { cellWidth: number; cellHeight: number; cols: number; rows: number } {
|
|
193
|
-
const cellWidth = Math.floor(canvasWidth / targetCols);
|
|
194
|
-
// Use a typical monospace aspect ratio of ~0.6
|
|
195
|
-
const cellHeight = Math.floor(cellWidth * 1.5);
|
|
196
|
-
const cols = Math.floor(canvasWidth / cellWidth);
|
|
197
|
-
const rows = Math.floor(canvasHeight / cellHeight);
|
|
198
|
-
|
|
199
|
-
return { cellWidth, cellHeight, cols, rows };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Apply a CSS blend mode to canvas compositing
|
|
204
|
-
*/
|
|
205
|
-
export function setBlendMode(
|
|
206
|
-
ctx: CanvasRenderingContext2D,
|
|
207
|
-
mode: 'normal' | 'multiply' | 'screen' | 'overlay' | 'darken' | 'lighten' | 'color-dodge' | 'color-burn' | 'soft-light' | 'hard-light' | 'difference' | 'exclusion'
|
|
208
|
-
): void {
|
|
209
|
-
ctx.globalCompositeOperation = mode === 'normal' ? 'source-over' : mode;
|
|
210
|
-
}
|
package/src/utils/image.ts
DELETED
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image Utilities
|
|
3
|
-
*
|
|
4
|
-
* Image loading, processing, and pixel manipulation for ASCII conversion.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Image loading options
|
|
9
|
-
*/
|
|
10
|
-
export interface ImageLoadOptions {
|
|
11
|
-
/** Cross-origin setting for external images */
|
|
12
|
-
crossOrigin?: 'anonymous' | 'use-credentials' | '';
|
|
13
|
-
/** Maximum width to scale image to */
|
|
14
|
-
maxWidth?: number;
|
|
15
|
-
/** Maximum height to scale image to */
|
|
16
|
-
maxHeight?: number;
|
|
17
|
-
/** Whether to preserve aspect ratio when scaling */
|
|
18
|
-
preserveAspectRatio?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Load an image from a URL
|
|
23
|
-
*/
|
|
24
|
-
export function loadImage(src: string, options: ImageLoadOptions = {}): Promise<HTMLImageElement> {
|
|
25
|
-
return new Promise((resolve, reject) => {
|
|
26
|
-
const img = new Image();
|
|
27
|
-
|
|
28
|
-
if (options.crossOrigin !== undefined) {
|
|
29
|
-
img.crossOrigin = options.crossOrigin;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
img.onload = () => resolve(img);
|
|
33
|
-
img.onerror = () => reject(new Error(`Failed to load image: ${src}`));
|
|
34
|
-
|
|
35
|
-
img.src = src;
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Load and scale an image to fit within bounds
|
|
41
|
-
*/
|
|
42
|
-
export async function loadAndScaleImage(
|
|
43
|
-
src: string,
|
|
44
|
-
maxWidth: number,
|
|
45
|
-
maxHeight: number,
|
|
46
|
-
options: ImageLoadOptions = {}
|
|
47
|
-
): Promise<{ image: HTMLImageElement; width: number; height: number }> {
|
|
48
|
-
const img = await loadImage(src, options);
|
|
49
|
-
|
|
50
|
-
let width = img.naturalWidth;
|
|
51
|
-
let height = img.naturalHeight;
|
|
52
|
-
|
|
53
|
-
// Scale down if needed
|
|
54
|
-
if (width > maxWidth || height > maxHeight) {
|
|
55
|
-
const widthRatio = maxWidth / width;
|
|
56
|
-
const heightRatio = maxHeight / height;
|
|
57
|
-
const scale = Math.min(widthRatio, heightRatio);
|
|
58
|
-
|
|
59
|
-
width = Math.floor(width * scale);
|
|
60
|
-
height = Math.floor(height * scale);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return { image: img, width, height };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Draw an image to a canvas and get its pixel data
|
|
68
|
-
*/
|
|
69
|
-
export function imageToPixelData(
|
|
70
|
-
image: HTMLImageElement | HTMLCanvasElement | ImageBitmap,
|
|
71
|
-
width?: number,
|
|
72
|
-
height?: number
|
|
73
|
-
): ImageData {
|
|
74
|
-
const w = width ?? (image instanceof HTMLImageElement ? image.naturalWidth : image.width);
|
|
75
|
-
const h = height ?? (image instanceof HTMLImageElement ? image.naturalHeight : image.height);
|
|
76
|
-
|
|
77
|
-
const canvas = document.createElement('canvas');
|
|
78
|
-
canvas.width = w;
|
|
79
|
-
canvas.height = h;
|
|
80
|
-
|
|
81
|
-
const ctx = canvas.getContext('2d');
|
|
82
|
-
if (!ctx) {
|
|
83
|
-
throw new Error('Failed to get 2D context');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
ctx.drawImage(image, 0, 0, w, h);
|
|
87
|
-
return ctx.getImageData(0, 0, w, h);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Extract brightness values from image data
|
|
92
|
-
*/
|
|
93
|
-
export function extractBrightness(
|
|
94
|
-
imageData: ImageData,
|
|
95
|
-
brightnessFunction: (r: number, g: number, b: number) => number = (r, g, b) => 0.21 * r + 0.72 * g + 0.07 * b
|
|
96
|
-
): number[] {
|
|
97
|
-
const { data, width, height } = imageData;
|
|
98
|
-
const brightness: number[] = new Array(width * height);
|
|
99
|
-
|
|
100
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
101
|
-
brightness[i / 4] = brightnessFunction(data[i], data[i + 1], data[i + 2]);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return brightness;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Sample image data at cell-based intervals
|
|
109
|
-
*/
|
|
110
|
-
export function sampleImageCells(
|
|
111
|
-
imageData: ImageData,
|
|
112
|
-
cellWidth: number,
|
|
113
|
-
cellHeight: number,
|
|
114
|
-
brightnessFunction: (r: number, g: number, b: number) => number = (r, g, b) => 0.21 * r + 0.72 * g + 0.07 * b
|
|
115
|
-
): { brightness: number; color: string }[][] {
|
|
116
|
-
const { data, width, height } = imageData;
|
|
117
|
-
const cols = Math.ceil(width / cellWidth);
|
|
118
|
-
const rows = Math.ceil(height / cellHeight);
|
|
119
|
-
|
|
120
|
-
const result: { brightness: number; color: string }[][] = [];
|
|
121
|
-
|
|
122
|
-
for (let row = 0; row < rows; row++) {
|
|
123
|
-
result[row] = [];
|
|
124
|
-
|
|
125
|
-
for (let col = 0; col < cols; col++) {
|
|
126
|
-
const cellData = sampleCell(data, width, col * cellWidth, row * cellHeight, cellWidth, cellHeight);
|
|
127
|
-
const brightness = brightnessFunction(cellData.r, cellData.g, cellData.b);
|
|
128
|
-
|
|
129
|
-
result[row][col] = {
|
|
130
|
-
brightness,
|
|
131
|
-
color: `rgb(${Math.round(cellData.r)}, ${Math.round(cellData.g)}, ${Math.round(cellData.b)})`,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return result;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Sample average color from a cell region
|
|
141
|
-
*/
|
|
142
|
-
function sampleCell(
|
|
143
|
-
data: Uint8ClampedArray,
|
|
144
|
-
imageWidth: number,
|
|
145
|
-
startX: number,
|
|
146
|
-
startY: number,
|
|
147
|
-
cellWidth: number,
|
|
148
|
-
cellHeight: number
|
|
149
|
-
): { r: number; g: number; b: number; a: number } {
|
|
150
|
-
let totalR = 0;
|
|
151
|
-
let totalG = 0;
|
|
152
|
-
let totalB = 0;
|
|
153
|
-
let totalA = 0;
|
|
154
|
-
let count = 0;
|
|
155
|
-
|
|
156
|
-
for (let y = startY; y < startY + cellHeight; y++) {
|
|
157
|
-
for (let x = startX; x < startX + cellWidth; x++) {
|
|
158
|
-
const px = (y * imageWidth + x) * 4;
|
|
159
|
-
|
|
160
|
-
if (px >= 0 && px + 3 < data.length) {
|
|
161
|
-
totalR += data[px];
|
|
162
|
-
totalG += data[px + 1];
|
|
163
|
-
totalB += data[px + 2];
|
|
164
|
-
totalA += data[px + 3];
|
|
165
|
-
count++;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (count === 0) {
|
|
171
|
-
return { r: 0, g: 0, b: 0, a: 0 };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
r: totalR / count,
|
|
176
|
-
g: totalG / count,
|
|
177
|
-
b: totalB / count,
|
|
178
|
-
a: totalA / count,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Convert RGB to hex color string
|
|
184
|
-
*/
|
|
185
|
-
export function rgbToHex(r: number, g: number, b: number): string {
|
|
186
|
-
const toHex = (n: number) =>
|
|
187
|
-
Math.max(0, Math.min(255, Math.round(n)))
|
|
188
|
-
.toString(16)
|
|
189
|
-
.padStart(2, '0');
|
|
190
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Convert hex color to RGB
|
|
195
|
-
*/
|
|
196
|
-
export function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
|
|
197
|
-
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
198
|
-
return result
|
|
199
|
-
? {
|
|
200
|
-
r: parseInt(result[1], 16),
|
|
201
|
-
g: parseInt(result[2], 16),
|
|
202
|
-
b: parseInt(result[3], 16),
|
|
203
|
-
}
|
|
204
|
-
: null;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Adjust image brightness
|
|
209
|
-
*/
|
|
210
|
-
export function adjustBrightness(imageData: ImageData, amount: number): ImageData {
|
|
211
|
-
const { data, width, height } = imageData;
|
|
212
|
-
const adjusted = new Uint8ClampedArray(data.length);
|
|
213
|
-
|
|
214
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
215
|
-
adjusted[i] = Math.min(255, Math.max(0, data[i] + amount));
|
|
216
|
-
adjusted[i + 1] = Math.min(255, Math.max(0, data[i + 1] + amount));
|
|
217
|
-
adjusted[i + 2] = Math.min(255, Math.max(0, data[i + 2] + amount));
|
|
218
|
-
adjusted[i + 3] = data[i + 3];
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return new ImageData(adjusted, width, height);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Adjust image contrast
|
|
226
|
-
*/
|
|
227
|
-
export function adjustContrast(imageData: ImageData, amount: number): ImageData {
|
|
228
|
-
const { data, width, height } = imageData;
|
|
229
|
-
const adjusted = new Uint8ClampedArray(data.length);
|
|
230
|
-
const factor = (259 * (amount + 255)) / (255 * (259 - amount));
|
|
231
|
-
|
|
232
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
233
|
-
adjusted[i] = Math.min(255, Math.max(0, factor * (data[i] - 128) + 128));
|
|
234
|
-
adjusted[i + 1] = Math.min(255, Math.max(0, factor * (data[i + 1] - 128) + 128));
|
|
235
|
-
adjusted[i + 2] = Math.min(255, Math.max(0, factor * (data[i + 2] - 128) + 128));
|
|
236
|
-
adjusted[i + 3] = data[i + 3];
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return new ImageData(adjusted, width, height);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Invert image colors
|
|
244
|
-
*/
|
|
245
|
-
export function invertColors(imageData: ImageData): ImageData {
|
|
246
|
-
const { data, width, height } = imageData;
|
|
247
|
-
const inverted = new Uint8ClampedArray(data.length);
|
|
248
|
-
|
|
249
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
250
|
-
inverted[i] = 255 - data[i];
|
|
251
|
-
inverted[i + 1] = 255 - data[i + 1];
|
|
252
|
-
inverted[i + 2] = 255 - data[i + 2];
|
|
253
|
-
inverted[i + 3] = data[i + 3];
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return new ImageData(inverted, width, height);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Convert image to grayscale
|
|
261
|
-
*/
|
|
262
|
-
export function toGrayscale(imageData: ImageData): ImageData {
|
|
263
|
-
const { data, width, height } = imageData;
|
|
264
|
-
const grayscale = new Uint8ClampedArray(data.length);
|
|
265
|
-
|
|
266
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
267
|
-
const gray = 0.21 * data[i] + 0.72 * data[i + 1] + 0.07 * data[i + 2];
|
|
268
|
-
grayscale[i] = gray;
|
|
269
|
-
grayscale[i + 1] = gray;
|
|
270
|
-
grayscale[i + 2] = gray;
|
|
271
|
-
grayscale[i + 3] = data[i + 3];
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return new ImageData(grayscale, width, height);
|
|
275
|
-
}
|