package io.scanbot.sdk.reactnative.components.cropping_view import android.content.Context import android.graphics.Color import android.util.AttributeSet import android.view.ViewGroup import android.widget.FrameLayout import androidx.core.graphics.createBitmap import io.scanbot.sdk.image.ImageRef import io.scanbot.sdk.image.ImageRotation import io.scanbot.sdk.process.ImageProcessor import io.scanbot.sdk.reactnative.extensions.layoutView import io.scanbot.sdk.reactnative.extensions.sendEvent import io.scanbot.sdk.reactnative.extensions.toPx import io.scanbot.sdk.reactnative.models.CroppingViewCroppedAreaResult import io.scanbot.sdk.reactnative.models.CroppingViewDefaultConfiguration.DEFAULT_ANCHOR_POINTS_COLOR import io.scanbot.sdk.reactnative.models.CroppingViewDefaultConfiguration.DEFAULT_EDGE_COLOR import io.scanbot.sdk.reactnative.models.CroppingViewDefaultConfiguration.DEFAULT_EDGE_ON_LINE_COLOR import io.scanbot.sdk.ui.EditPolygonImageView import io.scanbot.sdk.ui.MagnifierView import io.scanbot.sdk.util.PolygonHelper import io.scanbot.sdk_wrapper.document.operations.doc_detection.SBDDocumentDetection import io.scanbot.sdk_wrapper.exceptions.SBWrapperError import io.scanbot.sdk_wrapper.extensions.toImageRef import io.scanbot.sdk_wrapper.extensions.toValidUri import java.util.UUID import java.util.concurrent.Executors class ScanbotCroppingView( context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int ) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { constructor(context: Context) : this(context, null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : this( context, attrs, defStyleAttr, 0 ) val editPolygonImageView = EditPolygonImageView(context, attrs) private val magnifierView = MagnifierView(context, attrs) private var editingImage: ImageRef? = null private val executor = Executors.newSingleThreadExecutor() init { layoutParams = ViewGroup.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT ) setBackgroundColor(Color.BLACK) initCroppingView() addView(editPolygonImageView) addView(magnifierView) } private fun initCroppingView() { configureDefaults() editPolygonImageView.setImageBitmap(createBitmap(100, 100)) editPolygonImageView.polygon = PolygonHelper.getFullPolygon() attachMagnifier() } private fun configureDefaults() { editPolygonImageView.setBackgroundColor(Color.BLACK) editPolygonImageView.setEdgeColor(DEFAULT_EDGE_COLOR) editPolygonImageView.setEdgeWidth(2.toPx(this.resources).toFloat()) editPolygonImageView.setEdgeColorOnLine(DEFAULT_EDGE_ON_LINE_COLOR) editPolygonImageView.setAnchorPointsColor(DEFAULT_ANCHOR_POINTS_COLOR) } private fun attachMagnifier() { editPolygonImageView.setMagnifier(magnifierView) magnifierView.setupMagnifier(editPolygonImageView) } // region Sources private fun setEditingBitmap(image: ImageRef) { try { editPolygonImageView.setImageBitmap(image.toBitmap().getOrThrow()) attachMagnifier() postDelayed({ detectPolygon() }, 500) } catch (ex: Throwable) { sendEvent(CroppingViewErrorEvent(ex)) } } fun setEditingImage(fileUri: String) { try { fileUri.toValidUri().toImageRef().let { editingImage = it setEditingBitmap(it) } } catch (ex: Throwable) { sendEvent(CroppingViewErrorEvent(ex)) } } fun setEditingImageRef(uuid: String) { try { if (uuid.isNotEmpty()) { ImageRef.fromSerializedReference(UUID.fromString(uuid)).let { editingImage = it setEditingBitmap(it) } } } catch (ex: Throwable) { sendEvent(CroppingViewErrorEvent(ex)) } } // endregion // region Commands fun extractCroppedArea() { executor.execute { editingImage?.let { try { val imageRotation = when (editPolygonImageView.rotation.toInt()) { 90, -270 -> ImageRotation.CLOCKWISE_90 -90, 270 -> ImageRotation.COUNTERCLOCKWISE_90 180, -180 -> ImageRotation.CLOCKWISE_180 else -> ImageRotation.NONE } ImageProcessor(it) .crop(editPolygonImageView.polygon) .rotate(imageRotation) .processedImageRef() ?.let { croppedImage -> sendEvent( CroppingViewExtractCroppedAreaEvent( CroppingViewCroppedAreaResult( polygon = editPolygonImageView.polygon, rotation = imageRotation, sourceImage = croppedImage ) ) ) } ?: run { sendEvent(CroppingViewErrorEvent(SBWrapperError.ResultError("Failed to crop the image"))) } } catch (ex: Throwable) { sendEvent(CroppingViewErrorEvent(ex)) } } } } fun detectPolygon() { executor.execute { editingImage?.let { try { val documentDetectionResult = SBDDocumentDetection.scanFromBitmap(it.toBitmap().getOrThrow()) editPolygonImageView.setLines( documentDetectionResult.horizontalLinesNormalized, documentDetectionResult.verticalLinesNormalized ) editPolygonImageView.polygon = documentDetectionResult.pointsNormalized editPolygonImageView.requestLayout() } catch (ex: Throwable) { sendEvent(CroppingViewErrorEvent(ex)) } } } } fun resetPolygon() { editPolygonImageView.polygon = PolygonHelper.getFullPolygon() } fun rotate(clockwise: Boolean) { if (clockwise) { editPolygonImageView.rotateClockwise() } else { editPolygonImageView.rotateCounterClockwise() } } // endregion fun removeAttachedListeners() { executor.shutdownNow() editingImage?.close() } override fun requestLayout() { super.requestLayout() post { layoutView() } } }