Skip to content

Commit 45bb60f

Browse files
committed
refactor: reorganize canvas utilities and enhance type definitions
1 parent a2c95c6 commit 45bb60f

File tree

4 files changed

+151
-154
lines changed

4 files changed

+151
-154
lines changed

shared/features/thumbnail/canvasFactory.ts

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,7 @@ import type Path from 'path';
33

44
import type NapiRs from '@napi-rs/canvas';
55

6-
/*
7-
import type {
8-
Canvas as NapiCanvas,
9-
Image as NapiImage,
10-
GlobalFonts as NapiGlobalFonts,
11-
} from '@napi-rs/canvas';
12-
*/
13-
14-
export interface CanvasUtils {
15-
createCanvas(width: number, height: number): any;
16-
loadImage(src: string): Promise<any>;
17-
getPath(filename: string): string | URL;
18-
useFont(): void;
19-
saveToImage(canvas: HTMLCanvasElement | NapiRs.Canvas): Promise<Uint8Array>;
20-
noteBlockImage: Promise<any> | any;
21-
DrawingCanvas: any;
22-
RenderedImage: any;
23-
}
6+
import { CanvasUtils } from './types';
247

258
let canvasUtils: CanvasUtils;
269

@@ -39,7 +22,7 @@ if (typeof document === 'undefined') {
3922
const workingDir = process.cwd();
4023
const fullPath = path.join(workingDir, filename.split('/').join(path.sep));
4124

42-
return 'file://' + fullPath;
25+
return fullPath;
4326
};
4427

4528
const saveToImage = (canvas: NapiRs.Canvas) => canvas.encode('png');
@@ -48,26 +31,15 @@ if (typeof document === 'undefined') {
4831
const path = getPath('assets/fonts/Lato-Regular.ttf').toString();
4932
console.log('Font path: ', path);
5033

51-
// ensure the file exists
52-
Bun.file(path)
53-
.exists()
54-
.then((exists) => {
55-
if (exists) {
56-
console.log('Font file exists');
57-
} else {
58-
console.error('Font file does not exist at path: ', path);
59-
}
60-
});
61-
6234
GlobalFonts.registerFromPath(path, 'Lato');
6335
};
6436

6537
let noteBlockImage: Promise<any>;
6638

6739
try {
68-
noteBlockImage = nodeLoadImage(
69-
new URL(getPath('assets/img/note-block-grayscale.png')),
70-
);
40+
const path = getPath('assets/img/note-block-grayscale.png');
41+
42+
noteBlockImage = nodeLoadImage(path);
7143
} catch (error) {
7244
console.error('Error loading image: ', error);
7345
noteBlockImage = Promise.reject(error);
@@ -113,7 +85,9 @@ if (typeof document === 'undefined') {
11385
});
11486
};
11587

116-
const noteBlockImage = loadImage(getPath('/img/note-block-grayscale.png'));
88+
const noteBlockImagePath = getPath('/img/note-block-grayscale.png');
89+
90+
const noteBlockImage = loadImage(noteBlockImagePath);
11791

