your personal website on atproto - mirror blento.app
at update-docs 152 lines 3.8 kB view raw
1import { getCDNImageBlobUrl, uploadBlob } from './methods'; 2 3export function compressImage( 4 file: File | Blob, 5 maxSize: number = 900 * 1024, 6 maxDimension: number = 2048 7): Promise<{ 8 blob: Blob; 9 aspectRatio: { 10 width: number; 11 height: number; 12 }; 13}> { 14 return new Promise((resolve, reject) => { 15 const img = new Image(); 16 const reader = new FileReader(); 17 18 reader.onload = (e) => { 19 if (!e.target?.result) { 20 return reject(new Error('Failed to read file.')); 21 } 22 img.src = e.target.result as string; 23 }; 24 25 reader.onerror = (err) => reject(err); 26 reader.readAsDataURL(file); 27 28 img.onload = () => { 29 let width = img.width; 30 let height = img.height; 31 32 // If image is already small enough, return original 33 if (file.size <= maxSize) { 34 console.log('skipping compression+resizing, already small enough'); 35 return resolve({ 36 blob: file, 37 aspectRatio: { 38 width, 39 height 40 } 41 }); 42 } 43 44 if (width > maxDimension || height > maxDimension) { 45 if (width > height) { 46 height = Math.round((maxDimension / width) * height); 47 width = maxDimension; 48 } else { 49 width = Math.round((maxDimension / height) * width); 50 height = maxDimension; 51 } 52 } 53 54 // Create a canvas to draw the image 55 const canvas = document.createElement('canvas'); 56 canvas.width = width; 57 canvas.height = height; 58 const ctx = canvas.getContext('2d'); 59 if (!ctx) return reject(new Error('Failed to get canvas context.')); 60 ctx.drawImage(img, 0, 0, width, height); 61 62 // Use WebP for both compression and transparency support 63 let quality = 0.9; 64 65 function attemptCompression() { 66 canvas.toBlob( 67 (blob) => { 68 if (!blob) { 69 return reject(new Error('Compression failed.')); 70 } 71 if (blob.size <= maxSize || quality < 0.3) { 72 resolve({ 73 blob, 74 aspectRatio: { 75 width, 76 height 77 } 78 }); 79 } else { 80 quality -= 0.1; 81 attemptCompression(); 82 } 83 }, 84 'image/webp', 85 quality 86 ); 87 } 88 89 attemptCompression(); 90 }; 91 92 img.onerror = (err) => reject(err); 93 }); 94} 95 96export async function checkAndUploadImage( 97 recordWithImage: Record<string, any>, 98 key: string = 'image', 99 // e.g. /api/image-proxy?url= 100 imageProxy?: string 101) { 102 if (!recordWithImage[key]) return; 103 104 // Already uploaded as blob 105 if (typeof recordWithImage[key] === 'object' && recordWithImage[key].$type === 'blob') { 106 return; 107 } 108 109 if (typeof recordWithImage[key] === 'string' && imageProxy) { 110 const proxyUrl = imageProxy + encodeURIComponent(recordWithImage[key]); 111 const response = await fetch(proxyUrl); 112 if (!response.ok) { 113 throw Error('failed to get image from image proxy'); 114 } 115 116 const blob = await response.blob(); 117 const compressedBlob = await compressImage(blob); 118 119 recordWithImage[key] = await uploadBlob({ blob: compressedBlob.blob }); 120 121 return; 122 } 123 124 if (recordWithImage[key]?.blob) { 125 if (recordWithImage[key].objectUrl) { 126 URL.revokeObjectURL(recordWithImage[key].objectUrl); 127 } 128 const compressedBlob = await compressImage(recordWithImage[key].blob); 129 recordWithImage[key] = await uploadBlob({ blob: compressedBlob.blob }); 130 } 131} 132 133export function getImageFromRecord( 134 recordWithImage: Record<string, any> | undefined, 135 did: string, 136 key: string = 'image' 137): string | undefined { 138 if (!recordWithImage?.[key]) return; 139 140 if (typeof recordWithImage[key] === 'object' && recordWithImage[key].$type === 'blob') { 141 return getCDNImageBlobUrl({ did, blob: recordWithImage[key] }); 142 } 143 144 if (recordWithImage[key].objectUrl) return recordWithImage[key].objectUrl; 145 146 if (recordWithImage[key].blob) { 147 recordWithImage[key].objectUrl = URL.createObjectURL(recordWithImage[key].blob); 148 return recordWithImage[key].objectUrl; 149 } 150 151 return recordWithImage[key]; 152}