diff options
Diffstat (limited to 'src/lib/media/manip.web.ts')
-rw-r--r-- | src/lib/media/manip.web.ts | 192 |
1 files changed, 114 insertions, 78 deletions
diff --git a/src/lib/media/manip.web.ts b/src/lib/media/manip.web.ts index cd0bb3bc9..85f6b6138 100644 --- a/src/lib/media/manip.web.ts +++ b/src/lib/media/manip.web.ts @@ -1,6 +1,40 @@ -// import {Share} from 'react-native' -// import * as Toast from 'view/com/util/Toast' -import {extractDataUriMime, getDataUriSize} from './util' +import {Dimensions} from './types' +import {Image as RNImage} from 'react-native-image-crop-picker' +import {getDataUriSize, blobToDataUri} from './util' +import {POST_IMG_MAX} from 'lib/constants' + +export async function compressAndResizeImageForPost({ + path, + width, + height, +}: { + path: string + width: number + height: number +}): Promise<RNImage> { + // Compression is handled in `doResize` via `quality` + return await doResize(path, { + width, + height, + maxSize: POST_IMG_MAX.size, + mode: 'stretch', + }) +} + +export async function compressIfNeeded( + img: RNImage, + maxSize: number, +): Promise<RNImage> { + 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 @@ -11,14 +45,6 @@ export interface DownloadAndResizeOpts { timeout: number } -export interface Image { - path: string - mime: string - size: number - width: number - height: number -} - export async function downloadAndResize(opts: DownloadAndResizeOpts) { const controller = new AbortController() const to = setTimeout(() => controller.abort(), opts.timeout || 5e3) @@ -27,58 +53,7 @@ export async function downloadAndResize(opts: DownloadAndResizeOpts) { clearTimeout(to) const dataUri = await blobToDataUri(resBody) - return await resize(dataUri, opts) -} - -export interface ResizeOpts { - width: number - height: number - mode: 'contain' | 'cover' | 'stretch' - maxSize: number -} - -export async function resize( - dataUri: string, - _opts: ResizeOpts, -): Promise<Image> { - const dim = await getImageDim(dataUri) - // TODO -- need to resize - return { - path: dataUri, - mime: extractDataUriMime(dataUri), - size: getDataUriSize(dataUri), - width: dim.width, - height: dim.height, - } -} - -export async function compressIfNeeded( - img: Image, - maxSize: number, -): Promise<Image> { - if (img.size > maxSize) { - // TODO - throw new Error( - "This image is too large and we haven't implemented compression yet -- sorry!", - ) - } - return img -} - -export interface Dim { - width: number - height: number -} -export function scaleDownDimensions(dim: Dim, max: Dim): Dim { - if (dim.width < max.width && dim.height < max.height) { - return dim - } - let wScale = dim.width > max.width ? max.width / dim.width : 1 - let hScale = dim.height > max.height ? max.height / dim.height : 1 - if (wScale < hScale) { - return {width: dim.width * wScale, height: dim.height * wScale} - } - return {width: dim.width * hScale, height: dim.height * hScale} + return await doResize(dataUri, opts) } export async function saveImageModal(_opts: {uri: string}) { @@ -86,11 +61,7 @@ export async function saveImageModal(_opts: {uri: string}) { throw new Error('TODO') } -export async function moveToPremanantPath(path: string) { - return path -} - -export async function getImageDim(path: string): Promise<Dim> { +export async function getImageDim(path: string): Promise<Dimensions> { var img = document.createElement('img') const promise = new Promise((resolve, reject) => { img.onload = resolve @@ -101,17 +72,82 @@ export async function getImageDim(path: string): Promise<Dim> { return {width: img.width, height: img.height} } -function blobToDataUri(blob: Blob): Promise<string> { +// internal methods +// = + +interface DoResizeOpts { + width: number + height: number + mode: 'contain' | 'cover' | 'stretch' + maxSize: number +} + +async function doResize(dataUri: string, opts: DoResizeOpts): Promise<RNImage> { + let newDataUri + + for (let i = 0; i <= 10; i++) { + newDataUri = await createResizedImage(dataUri, { + width: opts.width, + height: opts.height, + quality: 1 - i * 0.1, + mode: opts.mode, + }) + if (getDataUriSize(newDataUri) < opts.maxSize) { + break + } + } + 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<string> { return new Promise((resolve, reject) => { - const reader = new FileReader() - reader.onloadend = () => { - if (typeof reader.result === 'string') { - resolve(reader.result) - } else { - reject(new Error('Failed to read blob')) + 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')) } - } - reader.onerror = reject - reader.readAsDataURL(blob) + + canvas.width = width + canvas.height = height + + 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 + let x = (width - w) / 2 + let y = (height - h) / 2 + + ctx.drawImage(img, x, y, w, h) + resolve(canvas.toDataURL('image/jpeg', quality)) + }) + img.src = dataUri }) } |