Skip to content

Commit a825181

Browse files
committed
fix: restore image transforms after upgrades
1 parent a63d83b commit a825181

3 files changed

Lines changed: 63 additions & 20 deletions

File tree

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

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.graphics.Bitmap
44
import android.graphics.Canvas
55
import android.graphics.Matrix
66
import android.graphics.Rect
7+
import android.graphics.RectF
78
import android.os.Build
89
import androidx.annotation.Keep
910
import androidx.core.graphics.createBitmap
@@ -22,6 +23,18 @@ import com.margelo.nitro.image.extensions.toCpuAccessible
2223
import com.margelo.nitro.image.extensions.toMutable
2324
import java.io.File
2425
import java.nio.ByteBuffer
26+
import kotlin.math.abs
27+
import kotlin.math.ceil
28+
import kotlin.math.round
29+
30+
private fun roundedUpPixelDimension(value: Float): Int {
31+
val rounded = round(value)
32+
return if (abs(value - rounded) < 0.0001f) {
33+
rounded.toInt()
34+
} else {
35+
ceil(value).toInt()
36+
}.coerceAtLeast(1)
37+
}
2538

2639
@Suppress("ConvertSecondaryConstructorToPrimary")
2740
@Keep
@@ -54,7 +67,7 @@ class HybridImage: HybridImageSpec {
5467
} else {
5568
// Copy the data into a CPU buffer (ByteBuffer)
5669
var bitmap = bitmap
57-
if (bitmap.isGPU) {
70+
if (bitmap.isGPU || bitmap.pixelFormat == PixelFormat.UNKNOWN) {
5871
// If this is a GPU-based bitmap (but we cannot use GPU), copy it to a CPU Bitmap first
5972
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false)
6073
}
@@ -84,10 +97,19 @@ class HybridImage: HybridImageSpec {
8497
// 1. Make sure the Bitmap we want to draw is drawable (HARDWARE isn't)
8598
val source = bitmap.toCpuAccessible()
8699
// 2. Create a rotation Matrix
87-
val matrix = Matrix()
88-
matrix.setRotate(degrees.toFloat(), source.width / 2f, source.height / 2f)
89-
// 3. Create a new blank Bitmap as our output
90-
val destination = createBitmap(bitmap.width, bitmap.height)
100+
val matrix = Matrix().apply {
101+
setRotate(degrees.toFloat())
102+
}
103+
val sourceRect = RectF(0f, 0f, source.width.toFloat(), source.height.toFloat())
104+
val rotatedRect = RectF()
105+
matrix.mapRect(rotatedRect, sourceRect)
106+
// 3. Create a new blank Bitmap as our output, large enough to contain the rotated source.
107+
val destination = createBitmap(
108+
roundedUpPixelDimension(rotatedRect.width()),
109+
roundedUpPixelDimension(rotatedRect.height()),
110+
source.config ?: Bitmap.Config.ARGB_8888
111+
)
112+
matrix.postTranslate(-rotatedRect.left, -rotatedRect.top)
91113
// 4. Draw the Bitmap to our destination
92114
Canvas(destination).apply {
93115
drawBitmap(source, matrix, null)
@@ -201,8 +223,8 @@ class HybridImage: HybridImageSpec {
201223
// 3. Prepare the Bitmap we want to draw into our Canvas
202224
val rect = Rect(x.toInt(),
203225
y.toInt(),
204-
width.toInt(),
205-
height.toInt())
226+
(x + width).toInt(),
227+
(y + height).toInt())
206228

207229
// 4. Make sure we can draw the Bitmap (HARDWARE isn't CPU accessible)
208230
val drawable = newImage.bitmap.toCpuAccessible()

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class HybridImageFactory: HybridImageFactorySpec {
1717
let size = CGSize(width: width, height: height)
1818
let format = UIGraphicsImageRendererFormat()
1919
format.opaque = !enableAlpha
20+
format.scale = 1
2021
// 2. Create a new UIImage
2122
let uiImage = UIGraphicsImageRenderer(size: size, format: format).image { canvas in
2223
if let fill {

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

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88
import UIKit
99
import NitroModules
1010

11+
private func roundedUpPixelDimension(_ value: CGFloat) -> CGFloat {
12+
let rounded = round(value)
13+
if abs(value - rounded) < 0.0001 {
14+
return rounded
15+
}
16+
return ceil(value)
17+
}
18+
1119
/**
1220
* A protocol that represents a native image.
1321
* This can be used to downcast from `HybridImageSpec`
@@ -63,20 +71,25 @@ public extension NativeImage {
6371
return HybridImage(uiImage: rotated)
6472
} else {
6573
// Slow path: we actually rotate using UIGraphicsImageRenderer
66-
let renderer = UIGraphicsImageRenderer(size: uiImage.size)
74+
let radians = degrees * .pi / 180
75+
let sourceSize = uiImage.size
76+
let sourceRect = CGRect(origin: .zero, size: sourceSize)
77+
let rotatedRect = sourceRect.applying(CGAffineTransform(rotationAngle: radians))
78+
let outputSize = CGSize(width: roundedUpPixelDimension(abs(rotatedRect.width)),
79+
height: roundedUpPixelDimension(abs(rotatedRect.height)))
80+
let format = UIGraphicsImageRendererFormat()
81+
format.scale = 1
82+
let renderer = UIGraphicsImageRenderer(size: outputSize, format: format)
6783
let rotatedImage = renderer.image { context in
68-
let width = uiImage.size.width
69-
let height = uiImage.size.height
7084
// 1. Move to the center of the image so our origin is the center
71-
context.cgContext.translateBy(x: width / 2, y: height / 2)
85+
context.cgContext.translateBy(x: outputSize.width / 2, y: outputSize.height / 2)
7286
// 2. Rotate by the given radians
73-
let radians = degrees * .pi / 180
7487
context.cgContext.rotate(by: radians)
7588
// 3. Draw the Image offset by half the frame so we counter our center origin from step 1.
76-
let rect = CGRect(x: -(width / 2),
77-
y: -(height / 2),
78-
width: width,
79-
height: height)
89+
let rect = CGRect(x: -(sourceSize.width / 2),
90+
y: -(sourceSize.height / 2),
91+
width: sourceSize.width,
92+
height: sourceSize.height)
8093
uiImage.draw(in: rect)
8194
}
8295
return HybridImage(uiImage: rotatedImage)
@@ -97,7 +110,9 @@ public extension NativeImage {
97110
}
98111
let targetSize = CGSize(width: width, height: height)
99112

100-
let renderer = UIGraphicsImageRenderer(size: targetSize)
113+
let format = UIGraphicsImageRendererFormat()
114+
format.scale = 1
115+
let renderer = UIGraphicsImageRenderer(size: targetSize, format: format)
101116
let resizedImage = renderer.image { context in
102117
let targetRect = CGRect(origin: .zero, size: targetSize)
103118
uiImage.draw(in: targetRect)
@@ -124,12 +139,17 @@ public extension NativeImage {
124139
throw RuntimeError.error(withMessage: "This image does not have an underlying .cgImage!")
125140
}
126141

127-
let targetRect = CGRect(origin: CGPoint(x: startX, y: startY),
128-
size: CGSize(width: uiImage.size.width, height: uiImage.size.height))
142+
let scale = uiImage.scale
143+
let targetRect = CGRect(x: startX * scale,
144+
y: startY * scale,
145+
width: targetWidth * scale,
146+
height: targetHeight * scale).integral
129147
guard let croppedCgImage = cgImage.cropping(to: targetRect) else {
130148
throw RuntimeError.error(withMessage: "Failed to crop CGImage to \(targetRect)!")
131149
}
132-
let croppedUiImage = UIImage(cgImage: croppedCgImage)
150+
let croppedUiImage = UIImage(cgImage: croppedCgImage,
151+
scale: scale,
152+
orientation: uiImage.imageOrientation)
133153
return HybridImage(uiImage: croppedUiImage)
134154
}
135155

0 commit comments

Comments
 (0)