Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,15 @@ const webImage = await WebImages.loadFromURLAsync('https://picsum.photos/seed/
const upsideDown = await webImage.rotateAsync(180)
```

#### Mirroring

An `Image` can be mirrored horizontally (left <-> right) entirely in-memory, without ever writing to- or reading from- a file:

```ts
const webImage = await WebImages.loadFromURLAsync('https://picsum.photos/seed/123/400')
const mirrored = await webImage.mirrorHorizontallyAsync()
```

#### Render into another Image

An `Image` can be rendered into another `Image` entirely in-memory. This creates a third image (the result):
Expand Down
16 changes: 5 additions & 11 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2493,9 +2493,9 @@ DEPENDENCIES:
- fmt (from `../../node_modules/react-native/third-party-podspecs/fmt.podspec`)
- glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- NitroImage (from `../node_modules/react-native-nitro-image`)
- NitroImage (from `../../node_modules/react-native-nitro-image`)
- NitroModules (from `../../node_modules/react-native-nitro-modules`)
- NitroWebImage (from `../node_modules/react-native-nitro-web-image`)
- NitroWebImage (from `../../node_modules/react-native-nitro-web-image`)
- RCT-Folly (from `../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTDeprecation (from `../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
- RCTRequired (from `../../node_modules/react-native/Libraries/Required`)
Expand Down Expand Up @@ -2591,11 +2591,11 @@ EXTERNAL SOURCES:
:podspec: "../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
:tag: hermes-2025-07-07-RNv0.81.0-e0fc67142ec0763c6b6153ca2bf96df815539782
NitroImage:
:path: "../node_modules/react-native-nitro-image"
:path: "../../node_modules/react-native-nitro-image"
NitroModules:
:path: "../../node_modules/react-native-nitro-modules"
NitroWebImage:
:path: "../node_modules/react-native-nitro-web-image"
:path: "../../node_modules/react-native-nitro-web-image"
RCT-Folly:
:podspec: "../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
RCTDeprecation:
Expand Down
4 changes: 2 additions & 2 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"react": "19.1.0",
"react-native": "0.81.0",
"react-native-fast-image": "^8.6.3",
"react-native-nitro-image": "../packages/react-native-nitro-image",
"react-native-nitro-web-image": "../packages/react-native-nitro-web-image",
"react-native-nitro-image": "workspace:*",
"react-native-nitro-web-image": "workspace:*",
"react-native-nitro-modules": "0.33.9",
"react-native-safe-area-context": "^5.6.0",
"react-native-screens": "^4.14.1"
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"name": "react-native-nitro-image-monorepo",
"packageManager": "bun@1.3.9",
"private": true,
"version": "0.11.1",
"repository": "https://github.com/mrousavy/react-native-nitro-image.git",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,18 @@ class HybridImage: HybridImageSpec {
return Promise.async { crop(startX, startY, endX, endY) }
}

override fun mirrorHorizontally(): HybridImageSpec {
val matrix = Matrix().apply {
preScale(-1f, 1f)
}
val mirrored = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, false)
return HybridImage(mirrored)
}

override fun mirrorHorizontallyAsync(): Promise<HybridImageSpec> {
return Promise.async { mirrorHorizontally() }
}

override fun saveToFileAsync(
path: String,
format: ImageFormat,
Expand Down
35 changes: 23 additions & 12 deletions packages/react-native-nitro-image/ios/NativeImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public extension NativeImage {
func toRawPixelData(allowGpu _: Bool?) throws -> RawPixelData {
return try uiImage.toRawPixelData()
}
func toRawPixelDataAsync(allowGpu: Bool?) throws -> Promise<RawPixelData> {
func toRawPixelDataAsync(allowGpu: Bool?) -> Promise<RawPixelData> {
return Promise.async {
return try self.toRawPixelData(allowGpu: allowGpu)
}
Expand All @@ -46,12 +46,12 @@ public extension NativeImage {
return try uiImage.toEncodedImageData(format: format, quality: quality ?? 100)
}

func toEncodedImageDataAsync(format: ImageFormat, quality: Double?) throws -> Promise<EncodedImageData> {
func toEncodedImageDataAsync(format: ImageFormat, quality: Double?) -> Promise<EncodedImageData> {
return Promise.async {
return try self.toEncodedImageData(format: format, quality: quality)
}
}

func rotate(degrees: Double, allowFastFlagRotation: Bool?) -> any HybridImageSpec {
if allowFastFlagRotation == true,
degrees.truncatingRemainder(dividingBy: 90) == 0,
Expand Down Expand Up @@ -105,7 +105,7 @@ public extension NativeImage {
return HybridImage(uiImage: resizedImage)
}

func resizeAsync(width: Double, height: Double) throws -> Promise<any HybridImageSpec> {
func resizeAsync(width: Double, height: Double) -> Promise<any HybridImageSpec> {
return Promise.async {
return try self.resize(width: width, height: height)
}
Expand Down Expand Up @@ -133,12 +133,23 @@ public extension NativeImage {
return HybridImage(uiImage: croppedUiImage)
}

func cropAsync(startX: Double, startY: Double, endX: Double, endY: Double) throws -> Promise<any HybridImageSpec> {
func cropAsync(startX: Double, startY: Double, endX: Double, endY: Double) -> Promise<any HybridImageSpec> {
return Promise.async {
return try self.crop(startX: startX, startY: startY, endX: endX, endY: endY)
}
}

func mirrorHorizontally() -> any HybridImageSpec {
let mirrored = uiImage.withHorizontallyFlippedOrientation()
return HybridImage(uiImage: mirrored)
}

func mirrorHorizontallyAsync() -> Promise<any HybridImageSpec> {
return Promise.async {
return mirrorHorizontally()
}
}

private func saveImage(to path: String, format: ImageFormat, quality: Double) throws {
let data = try uiImage.getData(in: format, quality: quality)
guard let url = URL(string: path) else {
Expand All @@ -147,13 +158,13 @@ public extension NativeImage {
try data.write(to: url)
}

func saveToFileAsync(path: String, format: ImageFormat, quality: Double?) throws -> Promise<Void> {
func saveToFileAsync(path: String, format: ImageFormat, quality: Double?) -> Promise<Void> {
return Promise.async(.utility) {
try self.saveImage(to: path, format: format, quality: quality ?? 100.0)
}
}

func saveToTemporaryFileAsync(format: ImageFormat, quality: Double?) throws -> Promise<String> {
func saveToTemporaryFileAsync(format: ImageFormat, quality: Double?) -> Promise<String> {
return Promise.async(.utility) {
let tempDirectory = FileManager.default.temporaryDirectory
let fileName = UUID().uuidString
Expand All @@ -170,12 +181,12 @@ public extension NativeImage {
return try ArrayBuffer.copy(data: thumbHash)
}

func toThumbHashAsync() throws -> Promise<ArrayBuffer> {
func toThumbHashAsync() -> Promise<ArrayBuffer> {
return Promise.async {
return try self.toThumbHash()
}
}

func renderInto(image newImage: any HybridImageSpec, x: Double, y: Double, width: Double, height: Double) throws -> any HybridImageSpec {
guard let newImage = newImage as? NativeImage else {
throw RuntimeError.error(withMessage: "The given image (\(newImage)) is not a `NativeImage`!")
Expand All @@ -186,16 +197,16 @@ public extension NativeImage {
let renderedImage = renderer.image { context in
// 2. Render our own image (copy)
self.uiImage.draw(at: .zero)

// 3. Render the new image into our copy
let rect = CGRect(x: x, y: y, width: width, height: height)
newImage.uiImage.draw(in: rect)
}
// 4. Wrap the resulting UIImage in a HybridImage
return HybridImage(uiImage: renderedImage)
}
func renderIntoAsync(image newImage: any HybridImageSpec, x: Double, y: Double, width: Double, height: Double) throws -> Promise<any HybridImageSpec> {

func renderIntoAsync(image newImage: any HybridImageSpec, x: Double, y: Double, width: Double, height: Double) -> Promise<any HybridImageSpec> {
return Promise.async {
return try self.renderInto(image: newImage, x: x, y: y, width: width, height: height)
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading