Skip to content

Commit 52a78aa

Browse files
author
Jonas Thuresson
committed
Fix bitmap crop/scaling for android
1 parent 16b6ee4 commit 52a78aa

File tree

5 files changed

+84
-24
lines changed

5 files changed

+84
-24
lines changed

android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorModuleImpl.kt

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,10 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
206206
BitmapRegionDecoder.newInstance(it)
207207
} else {
208208
@Suppress("DEPRECATION") BitmapRegionDecoder.newInstance(it, false)
209-
}
209+
} ?: throw Error("Could not create bitmap decoder. Uri: $uri")
210210

211-
val imageHeight: Int = decoder!!.height
212-
val imageWidth: Int = decoder!!.width
211+
val imageHeight: Int = decoder.height
212+
val imageWidth: Int = decoder.width
213213
val orientation = getOrientation(reactContext, Uri.parse(uri))
214214

215215
val (left, top) =
@@ -229,9 +229,9 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
229229

230230
return@use try {
231231
val rect = Rect(left, top, right, bottom)
232-
decoder!!.decodeRegion(rect, outOptions)
232+
decoder.decodeRegion(rect, outOptions)
233233
} finally {
234-
decoder!!.recycle()
234+
decoder.recycle()
235235
}
236236
}
237237
}
@@ -262,25 +262,22 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
262262
): Bitmap? {
263263
Assertions.assertNotNull(outOptions)
264264

265-
// Loading large bitmaps efficiently:
266-
// http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
267-
268-
// This uses scaling mode COVER
269-
270-
// Where would the crop rect end up within the scaled bitmap?
271-
272-
val bitmap =
273-
openBitmapInputStream(uri, headers)?.use {
274-
// This can use significantly less memory than decoding the full-resolution bitmap
275-
BitmapFactory.decodeStream(it, null, outOptions)
276-
} ?: return null
265+
return openBitmapInputStream(uri, headers)?.use {
266+
// Efficiently crops image without loading full resolution into memory
267+
// https://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html
268+
val decoder =
269+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
270+
BitmapRegionDecoder.newInstance(it)
271+
} else {
272+
@Suppress("DEPRECATION") BitmapRegionDecoder.newInstance(it, false)
273+
} ?: throw Error("Could not create bitmap decoder. Uri: $uri")
277274

278275
val orientation = getOrientation(reactContext, Uri.parse(uri))
279276
val (x, y) =
280277
when (orientation) {
281-
90 -> yPos to bitmap.height - rectWidth - xPos
282-
270 -> bitmap.width - rectHeight - yPos to xPos
283-
180 -> bitmap.width - rectWidth - xPos to bitmap.height - rectHeight - yPos
278+
90 -> yPos to decoder.height - rectWidth - xPos
279+
270 -> decoder.width - rectHeight - yPos to xPos
280+
180 -> decoder.width - rectWidth - xPos to decoder.height - rectHeight - yPos
284281
else -> xPos to yPos
285282
}
286283

@@ -323,7 +320,11 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
323320
val scaleMatrix = Matrix().apply { setScale(cropScale, cropScale) }
324321
val filter = true
325322

323+
val rect = Rect(0, 0, decoder.width, decoder.height)
324+
val bitmap =decoder.decodeRegion(rect, outOptions)
325+
326326
return Bitmap.createBitmap(bitmap, cropX, cropY, cropWidth, cropHeight, scaleMatrix, filter)
327+
}
327328
}
328329

329330
private fun openBitmapInputStream(uri: String, headers: HashMap<String, Any?>?): InputStream? {

example/ios/Podfile.lock

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,22 @@ PODS:
824824
- ReactCommon/turbomodule/bridging
825825
- ReactCommon/turbomodule/core
826826
- Yoga
827+
- react-native-slider (4.5.0):
828+
- RCT-Folly (= 2021.07.22.00)
829+
- RCTRequired
830+
- RCTTypeSafety
831+
- React-Codegen
832+
- React-Core
833+
- React-debug
834+
- React-Fabric
835+
- React-graphics
836+
- React-jsi
837+
- React-NativeModulesApple
838+
- React-RCTFabric
839+
- React-utils
840+
- ReactCommon/turbomodule/bridging
841+
- ReactCommon/turbomodule/core
842+
- Yoga
827843
- React-NativeModulesApple (0.72.6):
828844
- React-callinvoker
829845
- React-Core
@@ -985,6 +1001,7 @@ DEPENDENCIES:
9851001
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
9861002
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
9871003
- "react-native-image-editor (from `../node_modules/@react-native-community/image-editor`)"
1004+
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
9881005
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
9891006
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
9901007
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
@@ -1060,6 +1077,8 @@ EXTERNAL SOURCES:
10601077
:path: "../node_modules/react-native/ReactCommon/logger"
10611078
react-native-image-editor:
10621079
:path: "../node_modules/@react-native-community/image-editor"
1080+
react-native-slider:
1081+
:path: "../node_modules/@react-native-community/slider"
10631082
React-NativeModulesApple:
10641083
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios"
10651084
React-perflogger:
@@ -1130,11 +1149,12 @@ SPEC CHECKSUMS:
11301149
React-jsinspector: 194e32c6aab382d88713ad3dd0025c5f5c4ee072
11311150
React-logger: cebf22b6cf43434e471dc561e5911b40ac01d289
11321151
react-native-image-editor: 6491eca6c084de724a9a144056323cb00848b68c
1152+
react-native-slider: 69ccddffd41798b325247b9c4c09a0927e3b7cec
11331153
React-NativeModulesApple: 63505fb94b71e2469cab35bdaf36cca813cb5bfd
11341154
React-perflogger: e3596db7e753f51766bceadc061936ef1472edc3
11351155
React-RCTActionSheet: 17ab132c748b4471012abbcdcf5befe860660485
11361156
React-RCTAnimation: c8bbaab62be5817d2a31c36d5f2571e3f7dcf099
1137-
React-RCTAppDelegate: 100a4f479c664e4f63d4cfd3ff8acbd3915e31e7
1157+
React-RCTAppDelegate: 16245b0c3a216dd600db53209812f8d470643a57
11381158
React-RCTBlob: 86ab788db3fcc1af0d186a6625e7d0956ffeea5b
11391159
React-RCTFabric: 87e15f0ad21f7bf2642d4e78afaf162d349eb221
11401160
React-RCTImage: 670a3486b532292649b1aef3ffddd0b495a5cee4
@@ -1156,4 +1176,4 @@ SPEC CHECKSUMS:
11561176

11571177
PODFILE CHECKSUM: c464aabec59351f1e87d676c093bc6f524e21681
11581178

1159-
COCOAPODS: 1.13.0
1179+
COCOAPODS: 1.14.3

example/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
},
1515
"dependencies": {
1616
"@react-native-community/image-editor": "link:..",
17+
"@react-native-community/slider": "^4.5.0",
1718
"react": "18.2.0",
1819
"react-native": "0.72.6"
1920
},

