your personal website on atproto - mirror
blento.app
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 const isSmallEnough = file.size <= maxSize;
33
34 if (width > maxDimension || height > maxDimension) {
35 if (width > height) {
36 height = Math.round((maxDimension / width) * height);
37 width = maxDimension;
38 } else {
39 width = Math.round((maxDimension / height) * width);
40 height = maxDimension;
41 }
42 }
43
44 // Create a canvas to draw the image
45 const canvas = document.createElement('canvas');
46 canvas.width = width;
47 canvas.height = height;
48 const ctx = canvas.getContext('2d');
49 if (!ctx) return reject(new Error('Failed to get canvas context.'));
50 ctx.drawImage(img, 0, 0, width, height);
51
52 // Use WebP if supported, fall back to JPEG (Safari doesn't support WebP encoding)
53 const supportsWebP = canvas.toDataURL('image/webp').startsWith('data:image/webp');
54 const mimeType = supportsWebP ? 'image/webp' : 'image/jpeg';
55 let quality = 0.9;
56
57 function attemptCompression() {
58 canvas.toBlob(
59 (blob) => {
60 if (!blob) {
61 return reject(new Error('Compression failed.'));
62 }
63 if (isSmallEnough || blob.size <= maxSize || quality < 0.3) {
64 resolve({
65 blob,
66 aspectRatio: {
67 width,
68 height
69 }
70 });
71 } else {
72 quality -= 0.1;
73 attemptCompression();
74 }
75 },
76 mimeType,
77 quality
78 );
79 }
80
81 attemptCompression();
82 };
83
84 img.onerror = (err) => reject(err);
85 });
86}
87
88export async function checkAndUploadImage(
89 recordWithImage: Record<string, any>,
90 key: string = 'image',
91 // e.g. /api/image-proxy?url=
92 imageProxy?: string
93) {
94 if (!recordWithImage[key]) return;
95
96 // Already uploaded as blob
97 if (typeof recordWithImage[key] === 'object' && recordWithImage[key].$type === 'blob') {
98 return;
99 }
100
101 if (typeof recordWithImage[key] === 'string' && imageProxy) {
102 const proxyUrl = imageProxy + encodeURIComponent(recordWithImage[key]);
103 const response = await fetch(proxyUrl);
104 if (!response.ok) {
105 throw Error('failed to get image from image proxy');
106 }
107
108 const blob = await response.blob();
109 const compressedBlob = await compressImage(blob);
110
111 recordWithImage[key] = await uploadBlob({ blob: compressedBlob.blob });
112
113 return;
114 }
115
116 if (recordWithImage[key]?.blob) {
117 if (recordWithImage[key].objectUrl) {
118 URL.revokeObjectURL(recordWithImage[key].objectUrl);
119 }
120 const compressedBlob = await compressImage(recordWithImage[key].blob);
121 recordWithImage[key] = await uploadBlob({ blob: compressedBlob.blob });
122 }
123}
124
125export function getImageFromRecord(
126 recordWithImage: Record<string, any> | undefined,
127 did: string,
128 key: string = 'image'
129): string | undefined {
130 if (!recordWithImage?.[key]) return;
131
132 if (typeof recordWithImage[key] === 'object' && recordWithImage[key].$type === 'blob') {
133 return getCDNImageBlobUrl({ did, blob: recordWithImage[key] });
134 }
135
136 if (recordWithImage[key].objectUrl) return recordWithImage[key].objectUrl;
137
138 if (recordWithImage[key].blob) {
139 recordWithImage[key].objectUrl = URL.createObjectURL(recordWithImage[key].blob);
140 return recordWithImage[key].objectUrl;
141 }
142
143 return recordWithImage[key];
144}