/// import {type PickerImage} from './picker.shared' import {type Dimensions} from './types' import {blobToDataUri, getDataUriSize} from './util' export async function compressIfNeeded( img: PickerImage, maxSize: number, ): Promise { if (img.size < maxSize) { return img } return await doResize(img.path, { width: img.width, height: img.height, mode: 'stretch', maxSize, }) } export interface DownloadAndResizeOpts { uri: string width: number height: number mode: 'contain' | 'cover' | 'stretch' 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, opts) } export async function shareImageModal(_opts: {uri: string}) { // TODO throw new Error('TODO') } export async function saveImageToMediaLibrary(_opts: {uri: string}) { // TODO throw new Error('TODO') } 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 { width: number height: number mode: 'contain' | 'cover' | 'stretch' maxSize: number } async function doResize( dataUri: string, opts: DoResizeOpts, ): Promise { 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: opts.width, height: opts.height, quality: qualityPercentage / 100, mode: opts.mode, }) 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: opts.width, height: opts.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) await downloadUrl(url, filename) // Firefox requires a small delay setTimeout(() => URL.revokeObjectURL(url), 100) return true } async function downloadUrl(href: string, filename: string) { const a = document.createElement('a') a.href = href a.download = filename a.click() } export async function safeDeleteAsync() { // no-op }