example/src/SquareImageCropper.tsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
SafeAreaView,
99
} from 'react-native';
1010
import ImageEditor from '@react-native-community/image-editor';
11+
import Slider from '@react-native-community/slider';
1112

1213
import type { LayoutChangeEvent } from 'react-native';
1314
import { DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT } from './constants';
@@ -19,6 +20,7 @@ interface State {
1920
croppedImageURI: string | null;
2021
cropError: Error | null;
2122
measuredSize: ImageSize | null;
23+
cropScale: number;
2224
}
2325
interface Props {
2426
// noop
@@ -41,6 +43,7 @@ export class SquareImageCropper extends Component<Props, State> {
4143
measuredSize: null,
4244
croppedImageURI: null,
4345
cropError: null,
46+
cropScale: 1,
4447
};
4548
}
4649

@@ -89,6 +92,17 @@ export class SquareImageCropper extends Component<Props, State> {
8992
style={[styles.imageCropper, measuredSize]}
9093
onTransformDataChange={this._onTransformDataChange}
9194
/>
95+
<View style={styles.scaleSliderContainer}>
96+
<Text>Scale {this.state.cropScale.toFixed(2)}</Text>
97+
<Slider
98+
style={styles.scaleSlider}
99+
minimumValue={0}
100+
maximumValue={1}
101+
onValueChange={(cropScale) => this.setState({ cropScale })}
102+
value={this.state.cropScale}
103+
/>
104+
</View>
105+
92106
<TouchableHighlight
93107
accessibilityRole="button"
94108
style={styles.cropButtonTouchable}
@@ -131,9 +145,20 @@ export class SquareImageCropper extends Component<Props, State> {
131145
if (!this._transformData) {
132146
return;
133147
}
148+
const displaySize =
149+
this.state.cropScale !== 1
150+
? {
151+
width: this._transformData?.size.width * this.state.cropScale,
152+
height: this._transformData?.size.height * this.state.cropScale,
153+
}
154+
: undefined;
155+
const cropData: ImageCropData = {
156+
...this._transformData,
157+
displaySize,
158+
};
134159
const { uri } = await ImageEditor.cropImage(
135160
this.state.photo.uri,
136-
this._transformData
161+
cropData
137162
);
138163
if (uri) {
139164
this.setState({ croppedImageURI: uri });
@@ -146,7 +171,7 @@ export class SquareImageCropper extends Component<Props, State> {
146171
};
147172

148173
_reset = () => {
149-
this.setState({ croppedImageURI: null, cropError: null });
174+
this.setState({ croppedImageURI: null, cropError: null, cropScale: 1 });
150175
};
151176
}
152177

@@ -186,4 +211,12 @@ const styles = StyleSheet.create({
186211
fontSize: 16,
187212
marginBottom: 10,
188213
},
214+
scaleSlider: {
215+
width: '100%',
216+
},
217+
scaleSliderContainer: {
218+
width: '80%',
219+
alignSelf: 'center',
220+
alignItems: 'center',
221+
},
189222
});

example/yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1687,6 +1687,11 @@
16871687
version "0.0.0"
16881688
uid ""
16891689

1690+
"@react-native-community/slider@^4.5.0":
1691+
version "4.5.0"
1692+
resolved "https://registry.yarnpkg.com/@react-native-community/slider/-/slider-4.5.0.tgz#5c55488ee30060cd87100fb746b9d8655dbab04e"
1693+
integrity sha512-pyUvNTvu5IfCI5abzqRfO/dd3A009RC66RXZE6t0gyOwI/j0QDlq9VZRv3rjkpuIvNTnsYj+m5BHlh0DkSYUyA==
1694+
16901695
"@react-native/assets-registry@^0.72.0":
16911696
version "0.72.0"
16921697
resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.72.0.tgz#c82a76a1d86ec0c3907be76f7faf97a32bbed05d"

0 commit comments

Comments
 (0)