11892
canvasUtils = {
11993
createCanvas,

shared/features/thumbnail/index.ts

Lines changed: 11 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,16 @@
11
import {
2-
DrawingCanvas,
3-
RenderedImage,
42
createCanvas,
53
noteBlockImage,
64
saveToImage,
5+
useFont,
76
} from './canvasFactory';
8-
import { NoteQuadTree } from '../song/notes';
7+
import { Canvas, DrawParams } from './types';
8+
import { getKeyText, instrumentColors, isDarkColor, tintImage } from './utils';
99

1010
export { bgColorsArray } from './colors';
11+
useFont();
1112

12-
interface DrawParams {
13-
notes: NoteQuadTree;
14-
startTick: number;
15-
startLayer: number;
16-
zoomLevel: number;
17-
backgroundColor: string;
18-
canvasWidth?: number;
19-
canvasHeight?: number;
20-
imgWidth: number;
21-
imgHeight: number;
22-
}
23-
24-
type Canvas = typeof DrawingCanvas;
25-
type Image = typeof RenderedImage;
26-
27-
const instrumentColors = [
28-
'#1964ac',
29-
'#3c8e48',
30-
'#be6b6b',
31-
'#bebe19',
32-
'#9d5a98',
33-
'#572b21',
34-
'#bec65c',
35-
'#be19be',
36-
'#52908d',
37-
'#bebebe',
38-
'#1991be',
39-
'#be2328',
40-
'#be5728',
41-
'#19be19',
42-
'#be1957',
43-
'#575757',
44-
];
45-
46-
const tintedImages: Record<string, Canvas> = {};
47-
48-
// Function to apply tint to an image
49-
function tintImage(image: Image, color: string): Canvas {
50-
if (tintedImages[color]) {
51-
return tintedImages[color];
52-
}
53-
54-
const canvas = createCanvas(image.width, image.height);
55-
const ctx = canvas.getContext('2d');
56-
57-
if (!ctx) {
58-
throw new Error('Could not get canvas context');
59-
}
60-
61-
// Fill background with the color
62-
ctx.fillStyle = color;
63-
ctx.fillRect(0, 0, canvas.width, canvas.height);
64-
65-
// Apply the note block texture to the color
66-
ctx.globalCompositeOperation = 'hard-light';
67-
ctx.globalAlpha = 0.67;
68-
ctx.drawImage(image, 0, 0);
69-
70-
// Reset canvas settings
71-
ctx.globalCompositeOperation = 'source-over';
72-
ctx.globalAlpha = 1;
73-
74-
tintedImages[color] = canvas;
75-
76-
return canvas;
77-
}
78-
79-
// Function to convert key number to key text
80-
function getKeyText(key: number): string {
81-
const octaves = Math.floor((key + 9) / 12);
82-
83-
const notes = [
84-
'C',
85-
'C#',
86-
'D',
87-
'D#',
88-
'E',
89-
'F',
90-
'F#',
91-
'G',
92-
'G#',
93-
'A',
94-
'A#',
95-
'B',
96-
];
97-
98-
const note = notes[(key + 9) % 12];
99-
100-
return `${note}${octaves}`;
101-
}
102-
103-
function getLuma(color: string): number {
104-
// source: https://stackoverflow.com/a/12043228/9045426
105-
106-
const c = color?.substring(1) || ''; // strip #
107-
const rgb = parseInt(c, 16); // convert rrggbb to decimal
108-
const r = (rgb >> 16) & 0xff; // extract red
109-
const g = (rgb >> 8) & 0xff; // extract green
110-
const b = (rgb >> 0) & 0xff; // extract blue
111-
112-
const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709
113-
114-
return luma;
115-
}
116-
117-
function isDarkColor(color: string, threshold = 40): boolean {
118-
return getLuma(color) < threshold;
119-
}
120-
121-
export async function swap(src: Canvas, dst: Canvas) {
13+
export const swap = async (src: Canvas, dst: Canvas) => {
12214
/**
12315
* Run a `drawFunction` that returns a canvas and draw it to the passed `canvas`.
12416
*
@@ -127,7 +19,6 @@ export async function swap(src: Canvas, dst: Canvas) {
12719
*
12820
* @returns Nothing
12921
*/
130-
13122
// Get canvas context
13223
const ctx = dst.getContext('2d');
13324

@@ -137,9 +28,9 @@ export async function swap(src: Canvas, dst: Canvas) {
13728

13829
// Swap the canvas
13930
ctx.drawImage(src, 0, 0);
140-
}
31+
};
14132

142-
export async function drawNotesOffscreen({
33+
export const drawNotesOffscreen = async ({
14334
notes,
14435
startTick,
14536
startLayer,
@@ -149,7 +40,7 @@ export async function drawNotesOffscreen({
14940
//canvasHeight,
15041
imgWidth = 1280,
15142
imgHeight = 768,
152-
}: DrawParams) {
43+
}: DrawParams) => {
15344
// Create new offscreen canvas
15445
const canvas = createCanvas(imgWidth, imgHeight);
15546
const ctx = canvas.getContext('2d');
@@ -235,9 +126,9 @@ export async function drawNotesOffscreen({
235126
});
236127

237128
return canvas;
238-
}
129+
};
239130

240-
export async function drawToImage(params: DrawParams): Promise<Buffer> {
131+
export const drawToImage = async (params: DrawParams): Promise<Buffer> => {
241132
let canvas;
242133
const { imgWidth, imgHeight } = params;
243134

@@ -251,4 +142,4 @@ export async function drawToImage(params: DrawParams): Promise<Buffer> {
251142
// Convert to Buffer
252143
const buffer = Buffer.from(byteArray);
253144
return buffer;
254-
}
145+
};

shared/features/thumbnail/types.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type NapiRs from '@napi-rs/canvas';
2+
3+
import { DrawingCanvas, RenderedImage } from './canvasFactory';
4+
import { NoteQuadTree } from '../song/notes';
5+
6+
export interface DrawParams {
7+
notes: NoteQuadTree;
8+
startTick: number;
9+
startLayer: number;
10+
zoomLevel: number;
11+
backgroundColor: string;
12+
canvasWidth?: number;
13+
canvasHeight?: number;
14+
imgWidth: number;
15+
imgHeight: number;
16+
}
17+
18+
export type Canvas = typeof DrawingCanvas;
19+
20+
export type Image = typeof RenderedImage; /*
21+
import type {
22+
Canvas as NapiCanvas,
23+
Image as NapiImage,
24+
GlobalFonts as NapiGlobalFonts,
25+
} from '@napi-rs/canvas';
26+
*/
27+
28+
export interface CanvasUtils {
29+
createCanvas(width: number, height: number): any;
30+
loadImage(src: string): Promise<any>;
31+
getPath(filename: string): string | URL;
32+
useFont(): void;
33+
saveToImage(canvas: HTMLCanvasElement | NapiRs.Canvas): Promise<Uint8Array>;
34+
noteBlockImage: Promise<any> | any;
35+
DrawingCanvas: any;
36+
RenderedImage: any;
37+
}

shared/features/thumbnail/utils.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { createCanvas } from './canvasFactory';
2+
import { Canvas, Image } from './types';
3+
4+
export const instrumentColors = [
5+
'#1964ac',
6+
'#3c8e48',
7+
'#be6b6b',
8+
'#bebe19',
9+
'#9d5a98',
10+
'#572b21',
11+
'#bec65c',
12+
'#be19be',
13+
'#52908d',
14+
'#bebebe',
15+
'#1991be',
16+
'#be2328',
17+
'#be5728',
18+
'#19be19',
19+
'#be1957',
20+
'#575757',
21+
];
22+
23+
const tintedImages: Record<string, Canvas> = {};
24+
25+
// Function to apply tint to an image
26+
export const tintImage = (image: Image, color: string): Canvas => {
27+
if (tintedImages[color]) {
28+
return tintedImages[color];
29+
}
30+
31+
const canvas = createCanvas(image.width, image.height);
32+
const ctx = canvas.getContext('2d');
33+
34+
if (!ctx) {
35+
throw new Error('Could not get canvas context');
36+
}
37+
38+
// Fill background with the color
39+
ctx.fillStyle = color;
40+
ctx.fillRect(0, 0, canvas.width, canvas.height);
41+
42+
// Apply the note block texture to the color
43+
ctx.globalCompositeOperation = 'hard-light';
44+
ctx.globalAlpha = 0.67;
45+
ctx.drawImage(image, 0, 0);
46+
47+
// Reset canvas settings
48+
ctx.globalCompositeOperation = 'source-over';
49+
ctx.globalAlpha = 1;
50+
51+
tintedImages[color] = canvas;
52+
53+
return canvas;
54+
};
55+
56+
// Function to convert key number to key text
57+
export const getKeyText = (key: number): string => {
58+
const octaves = Math.floor((key + 9) / 12);
59+
60+
const notes = [
61+
'C',
62+
'C#',
63+
'D',
64+
'D#',
65+
'E',
66+
'F',
67+
'F#',
68+
'G',
69+
'G#',
70+
'A',
71+
'A#',
72+
'B',
73+
];
74+
75+
const note = notes[(key + 9) % 12];
76+
77+
return `${note}${octaves}`;
78+
};
79+
80+
const getLuma = (color: string): number => {
81+
// source: https://stackoverflow.com/a/12043228/9045426
82+
const c = color?.substring(1) || ''; // strip #
83+
const rgb = parseInt(c, 16); // convert rrggbb to decimal
84+
const r = (rgb >> 16) & 255; // extract red
85+
const g = (rgb >> 8) & 255; // extract green
86+
const b = (rgb >> 0) & 255; // extract blue
87+
88+
const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709
89+
90+
return luma;
91+
};
92+
93+
export const isDarkColor = (color: string, threshold = 40): boolean => {
94+
return getLuma(color) < threshold;
95+
};

0 commit comments

Comments
 (0)