import {type PickerImage} from './picker.shared' import {type Dimensions} from './types' import { blobToDataUri, convertCdnPreset, getDataUriSize, getResizedDimensions, } from './util' export async function compressIfNeeded( img: PickerImage, {maxDimension, maxSize}: {maxDimension: number; maxSize: number}, ): Promise { if (img.size < maxSize) { return img } return await doResize(img.path, { maxDimension, maxSize, }) } export interface DownloadAndResizeOpts { uri: string maxDimension: number maxSize: number timeout: number } export async function downloadAndResize(opts: DownloadAndResizeOpts) { const controller = new AbortController() const to = setTimeout(() => controller.abort(), opts.timeout || 5e3) const res = await fetch(opts.uri) const resBody = await res.blob() clearTimeout(to) const dataUri = await blobToDataUri(resBody) return await doResize(dataUri, { maxDimension: opts.maxDimension, maxSize: opts.maxSize, }) } export async function shareImageModal(_opts: {uri: string}) { // TODO throw new Error('TODO') } /** * Saves an image to the user's device. Uses the CDN's `download` preset * which uses the JPEG version with the Content-Disposition header set to * `attachment; filename=`. On native this saves to the media library; * on web it triggers a browser download. */ export async function saveImageToMediaLibrary({uri}: {uri: string}) { const downloadUri = convertCdnPreset(uri, 'download') const segments = downloadUri.split('/') const filename = `bluesky-${segments.at(-1)}.jpg` downloadUrl(downloadUri, filename) } export async function getImageDim(path: string): Promise { var img = document.createElement('img') const promise = new Promise((resolve, reject) => { img.onload = resolve img.onerror = reject }) img.src = path await promise return {width: img.width, height: img.height} } // internal methods // = interface DoResizeOpts { maxDimension: number maxSize: number } async function doResize( dataUri: string, opts: DoResizeOpts, ): Promise { const sourceDims = await getImageDim(dataUri) const newDimensions = getResizedDimensions(sourceDims, opts.maxDimension) let newDataUri let minQualityPercentage = 0 let maxQualityPercentage = 101 //exclusive while (maxQualityPercentage - minQualityPercentage > 1) { const qualityPercentage = Math.round( (maxQualityPercentage + minQualityPercentage) / 2, ) const tempDataUri = await createResizedImage(dataUri, { width: newDimensions.width, height: newDimensions.height, quality: qualityPercentage / 100, mode: 'contain', }) if (getDataUriSize(tempDataUri) < opts.maxSize) { minQualityPercentage = qualityPercentage newDataUri = tempDataUri } else { maxQualityPercentage = qualityPercentage } } if (!newDataUri) { throw new Error('Failed to compress image') } return { path: newDataUri, mime: 'image/jpeg', size: getDataUriSize(newDataUri), width: newDimensions.width, height: newDimensions.height, } } function createResizedImage( dataUri: string, { width, height, quality, mode, }: { width: number height: number quality: number mode: 'contain' | 'cover' | 'stretch' }, ): Promise { return new Promise((resolve, reject) => { const img = document.createElement('img') img.addEventListener('load', () => { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') if (!ctx) { return reject(new Error('Failed to resize image')) } let scale = 1 if (mode === 'cover') { scale = img.width < img.height ? width / img.width : height / img.height } else if (mode === 'contain') { scale = img.width > img.height ? width / img.width : height / img.height } let w = img.width * scale let h = img.height * scale canvas.width = w canvas.height = h ctx.drawImage(img, 0, 0, w, h) resolve(canvas.toDataURL('image/jpeg', quality)) }) img.addEventListener('error', ev => { reject(ev.error) }) img.src = dataUri }) } export async function saveBytesToDisk( filename: string, bytes: Uint8Array, type: string, ) { const blob = new Blob([bytes], {type}) const url = URL.createObjectURL(blob) downloadUrl(url, filename) // Firefox requires a small delay setTimeout(() => URL.revokeObjectURL(url), 100) return true } function downloadUrl(href: string, filename: string) { const a = document.createElement('a') a.href = href a.download = filename a.style.display = 'none' document.body.appendChild(a) a.click() document.body.removeChild(a) } export async function safeDeleteAsync() { // no-op }