|
1 | 1 | /**
|
2 | 2 | * Create a thumbnail from a File object by drawing it onto an OffscreenCanvas
|
3 | 3 | */
|
4 |
| -export const createThumbnail = (file: File): Promise<string> => { |
| 4 | +export const createThumbnail = ( |
| 5 | + file: File, |
| 6 | + fileSrc?: string, |
| 7 | + mimeType?: string, |
| 8 | +): Promise<string> => { |
5 | 9 | return new Promise((resolve, reject) => {
|
6 |
| - const img = new Image() |
7 |
| - img.src = URL.createObjectURL(file) // Use Object URL directly |
8 |
| - |
9 |
| - img.onload = () => { |
10 |
| - const maxDimension = 280 |
11 |
| - let drawHeight: number, drawWidth: number |
12 |
| - |
13 |
| - // Calculate aspect ratio |
14 |
| - const aspectRatio = img.width / img.height |
15 |
| - |
16 |
| - // Determine dimensions to fit within maxDimension while maintaining aspect ratio |
17 |
| - if (aspectRatio > 1) { |
18 |
| - // Image is wider than tall |
19 |
| - drawWidth = maxDimension |
20 |
| - drawHeight = maxDimension / aspectRatio |
21 |
| - } else { |
22 |
| - // Image is taller than wide, or square |
23 |
| - drawWidth = maxDimension * aspectRatio |
24 |
| - drawHeight = maxDimension |
25 |
| - } |
| 10 | + /** |
| 11 | + * Create DOM element |
| 12 | + * Draw media on offscreen canvas |
| 13 | + * Resolve fn promise with the base64 image url |
| 14 | + * @param media |
| 15 | + * @param maxDimension |
| 16 | + */ |
| 17 | + const _getBase64ImageUrl = async ( |
| 18 | + media: HTMLImageElement | HTMLVideoElement, |
| 19 | + maxDimension = 280, |
| 20 | + ): Promise<string> => { |
| 21 | + return new Promise((_resolve, _reject) => { |
| 22 | + let drawHeight: number, drawWidth: number |
| 23 | + |
| 24 | + // Calculate aspect ratio |
| 25 | + const width = media.width || (media as HTMLVideoElement).videoWidth |
| 26 | + const height = media.height || (media as HTMLVideoElement).videoHeight |
| 27 | + const aspectRatio = width / height |
| 28 | + |
| 29 | + // Determine dimensions to fit within maxDimension while maintaining aspect ratio |
| 30 | + if (aspectRatio > 1) { |
| 31 | + // Image is wider than tall |
| 32 | + drawWidth = maxDimension |
| 33 | + drawHeight = maxDimension / aspectRatio |
| 34 | + } else { |
| 35 | + // Image is taller than wide, or square |
| 36 | + drawWidth = maxDimension * aspectRatio |
| 37 | + drawHeight = maxDimension |
| 38 | + } |
| 39 | + |
| 40 | + // Create an OffscreenCanvas |
| 41 | + const canvas = new OffscreenCanvas(drawWidth, drawHeight) |
| 42 | + const ctx = canvas.getContext('2d') |
| 43 | + |
| 44 | + // Draw the image onto the OffscreenCanvas with calculated dimensions |
| 45 | + ctx.drawImage(media, 0, 0, drawWidth, drawHeight) |
| 46 | + |
| 47 | + // Convert the OffscreenCanvas to a Blob and free up memory |
| 48 | + canvas |
| 49 | + .convertToBlob({ type: 'image/jpeg', quality: 0.25 }) |
| 50 | + .then((blob) => { |
| 51 | + // Release the Object URL |
| 52 | + URL.revokeObjectURL(media.src) |
| 53 | + const reader = new FileReader() |
26 | 54 |
|
27 |
| - const canvas = new OffscreenCanvas(drawWidth, drawHeight) // Create an OffscreenCanvas |
28 |
| - const ctx = canvas.getContext('2d') |
29 |
| - |
30 |
| - // Draw the image onto the OffscreenCanvas with calculated dimensions |
31 |
| - ctx.drawImage(img, 0, 0, drawWidth, drawHeight) |
32 |
| - |
33 |
| - // Convert the OffscreenCanvas to a Blob and free up memory |
34 |
| - canvas |
35 |
| - .convertToBlob({ type: 'image/jpeg', quality: 0.25 }) |
36 |
| - .then((blob) => { |
37 |
| - URL.revokeObjectURL(img.src) // Release the Object URL |
38 |
| - const reader = new FileReader() |
39 |
| - reader.onload = () => resolve(reader.result as string) // Resolve as data URL |
40 |
| - reader.onerror = reject |
41 |
| - reader.readAsDataURL(blob) |
42 |
| - }) |
43 |
| - .catch(reject) |
| 55 | + // Resolve as data URL |
| 56 | + reader.onload = () => { |
| 57 | + _resolve(reader.result as string) |
| 58 | + } |
| 59 | + reader.onerror = _reject |
| 60 | + reader.readAsDataURL(blob) |
| 61 | + }) |
| 62 | + .catch(_reject) |
| 63 | + }) |
| 64 | + } |
| 65 | + |
| 66 | + const fileType = mimeType?.split('/')?.[0] |
| 67 | + const url = fileSrc || URL.createObjectURL(file) |
| 68 | + let media: HTMLImageElement | HTMLVideoElement |
| 69 | + |
| 70 | + if (fileType === 'video') { |
| 71 | + media = document.createElement('video') |
| 72 | + media.src = url |
| 73 | + media.crossOrigin = 'anonymous' |
| 74 | + media.onloadeddata = () => { |
| 75 | + _getBase64ImageUrl(media) |
| 76 | + .then((url) => resolve(url)) |
| 77 | + .catch(reject) |
| 78 | + } |
| 79 | + } else { |
| 80 | + media = new Image() |
| 81 | + media.src = url |
| 82 | + media.onload = () => { |
| 83 | + _getBase64ImageUrl(media) |
| 84 | + .then((url) => resolve(url)) |
| 85 | + .catch(reject) |
| 86 | + } |
44 | 87 | }
|
45 | 88 |
|
46 |
| - img.onerror = (error) => { |
47 |
| - URL.revokeObjectURL(img.src) // Release Object URL on error |
| 89 | + media.onerror = (error) => { |
| 90 | + // Release Object URL on error |
| 91 | + URL.revokeObjectURL(media.src) |
48 | 92 | // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
49 | 93 | reject(error)
|
50 | 94 | }
|
|
0 commit comments