@@ -206,10 +206,10 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
206
206
BitmapRegionDecoder .newInstance(it)
207
207
} else {
208
208
@Suppress(" DEPRECATION" ) BitmapRegionDecoder .newInstance(it, false )
209
- }
209
+ } ? : throw Error ( " Could not create bitmap decoder. Uri: $uri " )
210
210
211
- val imageHeight: Int = decoder!! .height
212
- val imageWidth: Int = decoder!! .width
211
+ val imageHeight: Int = decoder.height
212
+ val imageWidth: Int = decoder.width
213
213
val orientation = getOrientation(reactContext, Uri .parse(uri))
214
214
215
215
val (left, top) =
@@ -229,9 +229,9 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
229
229
230
230
return @use try {
231
231
val rect = Rect (left, top, right, bottom)
232
- decoder!! .decodeRegion(rect, outOptions)
232
+ decoder.decodeRegion(rect, outOptions)
233
233
} finally {
234
- decoder!! .recycle()
234
+ decoder.recycle()
235
235
}
236
236
}
237
237
}
@@ -262,68 +262,79 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
262
262
): Bitmap ? {
263
263
Assertions .assertNotNull(outOptions)
264
264
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 " )
277
274
278
- val orientation = getOrientation(reactContext, Uri .parse(uri))
279
- val (x, y) =
280
- 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
284
- else -> xPos to yPos
285
- }
275
+ val orientation = getOrientation(reactContext, Uri .parse(uri))
276
+ val (x, y) =
277
+ when (orientation) {
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
281
+ else -> xPos to yPos
282
+ }
286
283
287
- val (width, height) =
288
- when (orientation) {
289
- 90 ,
290
- 270 -> rectHeight to rectWidth
291
- else -> rectWidth to rectHeight
292
- }
293
- val (targetWidth, targetHeight) =
294
- when (orientation) {
295
- 90 ,
296
- 270 -> outputHeight to outputWidth
297
- else -> outputWidth to outputHeight
298
- }
284
+ val (width, height) =
285
+ when (orientation) {
286
+ 90 ,
287
+ 270 -> rectHeight to rectWidth
288
+ else -> rectWidth to rectHeight
289
+ }
290
+ val (targetWidth, targetHeight) =
291
+ when (orientation) {
292
+ 90 ,
293
+ 270 -> outputHeight to outputWidth
294
+ else -> outputWidth to outputHeight
295
+ }
299
296
300
- val cropRectRatio = width / height.toFloat()
301
- val targetRatio = targetWidth / targetHeight.toFloat()
302
- val isCropRatioLargerThanTargetRatio = cropRectRatio > targetRatio
303
- val newWidth =
304
- if (isCropRatioLargerThanTargetRatio) height * targetRatio else width.toFloat()
305
- val newHeight =
306
- if (isCropRatioLargerThanTargetRatio) height.toFloat() else width / targetRatio
307
- val newX = if (isCropRatioLargerThanTargetRatio) x + (width - newWidth) / 2 else x.toFloat()
308
- val newY =
309
- if (isCropRatioLargerThanTargetRatio) y.toFloat() else y + (height - newHeight) / 2
310
- val scale =
311
- if (isCropRatioLargerThanTargetRatio) targetHeight / height.toFloat()
312
- else targetWidth / width.toFloat()
313
-
314
- // Decode the bitmap. We have to open the stream again, like in the example linked above.
315
- // Is there a way to just continue reading from the stream?
316
- outOptions.inSampleSize = getDecodeSampleSize(width, height, targetWidth, targetHeight)
317
-
318
- val cropX = (newX / outOptions.inSampleSize.toFloat()).roundToInt()
319
- val cropY = (newY / outOptions.inSampleSize.toFloat()).roundToInt()
320
- val cropWidth = (newWidth / outOptions.inSampleSize.toFloat()).roundToInt()
321
- val cropHeight = (newHeight / outOptions.inSampleSize.toFloat()).roundToInt()
322
- val cropScale = scale * outOptions.inSampleSize
323
- val scaleMatrix = Matrix ().apply { setScale(cropScale, cropScale) }
324
- val filter = true
325
-
326
- return Bitmap .createBitmap(bitmap, cropX, cropY, cropWidth, cropHeight, scaleMatrix, filter)
297
+ val cropRectRatio = width / height.toFloat()
298
+ val targetRatio = targetWidth / targetHeight.toFloat()
299
+ val isCropRatioLargerThanTargetRatio = cropRectRatio > targetRatio
300
+ val newWidth =
301
+ if (isCropRatioLargerThanTargetRatio) height * targetRatio else width.toFloat()
302
+ val newHeight =
303
+ if (isCropRatioLargerThanTargetRatio) height.toFloat() else width / targetRatio
304
+ val newX =
305
+ if (isCropRatioLargerThanTargetRatio) x + (width - newWidth) / 2 else x.toFloat()
306
+ val newY =
307
+ if (isCropRatioLargerThanTargetRatio) y.toFloat() else y + (height - newHeight) / 2
308
+ val scale =
309
+ if (isCropRatioLargerThanTargetRatio) targetHeight / height.toFloat()
310
+ else targetWidth / width.toFloat()
311
+
312
+ // Decode the bitmap. We have to open the stream again, like in the example linked
313
+ // above.
314
+ // Is there a way to just continue reading from the stream?
315
+ outOptions.inSampleSize = getDecodeSampleSize(width, height, targetWidth, targetHeight)
316
+
317
+ val cropX = (newX / outOptions.inSampleSize.toFloat()).roundToInt()
318
+ val cropY = (newY / outOptions.inSampleSize.toFloat()).roundToInt()
319
+ val cropWidth = (newWidth / outOptions.inSampleSize.toFloat()).roundToInt()
320
+ val cropHeight = (newHeight / outOptions.inSampleSize.toFloat()).roundToInt()
321
+ val cropScale = scale * outOptions.inSampleSize
322
+ val scaleMatrix = Matrix ().apply { setScale(cropScale, cropScale) }
323
+ val filter = true
324
+
325
+ val rect = Rect (0 , 0 , decoder.width, decoder.height)
326
+ val bitmap = decoder.decodeRegion(rect, outOptions)
327
+
328
+ return Bitmap .createBitmap(
329
+ bitmap,
330
+ cropX,
331
+ cropY,
332
+ cropWidth,
333
+ cropHeight,
334
+ scaleMatrix,
335
+ filter
336
+ )
337
+ }
327
338
}
328
339
329
340
private fun openBitmapInputStream (uri : String , headers : HashMap <String , Any ?>? ): InputStream ? {
0 commit comments