From 9054de701c91f617b1d4726c43f877d3e2924be5 Mon Sep 17 00:00:00 2001 From: Hailey Date: Sun, 23 Jun 2024 02:16:03 -0700 Subject: [PATCH] remove reizer --- __tests__/lib/images.test.ts | 55 ++++++++++++------------ jest/jestSetup.js | 18 ++++++-- package.json | 1 - src/lib/media/manip.ts | 82 +++++++++++++++++++++++++++++------- yarn.lock | 5 --- 5 files changed, 109 insertions(+), 52 deletions(-) diff --git a/__tests__/lib/images.test.ts b/__tests__/lib/images.test.ts index 595f566c47..dbe801ad0d 100644 --- a/__tests__/lib/images.test.ts +++ b/__tests__/lib/images.test.ts @@ -1,4 +1,5 @@ -import ImageResizer from '@bam.tech/react-native-image-resizer' +import {deleteAsync} from 'expo-file-system' +import {manipulateAsync, SaveFormat} from 'expo-image-manipulator' import RNFetchBlob from 'rn-fetch-blob' import { @@ -10,17 +11,19 @@ describe('downloadAndResize', () => { const errorSpy = jest.spyOn(global.console, 'error') const mockResizedImage = { - path: jest.fn().mockReturnValue('file://resized-image.jpg'), + path: 'file://resized-image.jpg', size: 100, - width: 50, - height: 50, + width: 100, + height: 100, mime: 'image/jpeg', } beforeEach(() => { - const mockedCreateResizedImage = - ImageResizer.createResizedImage as jest.Mock - mockedCreateResizedImage.mockResolvedValue(mockResizedImage) + const mockedCreateResizedImage = manipulateAsync as jest.Mock + mockedCreateResizedImage.mockResolvedValue({ + uri: 'file://resized-image.jpg', + ...mockResizedImage, + }) }) afterEach(() => { @@ -30,7 +33,7 @@ describe('downloadAndResize', () => { it('should return resized image for valid URI and options', async () => { const mockedFetch = RNFetchBlob.fetch as jest.Mock mockedFetch.mockResolvedValueOnce({ - path: jest.fn().mockReturnValue('file://downloaded-image.jpg'), + path: jest.fn().mockReturnValue('file://resized-image.jpg'), info: jest.fn().mockReturnValue({status: 200}), flush: jest.fn(), }) @@ -54,17 +57,15 @@ describe('downloadAndResize', () => { 'GET', 'https://example.com/image.jpg', ) - expect(ImageResizer.createResizedImage).toHaveBeenCalledWith( - 'file://downloaded-image.jpg', - 100, - 100, - 'JPEG', - 100, - undefined, - undefined, - undefined, - {mode: 'cover'}, + expect(manipulateAsync).toHaveBeenCalledWith(expect.anything(), [], { + format: SaveFormat.JPEG, + }) + expect(manipulateAsync).toHaveBeenCalledWith( + expect.anything(), + [{resize: {height: opts.height, width: opts.width}}], + {format: SaveFormat.JPEG, compress: 0.9}, ) + expect(deleteAsync).toHaveBeenCalledWith(expect.anything()) }) it('should return undefined for invalid URI', async () => { @@ -109,17 +110,15 @@ describe('downloadAndResize', () => { 'GET', 'https://example.com/image', ) - expect(ImageResizer.createResizedImage).toHaveBeenCalledWith( - 'file://downloaded-image', - 100, - 100, - 'JPEG', - 100, - undefined, - undefined, - undefined, - {mode: 'cover'}, + expect(manipulateAsync).toHaveBeenCalledWith(expect.anything(), [], { + format: SaveFormat.JPEG, + }) + expect(manipulateAsync).toHaveBeenCalledWith( + expect.anything(), + [{resize: {height: opts.height, width: opts.width}}], + {format: SaveFormat.JPEG, compress: 0.9}, ) + expect(deleteAsync).toHaveBeenCalledWith(expect.anything()) }) it('should return undefined for non-200 response', async () => { diff --git a/jest/jestSetup.js b/jest/jestSetup.js index a6b7c24f69..6145e33809 100644 --- a/jest/jestSetup.js +++ b/jest/jestSetup.js @@ -42,9 +42,21 @@ jest.mock('rn-fetch-blob', () => ({ fetch: jest.fn(), })) -jest.mock('@bam.tech/react-native-image-resizer', () => ({ - createResizedImage: jest.fn(), -})) +jest.mock('expo-image-manipulator', () => { + let SaveFormat + ;(function (SaveFormat) { + SaveFormat.JPEG = 'jpeg' + SaveFormat.PNG = 'png' + SaveFormat.WEBP = 'webp' + })(SaveFormat || (SaveFormat = {})) + + return { + manipulateAsync: jest.fn().mockResolvedValue({ + uri: 'file://resized-image', + }), + SaveFormat, + } +}) jest.mock('@segment/analytics-react-native', () => ({ createClient: () => ({ diff --git a/package.json b/package.json index 5159c70ecc..44326620ad 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,6 @@ }, "dependencies": { "@atproto/api": "0.12.22-next.0", - "@bam.tech/react-native-image-resizer": "^3.0.4", "@braintree/sanitize-url": "^6.0.2", "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet", "@emoji-mart/react": "^1.1.1", diff --git a/src/lib/media/manip.ts b/src/lib/media/manip.ts index 3e647004bb..26a9bf3b2b 100644 --- a/src/lib/media/manip.ts +++ b/src/lib/media/manip.ts @@ -6,17 +6,20 @@ import { copyAsync, deleteAsync, EncodingType, + FileInfo, + getInfoAsync, makeDirectoryAsync, StorageAccessFramework, writeAsStringAsync, } from 'expo-file-system' +import {manipulateAsync, SaveFormat} from 'expo-image-manipulator' import * as MediaLibrary from 'expo-media-library' import * as Sharing from 'expo-sharing' -import ImageResizer from '@bam.tech/react-native-image-resizer' import {Buffer} from 'buffer' import RNFetchBlob from 'rn-fetch-blob' import {logger} from '#/logger' +import {POST_IMG_MAX} from 'lib/constants' import {isAndroid, isIOS} from 'platform/detection' import {Dimensions} from './types' @@ -165,29 +168,56 @@ interface DoResizeOpts { } async function doResize(localUri: string, opts: DoResizeOpts): Promise { + // This is a bit of a hack, but it lets us get the original size of the image. The old image manipulation library + // allowed us to supply a max height/width and it would handle the resizing. With expo-image-manipulator, we have + // to supply the exact size and width that we want to resize to instead. We will calculate that ourselves based on + // the height/width results of this first manipulation + const imageRes = await manipulateAsync(localUri, [], { + format: SaveFormat.JPEG, + }) + + const newDimensions = getResizedDimensions({ + width: imageRes.width, + height: imageRes.height, + }) + + console.log(localUri) + for (let i = 0; i < 9; i++) { - const quality = 100 - i * 10 - const resizeRes = await ImageResizer.createResizedImage( + const quality = 0.9 - 0.1 * i + const resizeRes = await manipulateAsync( localUri, - opts.width, - opts.height, - 'JPEG', - quality, - undefined, - undefined, - undefined, - {mode: opts.mode}, + [{resize: {height: newDimensions.height, width: newDimensions.width}}], + { + format: SaveFormat.JPEG, + compress: quality, + }, ) - if (resizeRes.size < opts.maxSize) { + + const info: FileInfo = await getInfoAsync(resizeRes.uri, { + size: true, + }) + + // I'm not sure this can happen, but if we don't check `.exists`, then the `.size` type is undefined. + if (!info.exists) { + throw new Error( + 'The image manipulation library failed to create a new image.', + ) + } + + // We want to clean up every resize _except_ the final result. We'll clean that one up later when we're finished + // with it + if (info.size < opts.maxSize) { + await deleteAsync(imageRes.uri) return { - path: normalizePath(resizeRes.path), + path: normalizePath(resizeRes.uri), mime: 'image/jpeg', - size: resizeRes.size, + size: info.size, width: resizeRes.width, height: resizeRes.height, } } else { - safeDeleteAsync(resizeRes.path) + safeDeleteAsync(resizeRes.uri) } } throw new Error( @@ -317,3 +347,25 @@ async function withTempFile( safeDeleteAsync(tmpDirUri) } } + +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), + } +} diff --git a/yarn.lock b/yarn.lock index ce64eb1567..4e33e47f5d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2879,11 +2879,6 @@ "@babel/helper-validator-identifier" "^7.24.6" to-fast-properties "^2.0.0" -"@bam.tech/react-native-image-resizer@^3.0.4": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@bam.tech/react-native-image-resizer/-/react-native-image-resizer-3.0.5.tgz#6661ba020de156268f73bdc92fbb93ef86f88a13" - integrity sha512-u5QGUQGGVZiVCJ786k9/kd7pPRZ6eYfJCYO18myVCH8FbVI7J8b5GT2Svjj2x808DlWeqfaZOOzxPqo27XYvrQ== - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"