Skip to content

Commit 93a9517

Browse files
authored
feat: Add mirrorHorizontally() (#99)
* feat: Add `mirrorHorizontally()` * Update README.md
1 parent e3977a7 commit 93a9517

16 files changed

Lines changed: 151 additions & 30 deletions

File tree

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,15 @@ const webImage = await WebImages.loadFromURLAsync('https://picsum.photos/seed/
174174
const upsideDown = await webImage.rotateAsync(180)
175175
```
176176

177+
#### Mirroring
178+
179+
An `Image` can be mirrored horizontally (left <-> right) entirely in-memory, without ever writing to- or reading from- a file:
180+
181+
```ts
182+
const webImage = await WebImages.loadFromURLAsync('https://picsum.photos/seed/123/400')
183+
const mirrored = await webImage.mirrorHorizontallyAsync()
184+
```
185+
177186
#### Render into another Image
178187

179188
An `Image` can be rendered into another `Image` entirely in-memory. This creates a third image (the result):

bun.lock

Lines changed: 5 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/ios/Podfile.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2493,9 +2493,9 @@ DEPENDENCIES:
24932493
- fmt (from `../../node_modules/react-native/third-party-podspecs/fmt.podspec`)
24942494
- glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`)
24952495
- hermes-engine (from `../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
2496-
- NitroImage (from `../node_modules/react-native-nitro-image`)
2496+
- NitroImage (from `../../node_modules/react-native-nitro-image`)
24972497
- NitroModules (from `../../node_modules/react-native-nitro-modules`)
2498-
- NitroWebImage (from `../node_modules/react-native-nitro-web-image`)
2498+
- NitroWebImage (from `../../node_modules/react-native-nitro-web-image`)
24992499
- RCT-Folly (from `../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
25002500
- RCTDeprecation (from `../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
25012501
- RCTRequired (from `../../node_modules/react-native/Libraries/Required`)
@@ -2591,11 +2591,11 @@ EXTERNAL SOURCES:
25912591
:podspec: "../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
25922592
:tag: hermes-2025-07-07-RNv0.81.0-e0fc67142ec0763c6b6153ca2bf96df815539782
25932593
NitroImage:
2594-
:path: "../node_modules/react-native-nitro-image"
2594+
:path: "../../node_modules/react-native-nitro-image"
25952595
NitroModules:
25962596
:path: "../../node_modules/react-native-nitro-modules"
25972597
NitroWebImage:
2598-
:path: "../node_modules/react-native-nitro-web-image"
2598+
:path: "../../node_modules/react-native-nitro-web-image"
25992599
RCT-Folly:
26002600
:podspec: "../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
26012601
RCTDeprecation:

example/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
"react": "19.1.0",
1717
"react-native": "0.81.0",
1818
"react-native-fast-image": "^8.6.3",
19-
"react-native-nitro-image": "../packages/react-native-nitro-image",
20-
"react-native-nitro-web-image": "../packages/react-native-nitro-web-image",
19+
"react-native-nitro-image": "workspace:*",
20+
"react-native-nitro-web-image": "workspace:*",
2121
"react-native-nitro-modules": "0.33.9",
2222
"react-native-safe-area-context": "^5.6.0",
2323
"react-native-screens": "^4.14.1"

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"name": "react-native-nitro-image-monorepo",
3-
"packageManager": "bun@1.3.9",
43
"private": true,
54
"version": "0.11.1",
65
"repository": "https://github.com/mrousavy/react-native-nitro-image.git",

packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImage.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ class HybridImage: HybridImageSpec {
134134
return Promise.async { crop(startX, startY, endX, endY) }
135135
}
136136

137+
override fun mirrorHorizontally(): HybridImageSpec {
138+
val matrix = Matrix().apply {
139+
preScale(-1f, 1f)
140+
}
141+
val mirrored = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, false)
142+
return HybridImage(mirrored)
143+
}
144+
145+
override fun mirrorHorizontallyAsync(): Promise<HybridImageSpec> {
146+
return Promise.async { mirrorHorizontally() }
147+
}
148+
137149
override fun saveToFileAsync(
138150
path: String,
139151
format: ImageFormat,

packages/react-native-nitro-image/ios/NativeImage.swift

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public extension NativeImage {
3636
func toRawPixelData(allowGpu _: Bool?) throws -> RawPixelData {
3737
return try uiImage.toRawPixelData()
3838
}
39-
func toRawPixelDataAsync(allowGpu: Bool?) throws -> Promise<RawPixelData> {
39+
func toRawPixelDataAsync(allowGpu: Bool?) -> Promise<RawPixelData> {
4040
return Promise.async {
4141
return try self.toRawPixelData(allowGpu: allowGpu)
4242
}
@@ -46,12 +46,12 @@ public extension NativeImage {
4646
return try uiImage.toEncodedImageData(format: format, quality: quality ?? 100)
4747
}
4848

49-
func toEncodedImageDataAsync(format: ImageFormat, quality: Double?) throws -> Promise<EncodedImageData> {
49+
func toEncodedImageDataAsync(format: ImageFormat, quality: Double?) -> Promise<EncodedImageData> {
5050
return Promise.async {
5151
return try self.toEncodedImageData(format: format, quality: quality)
5252
}
5353
}
54-
54+
5555
func rotate(degrees: Double, allowFastFlagRotation: Bool?) -> any HybridImageSpec {
5656
if allowFastFlagRotation == true,
5757
degrees.truncatingRemainder(dividingBy: 90) == 0,
@@ -105,7 +105,7 @@ public extension NativeImage {
105105
return HybridImage(uiImage: resizedImage)
106106
}
107107

108-
func resizeAsync(width: Double, height: Double) throws -> Promise<any HybridImageSpec> {
108+
func resizeAsync(width: Double, height: Double) -> Promise<any HybridImageSpec> {
109109
return Promise.async {
110110
return try self.resize(width: width, height: height)
111111
}
@@ -133,12 +133,23 @@ public extension NativeImage {
133133
return HybridImage(uiImage: croppedUiImage)
134134
}
135135

136-
func cropAsync(startX: Double, startY: Double, endX: Double, endY: Double) throws -> Promise<any HybridImageSpec> {
136+
func cropAsync(startX: Double, startY: Double, endX: Double, endY: Double) -> Promise<any HybridImageSpec> {
137137
return Promise.async {
138138
return try self.crop(startX: startX, startY: startY, endX: endX, endY: endY)
139139
}
140140
}
141141

142+
func mirrorHorizontally() -> any HybridImageSpec {
143+
let mirrored = uiImage.withHorizontallyFlippedOrientation()
144+
return HybridImage(uiImage: mirrored)
145+
}
146+
147+
func mirrorHorizontallyAsync() -> Promise<any HybridImageSpec> {
148+
return Promise.async {
149+
return mirrorHorizontally()
150+
}
151+
}
152+
142153
private func saveImage(to path: String, format: ImageFormat, quality: Double) throws {
143154
let data = try uiImage.getData(in: format, quality: quality)
144155
guard let url = URL(string: path) else {
@@ -147,13 +158,13 @@ public extension NativeImage {
147158
try data.write(to: url)
148159
}
149160

150-
func saveToFileAsync(path: String, format: ImageFormat, quality: Double?) throws -> Promise<Void> {
161+
func saveToFileAsync(path: String, format: ImageFormat, quality: Double?) -> Promise<Void> {
151162
return Promise.async(.utility) {
152163
try self.saveImage(to: path, format: format, quality: quality ?? 100.0)
153164
}
154165
}
155166

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

173-
func toThumbHashAsync() throws -> Promise<ArrayBuffer> {
184+
func toThumbHashAsync() -> Promise<ArrayBuffer> {
174185
return Promise.async {
175186
return try self.toThumbHash()
176187
}
177188
}
178-
189+
179190
func renderInto(image newImage: any HybridImageSpec, x: Double, y: Double, width: Double, height: Double) throws -> any HybridImageSpec {
180191
guard let newImage = newImage as? NativeImage else {
181192
throw RuntimeError.error(withMessage: "The given image (\(newImage)) is not a `NativeImage`!")
@@ -186,16 +197,16 @@ public extension NativeImage {
186197
let renderedImage = renderer.image { context in
187198
// 2. Render our own image (copy)
188199
self.uiImage.draw(at: .zero)
189-
200+
190201
// 3. Render the new image into our copy
191202
let rect = CGRect(x: x, y: y, width: width, height: height)
192203
newImage.uiImage.draw(in: rect)
193204
}
194205
// 4. Wrap the resulting UIImage in a HybridImage
195206
return HybridImage(uiImage: renderedImage)
196207
}
197-
198-
func renderIntoAsync(image newImage: any HybridImageSpec, x: Double, y: Double, width: Double, height: Double) throws -> Promise<any HybridImageSpec> {
208+
209+
func renderIntoAsync(image newImage: any HybridImageSpec, x: Double, y: Double, width: Double, height: Double) -> Promise<any HybridImageSpec> {
199210
return Promise.async {
200211
return try self.renderInto(image: newImage, x: x, y: y, width: width, height: height)
201212
}

packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridImageSpec.cpp

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridImageSpec.hpp

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageSpec.kt

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)