Migrate to new ImageManipulator API
This commit is contained in:
@@ -1,150 +0,0 @@
|
|||||||
import {deleteAsync} from 'expo-file-system'
|
|
||||||
import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
|
|
||||||
import RNFetchBlob from 'rn-fetch-blob'
|
|
||||||
|
|
||||||
import {
|
|
||||||
downloadAndResize,
|
|
||||||
DownloadAndResizeOpts,
|
|
||||||
getResizedDimensions,
|
|
||||||
} from '../../src/lib/media/manip'
|
|
||||||
|
|
||||||
const mockResizedImage = {
|
|
||||||
path: 'file://resized-image.jpg',
|
|
||||||
size: 100,
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
mime: 'image/jpeg',
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('downloadAndResize', () => {
|
|
||||||
const errorSpy = jest.spyOn(global.console, 'error')
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const mockedCreateResizedImage = manipulateAsync as jest.Mock
|
|
||||||
mockedCreateResizedImage.mockResolvedValue({
|
|
||||||
uri: 'file://resized-image.jpg',
|
|
||||||
...mockResizedImage,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
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'),
|
|
||||||
info: jest.fn().mockReturnValue({status: 200}),
|
|
||||||
flush: jest.fn(),
|
|
||||||
})
|
|
||||||
|
|
||||||
const opts: DownloadAndResizeOpts = {
|
|
||||||
uri: 'https://example.com/image.jpg',
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
maxSize: 500000,
|
|
||||||
mode: 'cover',
|
|
||||||
timeout: 10000,
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await downloadAndResize(opts)
|
|
||||||
expect(result).toEqual(mockResizedImage)
|
|
||||||
expect(RNFetchBlob.config).toHaveBeenCalledWith({
|
|
||||||
fileCache: true,
|
|
||||||
appendExt: 'jpeg',
|
|
||||||
})
|
|
||||||
expect(RNFetchBlob.fetch).toHaveBeenCalledWith(
|
|
||||||
'GET',
|
|
||||||
'https://example.com/image.jpg',
|
|
||||||
)
|
|
||||||
|
|
||||||
// First time it gets called is to get dimensions
|
|
||||||
expect(manipulateAsync).toHaveBeenCalledWith(expect.any(String), [], {})
|
|
||||||
expect(manipulateAsync).toHaveBeenCalledWith(
|
|
||||||
expect.any(String),
|
|
||||||
[{resize: {height: opts.height, width: opts.width}}],
|
|
||||||
{format: SaveFormat.JPEG, compress: 1.0},
|
|
||||||
)
|
|
||||||
expect(deleteAsync).toHaveBeenCalledWith(expect.any(String), {
|
|
||||||
idempotent: true,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return undefined for invalid URI', async () => {
|
|
||||||
const opts: DownloadAndResizeOpts = {
|
|
||||||
uri: 'invalid-uri',
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
maxSize: 500000,
|
|
||||||
mode: 'cover',
|
|
||||||
timeout: 10000,
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await downloadAndResize(opts)
|
|
||||||
expect(errorSpy).toHaveBeenCalled()
|
|
||||||
expect(result).toBeUndefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return undefined for non-200 response', async () => {
|
|
||||||
const mockedFetch = RNFetchBlob.fetch as jest.Mock
|
|
||||||
mockedFetch.mockResolvedValueOnce({
|
|
||||||
path: jest.fn().mockReturnValue('file://downloaded-image'),
|
|
||||||
info: jest.fn().mockReturnValue({status: 400}),
|
|
||||||
flush: jest.fn(),
|
|
||||||
})
|
|
||||||
|
|
||||||
const opts: DownloadAndResizeOpts = {
|
|
||||||
uri: 'https://example.com/image',
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
maxSize: 500000,
|
|
||||||
mode: 'cover',
|
|
||||||
timeout: 10000,
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await downloadAndResize(opts)
|
|
||||||
expect(errorSpy).not.toHaveBeenCalled()
|
|
||||||
expect(result).toBeUndefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not downsize whenever dimensions are below the max dimensions', () => {
|
|
||||||
const initialDimensionsOne = {
|
|
||||||
width: 1200,
|
|
||||||
height: 1000,
|
|
||||||
}
|
|
||||||
const resizedDimensionsOne = getResizedDimensions(initialDimensionsOne)
|
|
||||||
|
|
||||||
const initialDimensionsTwo = {
|
|
||||||
width: 1000,
|
|
||||||
height: 1200,
|
|
||||||
}
|
|
||||||
const resizedDimensionsTwo = getResizedDimensions(initialDimensionsTwo)
|
|
||||||
|
|
||||||
expect(resizedDimensionsOne).toEqual(initialDimensionsOne)
|
|
||||||
expect(resizedDimensionsTwo).toEqual(initialDimensionsTwo)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should resize dimensions and maintain aspect ratio if they are above the max dimensons', () => {
|
|
||||||
const initialDimensionsOne = {
|
|
||||||
width: 3000,
|
|
||||||
height: 1500,
|
|
||||||
}
|
|
||||||
const resizedDimensionsOne = getResizedDimensions(initialDimensionsOne)
|
|
||||||
|
|
||||||
const initialDimensionsTwo = {
|
|
||||||
width: 2000,
|
|
||||||
height: 4000,
|
|
||||||
}
|
|
||||||
const resizedDimensionsTwo = getResizedDimensions(initialDimensionsTwo)
|
|
||||||
|
|
||||||
expect(resizedDimensionsOne).toEqual({
|
|
||||||
width: 2000,
|
|
||||||
height: 1000,
|
|
||||||
})
|
|
||||||
expect(resizedDimensionsTwo).toEqual({
|
|
||||||
width: 1000,
|
|
||||||
height: 2000,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -44,15 +44,6 @@ jest.mock('expo-file-system', () => ({
|
|||||||
deleteAsync: jest.fn(),
|
deleteAsync: jest.fn(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
jest.mock('expo-image-manipulator', () => ({
|
|
||||||
manipulateAsync: jest.fn().mockResolvedValue({
|
|
||||||
uri: 'file://resized-image',
|
|
||||||
}),
|
|
||||||
SaveFormat: {
|
|
||||||
JPEG: 'jpeg',
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('expo-camera', () => ({
|
jest.mock('expo-camera', () => ({
|
||||||
Camera: {
|
Camera: {
|
||||||
useCameraPermissions: jest.fn(() => [true]),
|
useCameraPermissions: jest.fn(() => [true]),
|
||||||
|
|||||||
+13
-10
@@ -11,7 +11,7 @@ import {
|
|||||||
StorageAccessFramework,
|
StorageAccessFramework,
|
||||||
writeAsStringAsync,
|
writeAsStringAsync,
|
||||||
} from 'expo-file-system'
|
} from 'expo-file-system'
|
||||||
import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
|
import {ImageManipulator, SaveFormat} from 'expo-image-manipulator'
|
||||||
import * as MediaLibrary from 'expo-media-library'
|
import * as MediaLibrary from 'expo-media-library'
|
||||||
import * as Sharing from 'expo-sharing'
|
import * as Sharing from 'expo-sharing'
|
||||||
import {Buffer} from 'buffer'
|
import {Buffer} from 'buffer'
|
||||||
@@ -172,7 +172,10 @@ async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
|
|||||||
// Now instead, we have to supply the final dimensions to the manipulation function instead.
|
// Now instead, we have to supply the final dimensions to the manipulation function instead.
|
||||||
// Performing an "empty" manipulation lets us get the dimensions of the original image. React Native's Image.getSize()
|
// Performing an "empty" manipulation lets us get the dimensions of the original image. React Native's Image.getSize()
|
||||||
// does not work for local files...
|
// does not work for local files...
|
||||||
const imageRes = await manipulateAsync(localUri, [], {})
|
const imageRes = await (
|
||||||
|
await ImageManipulator.manipulate(localUri).renderAsync()
|
||||||
|
).saveAsync()
|
||||||
|
|
||||||
const newDimensions = getResizedDimensions({
|
const newDimensions = getResizedDimensions({
|
||||||
width: imageRes.width,
|
width: imageRes.width,
|
||||||
height: imageRes.height,
|
height: imageRes.height,
|
||||||
@@ -181,14 +184,14 @@ async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
|
|||||||
for (let i = 0; i < 9; i++) {
|
for (let i = 0; i < 9; i++) {
|
||||||
// nearest 10th
|
// nearest 10th
|
||||||
const quality = Math.round((1 - 0.1 * i) * 10) / 10
|
const quality = Math.round((1 - 0.1 * i) * 10) / 10
|
||||||
const resizeRes = await manipulateAsync(
|
const resizeRes = await (
|
||||||
localUri,
|
await ImageManipulator.manipulate(localUri)
|
||||||
[{resize: newDimensions}],
|
.resize(newDimensions)
|
||||||
{
|
.renderAsync()
|
||||||
format: SaveFormat.JPEG,
|
).saveAsync({
|
||||||
compress: quality,
|
format: SaveFormat.JPEG,
|
||||||
},
|
compress: quality,
|
||||||
)
|
})
|
||||||
|
|
||||||
const fileInfo = await getInfoAsync(resizeRes.uri)
|
const fileInfo = await getInfoAsync(resizeRes.uri)
|
||||||
if (!fileInfo.exists) {
|
if (!fileInfo.exists) {
|
||||||
|
|||||||
+28
-11
@@ -7,7 +7,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
ActionCrop,
|
ActionCrop,
|
||||||
manipulateAsync,
|
ImageManipulator,
|
||||||
SaveFormat,
|
SaveFormat,
|
||||||
} from 'expo-image-manipulator'
|
} from 'expo-image-manipulator'
|
||||||
import {nanoid} from 'nanoid/non-secure'
|
import {nanoid} from 'nanoid/non-secure'
|
||||||
@@ -179,7 +179,24 @@ export async function manipulateImage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const source = img.source
|
const source = img.source
|
||||||
const result = await manipulateAsync(source.path, actions, {
|
const context = ImageManipulator.manipulate(source.path)
|
||||||
|
for (let action of actions) {
|
||||||
|
if ('resize' in action) {
|
||||||
|
context.resize(action.resize)
|
||||||
|
} else if ('rotate' in action) {
|
||||||
|
context.rotate(action.rotate)
|
||||||
|
} else if ('flip' in action) {
|
||||||
|
context.flip(action.flip)
|
||||||
|
} else if ('crop' in action) {
|
||||||
|
context.crop(action.crop)
|
||||||
|
} else if ('extent' in action) {
|
||||||
|
context.extent(action.extent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await (
|
||||||
|
await context.renderAsync()
|
||||||
|
).saveAsync({
|
||||||
format: SaveFormat.PNG,
|
format: SaveFormat.PNG,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -216,15 +233,15 @@ export async function compressImage(img: ComposerImage): Promise<ImageMeta> {
|
|||||||
// Float precision
|
// Float precision
|
||||||
const factor = i / 10
|
const factor = i / 10
|
||||||
|
|
||||||
const res = await manipulateAsync(
|
const res = await (
|
||||||
source.path,
|
await ImageManipulator.manipulate(source.path)
|
||||||
[{resize: {width: w, height: h}}],
|
.resize({width: w, height: h})
|
||||||
{
|
.renderAsync()
|
||||||
compress: factor,
|
).saveAsync({
|
||||||
format: SaveFormat.JPEG,
|
compress: factor,
|
||||||
base64: true,
|
format: SaveFormat.JPEG,
|
||||||
},
|
base64: true,
|
||||||
)
|
})
|
||||||
|
|
||||||
const base64 = res.base64
|
const base64 = res.base64
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||||
import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
|
import {ImageManipulator, SaveFormat} from 'expo-image-manipulator'
|
||||||
import {LinearGradient} from 'expo-linear-gradient'
|
import {LinearGradient} from 'expo-linear-gradient'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
@@ -45,25 +45,21 @@ export function Component({
|
|||||||
const onPressDone = async () => {
|
const onPressDone = async () => {
|
||||||
const img = imageRef.current!
|
const img = imageRef.current!
|
||||||
|
|
||||||
const result = await manipulateAsync(
|
const context = ImageManipulator.manipulate(uri)
|
||||||
uri,
|
|
||||||
isEmpty
|
if (!isEmpty) {
|
||||||
? []
|
context.crop({
|
||||||
: [
|
originX: (crop.x * img.naturalWidth) / 100,
|
||||||
{
|
originY: (crop.y * img.naturalHeight) / 100,
|
||||||
crop: {
|
width: (crop.width * img.naturalWidth) / 100,
|
||||||
originX: (crop.x * img.naturalWidth) / 100,
|
height: (crop.height * img.naturalHeight) / 100,
|
||||||
originY: (crop.y * img.naturalHeight) / 100,
|
})
|
||||||
width: (crop.width * img.naturalWidth) / 100,
|
}
|
||||||
height: (crop.height * img.naturalHeight) / 100,
|
const image = await context.renderAsync()
|
||||||
},
|
const result = await image.saveAsync({
|
||||||
},
|
base64: true,
|
||||||
],
|
format: SaveFormat.JPEG,
|
||||||
{
|
})
|
||||||
base64: true,
|
|
||||||
format: SaveFormat.JPEG,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
onSelect({
|
onSelect({
|
||||||
path: result.uri,
|
path: result.uri,
|
||||||
|
|||||||
Reference in New Issue
Block a user