One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

Image Manipulator

Crop, resize, rotate, and encode local images

One.ImageManipulator.transform reads a local image and writes a new JPEG, PNG, or WebP file in the app cache. It never changes the source, runs off the JavaScript thread, and needs no photo permission. Reach for it to prepare uploads, thumbnails, and avatars, or to straighten a photo before replacing its library content.

On iOS it renders through Core Image on a dedicated serial queue. On Android it decodes with BitmapFactory, applies EXIF orientation, and encodes on a worker thread. On web it draws to a canvas on the browser thread and returns a blob URL.

Usage

import { One } from 'one'
async function editPhoto(photoUri: string) {
const edited = await One.ImageManipulator.transform(photoUri, {
crop: { x: 20, y: 20, width: 400, height: 300 },
resize: { width: 200 },
rotate: 90,
flip: 'horizontal',
format: 'jpeg',
quality: 0.8,
})
console.log(edited.uri, edited.width, edited.height, edited.size)
}

Operations run in one fixed order: crop, resize, rotate, then flip. The result carries the new file URI, its pixel size, and its encoded byte length.

How it works

Crop coordinates are whole pixels from the top left of the upright source: orientation is applied first on every platform, including mirrored EXIF orientations on web. Resize takes a width, a height, or both; one dimension preserves the aspect ratio, and both pin the exact size. Rotation is clockwise and accepts only 0, 90, 180, or 270 degrees. Flip mirrors the rotated output horizontally or vertically. The default format is JPEG at quality 0.9; quality runs 0 to 1 and applies to JPEG and WebP. PNG preserves transparency and ignores quality, while JPEG drops it. WebP keeps small file sizes with quality-controlled lossy compression; on iOS it encodes through SDWebImageWebPCoder (libwebp), the same coder Expo uses, since ImageIO has no WebP writer. Output is capped at 25 megapixels and 10,000 pixels per side; anything larger rejects with E_IMAGE_INPUT.

On native the source must be a local file:// URI. Download network images to a file first. The result is a cache file yours to keep, move, or delete: pass it to One.PhotoLibrary.saveImage to add it to Photos, or delete it with One.FileSystem.delete when finished. On web the source can also be a data or blob URL, or a remote image that permits cross-origin canvas access, and the result is a blob URL that One.UI.Image, HTML images, and fetch all accept; release it with URL.revokeObjectURL when finished. Failures reject with coded errors: a non-file URI, a missing file, an undecodable image, invalid input, an encoder failure, or a write failure, each with its own E_IMAGE_* code. Server rendering rejects since there is no decoder without a browser.

API reference

MethodsTypeDescription
transform(uri: string, options: ImageManipulatorOptions) => Promise<ImageTransformResult>Crop, resize, rotate, flip, and encode. Format defaults to jpeg.
(uri: string, options: ImageManipulatorOptions) => Promise<ImageTransformResult>Crop, resize, rotate, flip, and encode. Format defaults to jpeg.
OptionsTypeDescription
cropImageCropWhole pixels from the top left, applied first.
ImageCropWhole pixels from the top left, applied first.
resizeImageResizeOne side keeps ratio; both pin the size.
ImageResizeOne side keeps ratio; both pin the size.
rotatenumberClockwise degrees: 0, 90, 180, or 270.
numberClockwise degrees: 0, 90, 180, or 270.
flipOneImageFlipMirror axis applied after rotation.
OneImageFlipMirror axis applied after rotation.
formatOneImageFormatJpeg, png, or webp.
OneImageFormatJpeg, png, or webp.
qualitynumberJPEG or WebP quality 0 to 1. Default 0.9.
numberJPEG or WebP quality 0 to 1. Default 0.9.
CropTypeDescription
xnumberLeft edge in whole pixels.
numberLeft edge in whole pixels.
ynumberTop edge in whole pixels.
numberTop edge in whole pixels.
widthnumberCrop width in whole pixels.
numberCrop width in whole pixels.
heightnumberCrop height in whole pixels.
numberCrop height in whole pixels.
ResizeTypeDescription
widthnumberTarget width. Optional.
numberTarget width. Optional.
heightnumberTarget height. Optional.
numberTarget height. Optional.
ResultTypeDescription
uristringThe new file, or blob URL on web.
stringThe new file, or blob URL on web.
widthnumberOutput width in pixels.
numberOutput width in pixels.
heightnumberOutput height in pixels.
numberOutput height in pixels.
sizenumberEncoded length in bytes.
numberEncoded length in bytes.

Platform differences

iOSAndroidWeb
EngineCore ImageBitmap toolsCanvas 2D
AcceptsLocal filesLocal filesURLs and blobs too
WritesCache fileCache fileBlob URL
OrientationApplied firstApplied firstApplied first

Limits and gotchas

Examples

Make an upload thumbnail

import { One } from 'one'
async function thumbnail(photoUri: string) {
const small = await One.ImageManipulator.transform(photoUri, {
resize: { width: 400 },
format: 'jpeg',
quality: 0.7,
})
const bytes = await (await fetch(small.uri)).arrayBuffer()
await One.FileSystem.delete(small.uri)
return bytes
}

One resize dimension keeps the aspect ratio while bounding the width. The cached file is read once for the upload bytes, then deleted.

Straighten a photo for the library

import { One } from 'one'
async function replaceWithUpright(identifier: string, photoUri: string) {
const edited = await One.ImageManipulator.transform(photoUri, {
rotate: 90,
format: 'jpeg',
})
await One.PhotoLibrary.replaceImageContent(identifier, edited.uri)
await One.FileSystem.delete(edited.uri)
}

Library replacement needs upright JPEG pixels rather than orientation metadata, which is exactly what a transform produces. The temp file is deleted once Photos has the new content.

Crop a square avatar

import { One } from 'one'
async function avatar(photoUri: string, side: number) {
const output = await One.ImageManipulator.transform(photoUri, {
crop: { x: 0, y: 0, width: side, height: side },
resize: { width: 256, height: 256 },
format: 'png',
})
return output.uri
}

The square crop runs before the resize, so the resize pins an exact 256-pixel square without distortion. PNG keeps transparency for avatars with soft or shaped edges.

Edit this page on GitHub.