@@ -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,68 +262,79 @@ 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
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+ }
286283
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+ }
299296
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+ }
327338 }
328339
329340 private fun openBitmapInputStream (uri : String , headers : HashMap <String , Any ?>? ): InputStream ? {
0 commit comments