Apply per-use-case image size configs (#10690)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+16
-39
@@ -16,24 +16,21 @@ import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
|
||||
import * as MediaLibrary from 'expo-media-library'
|
||||
import * as Sharing from 'expo-sharing'
|
||||
|
||||
import {POST_IMG_MAX} from '#/lib/constants'
|
||||
import {logger} from '#/logger'
|
||||
import {IS_ANDROID, IS_IOS} from '#/env'
|
||||
import {type PickerImage} from './picker.shared'
|
||||
import {type Dimensions} from './types'
|
||||
import {convertCdnPreset} from './util'
|
||||
import {convertCdnPreset, getResizedDimensions} from './util'
|
||||
|
||||
export async function compressIfNeeded(
|
||||
img: PickerImage,
|
||||
maxSize: number = POST_IMG_MAX.size,
|
||||
{maxDimension, maxSize}: {maxDimension: number; maxSize: number},
|
||||
): Promise<PickerImage> {
|
||||
if (img.size < maxSize) {
|
||||
return img
|
||||
}
|
||||
const resizedImage = await doResize(normalizePath(img.path), {
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
mode: 'stretch',
|
||||
maxDimension,
|
||||
maxSize,
|
||||
})
|
||||
const finalImageMovedPath = await moveToPermanentPath(
|
||||
@@ -49,9 +46,7 @@ export async function compressIfNeeded(
|
||||
|
||||
export interface DownloadAndResizeOpts {
|
||||
uri: string
|
||||
width: number
|
||||
height: number
|
||||
mode: 'contain' | 'cover' | 'stretch'
|
||||
maxDimension: number
|
||||
maxSize: number
|
||||
timeout: number
|
||||
}
|
||||
@@ -67,7 +62,10 @@ export async function downloadAndResize(opts: DownloadAndResizeOpts) {
|
||||
const path = await downloadImage(opts.uri, String(uuid.v4()), opts.timeout)
|
||||
|
||||
try {
|
||||
return await doResize(path, opts)
|
||||
return await doResize(path, {
|
||||
maxDimension: opts.maxDimension,
|
||||
maxSize: opts.maxSize,
|
||||
})
|
||||
} finally {
|
||||
void safeDeleteAsync(path)
|
||||
}
|
||||
@@ -188,9 +186,7 @@ export function getImageDim(path: string): Promise<Dimensions> {
|
||||
// =
|
||||
|
||||
interface DoResizeOpts {
|
||||
width: number
|
||||
height: number
|
||||
mode: 'contain' | 'cover' | 'stretch'
|
||||
maxDimension: number
|
||||
maxSize: number
|
||||
}
|
||||
|
||||
@@ -204,10 +200,13 @@ async function doResize(
|
||||
// Performing an "empty" manipulation lets us get the dimensions of the original image. React Native's Image.getSize()
|
||||
// does not work for local files...
|
||||
const imageRes = await manipulateAsync(localUri, [], {})
|
||||
const newDimensions = getResizedDimensions({
|
||||
width: imageRes.width,
|
||||
height: imageRes.height,
|
||||
})
|
||||
const newDimensions = getResizedDimensions(
|
||||
{
|
||||
width: imageRes.width,
|
||||
height: imageRes.height,
|
||||
},
|
||||
opts.maxDimension,
|
||||
)
|
||||
|
||||
let minQualityPercentage = 0
|
||||
let maxQualityPercentage = 101 // exclusive
|
||||
@@ -388,28 +387,6 @@ async function withTempFile<T>(
|
||||
}
|
||||
}
|
||||
|
||||
export function getResizedDimensions(originalDims: {
|
||||
width: number
|
||||
height: number
|
||||
}) {
|
||||
if (
|
||||
originalDims.width <= POST_IMG_MAX.width &&
|
||||
originalDims.height <= POST_IMG_MAX.height
|
||||
) {
|
||||
return originalDims
|
||||
}
|
||||
|
||||
const ratio = Math.min(
|
||||
POST_IMG_MAX.width / originalDims.width,
|
||||
POST_IMG_MAX.height / originalDims.height,
|
||||
)
|
||||
|
||||
return {
|
||||
width: Math.round(originalDims.width * ratio),
|
||||
height: Math.round(originalDims.height * ratio),
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadImage(uri: string, destName: string, timeout: number) {
|
||||
// Download to a temp path first, then rename with the correct extension
|
||||
// based on the response's mimeType.
|
||||
|
||||
+22
-17
@@ -1,27 +1,28 @@
|
||||
import {type PickerImage} from './picker.shared'
|
||||
import {type Dimensions} from './types'
|
||||
import {blobToDataUri, convertCdnPreset, getDataUriSize} from './util'
|
||||
import {
|
||||
blobToDataUri,
|
||||
convertCdnPreset,
|
||||
getDataUriSize,
|
||||
getResizedDimensions,
|
||||
} from './util'
|
||||
|
||||
export async function compressIfNeeded(
|
||||
img: PickerImage,
|
||||
maxSize: number,
|
||||
{maxDimension, maxSize}: {maxDimension: number; maxSize: number},
|
||||
): Promise<PickerImage> {
|
||||
if (img.size < maxSize) {
|
||||
return img
|
||||
}
|
||||
return await doResize(img.path, {
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
mode: 'stretch',
|
||||
maxDimension,
|
||||
maxSize,
|
||||
})
|
||||
}
|
||||
|
||||
export interface DownloadAndResizeOpts {
|
||||
uri: string
|
||||
width: number
|
||||
height: number
|
||||
mode: 'contain' | 'cover' | 'stretch'
|
||||
maxDimension: number
|
||||
maxSize: number
|
||||
timeout: number
|
||||
}
|
||||
@@ -34,7 +35,10 @@ export async function downloadAndResize(opts: DownloadAndResizeOpts) {
|
||||
clearTimeout(to)
|
||||
|
||||
const dataUri = await blobToDataUri(resBody)
|
||||
return await doResize(dataUri, opts)
|
||||
return await doResize(dataUri, {
|
||||
maxDimension: opts.maxDimension,
|
||||
maxSize: opts.maxSize,
|
||||
})
|
||||
}
|
||||
|
||||
export async function shareImageModal(_opts: {uri: string}) {
|
||||
@@ -70,9 +74,7 @@ export async function getImageDim(path: string): Promise<Dimensions> {
|
||||
// =
|
||||
|
||||
interface DoResizeOpts {
|
||||
width: number
|
||||
height: number
|
||||
mode: 'contain' | 'cover' | 'stretch'
|
||||
maxDimension: number
|
||||
maxSize: number
|
||||
}
|
||||
|
||||
@@ -80,6 +82,9 @@ async function doResize(
|
||||
dataUri: string,
|
||||
opts: DoResizeOpts,
|
||||
): Promise<PickerImage> {
|
||||
const sourceDims = await getImageDim(dataUri)
|
||||
const newDimensions = getResizedDimensions(sourceDims, opts.maxDimension)
|
||||
|
||||
let newDataUri
|
||||
|
||||
let minQualityPercentage = 0
|
||||
@@ -90,10 +95,10 @@ async function doResize(
|
||||
(maxQualityPercentage + minQualityPercentage) / 2,
|
||||
)
|
||||
const tempDataUri = await createResizedImage(dataUri, {
|
||||
width: opts.width,
|
||||
height: opts.height,
|
||||
width: newDimensions.width,
|
||||
height: newDimensions.height,
|
||||
quality: qualityPercentage / 100,
|
||||
mode: opts.mode,
|
||||
mode: 'contain',
|
||||
})
|
||||
|
||||
if (getDataUriSize(tempDataUri) < opts.maxSize) {
|
||||
@@ -111,8 +116,8 @@ async function doResize(
|
||||
path: newDataUri,
|
||||
mime: 'image/jpeg',
|
||||
size: getDataUriSize(newDataUri),
|
||||
width: opts.width,
|
||||
height: opts.height,
|
||||
width: newDimensions.width,
|
||||
height: newDimensions.height,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import ExpoImageCropTool, {
|
||||
type OpenCropperOptions,
|
||||
} from '@bsky.app/expo-image-crop-tool'
|
||||
|
||||
import {IMAGE_SIZE_CONFIG_2K_1MB} from '#/lib/constants'
|
||||
import {compressIfNeeded} from './manip'
|
||||
import {type PickerImage} from './picker.shared'
|
||||
|
||||
@@ -28,13 +29,16 @@ async function getFile() {
|
||||
throw new Error('Failed to get file info')
|
||||
}
|
||||
|
||||
return await compressIfNeeded({
|
||||
path: file,
|
||||
mime: 'image/jpeg',
|
||||
size: fileInfo.size,
|
||||
width: 4288,
|
||||
height: 2848,
|
||||
})
|
||||
return await compressIfNeeded(
|
||||
{
|
||||
path: file,
|
||||
mime: 'image/jpeg',
|
||||
size: fileInfo.size,
|
||||
width: 4288,
|
||||
height: 2848,
|
||||
},
|
||||
IMAGE_SIZE_CONFIG_2K_1MB,
|
||||
)
|
||||
}
|
||||
|
||||
export async function openPicker(): Promise<PickerImage[]> {
|
||||
|
||||
@@ -2,6 +2,31 @@ export function extractDataUriMime(uri: string): string {
|
||||
return uri.substring(uri.indexOf(':') + 1, uri.indexOf(';'))
|
||||
}
|
||||
|
||||
export function getResizedDimensions(
|
||||
originalDims: {
|
||||
width: number
|
||||
height: number
|
||||
},
|
||||
maxDimension: number,
|
||||
) {
|
||||
if (
|
||||
originalDims.width <= maxDimension &&
|
||||
originalDims.height <= maxDimension
|
||||
) {
|
||||
return originalDims
|
||||
}
|
||||
|
||||
const ratio = Math.min(
|
||||
maxDimension / originalDims.width,
|
||||
maxDimension / originalDims.height,
|
||||
)
|
||||
|
||||
return {
|
||||
width: Math.round(originalDims.width * ratio),
|
||||
height: Math.round(originalDims.height * ratio),
|
||||
}
|
||||
}
|
||||
|
||||
// Fairly accurate estimate that is more performant
|
||||
// than decoding and checking length of URI
|
||||
export function getDataUriSize(uri: string): number {
|
||||
|
||||
Reference in New Issue
Block a user