You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tauri-plugins-workspace/plugins/barcode-scanner/android/src/main/java/GraphicOverlay.kt

192 lines
6.1 KiB

// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
package app.tauri.barcodescanner
import android.content.Context
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import com.google.android.gms.common.internal.Preconditions
class GraphicOverlay: View {
private val lock = Any()
private val graphics: MutableList<Graphic> = ArrayList()
private val transformationMatrix = Matrix()
private var imageWidth = 0
private var imageHeight = 0
private var scaleFactor = 1.0f
private var postScaleWidthOffset = 0f
private var postScaleHeightOffset = 0f
private var isImageFlipped = false
private var needUpdateTransformation = true
abstract class Graphic(private val overlay: GraphicOverlay) {
abstract fun draw(canvas: Canvas?)
protected fun drawRect(
canvas: Canvas,
left: Float,
top: Float,
right: Float,
bottom: Float,
paint: Paint?
) {
canvas.drawRect(left, top, right, bottom, paint!!)
}
protected fun drawText(canvas: Canvas, text: String?, x: Float, y: Float, paint: Paint?) {
canvas.drawText(text!!, x, y, paint!!)
}
/** Adjusts the supplied value from the image scale to the view scale. */
fun scale(imagePixel: Float): Float {
return imagePixel * overlay.scaleFactor
}
val applicationContext
get() = overlay.context.applicationContext
fun isImageFlipped(): Boolean {
return overlay.isImageFlipped
}
fun translateX(x: Float): Float {
return if (overlay.isImageFlipped) {
overlay.width - (scale(x) - overlay.postScaleWidthOffset)
} else {
scale(x) - overlay.postScaleWidthOffset
}
}
fun translateY(y: Float): Float {
return scale(y) - overlay.postScaleHeightOffset
}
fun getTransformationMatrix(): Matrix {
return overlay.transformationMatrix
}
fun postInvalidate() {
overlay.postInvalidate()
}
fun updatePaintColorByZValue(
paint: Paint,
canvas: Canvas,
visualizeZ: Boolean,
rescaleZForVisualization: Boolean,
zInImagePixel: Float,
zMin: Float,
zMax: Float
) {
if (!visualizeZ) {
return
}
val zLowerBoundInScreenPixel: Float
val zUpperBoundInScreenPixel: Float
if (rescaleZForVisualization) {
zLowerBoundInScreenPixel = (-0.001f).coerceAtMost(scale(zMin))
zUpperBoundInScreenPixel = 0.001f.coerceAtLeast(scale(zMax))
} else {
val defaultRangeFactor = 1f
zLowerBoundInScreenPixel = -defaultRangeFactor * canvas.width
zUpperBoundInScreenPixel = defaultRangeFactor * canvas.width
}
val zInScreenPixel = scale(zInImagePixel)
if (zInScreenPixel < 0) {
val v = (zInScreenPixel / zLowerBoundInScreenPixel * 255).toInt()
paint.setARGB(0, 0, 255, 0)
} else {
val v = (zInScreenPixel / zUpperBoundInScreenPixel * 255).toInt()
paint.setARGB(0, 0, 255, 0)
}
}
}
constructor(context: Context): super(context)
constructor(context: Context, attrs: AttributeSet): super(context, attrs) {
addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
needUpdateTransformation = true
}
}
fun clear() {
synchronized(lock) { graphics.clear() }
postInvalidate()
}
fun add(graphic: Graphic) {
synchronized(lock) { graphics.add(graphic) }
}
fun remove(graphic: Graphic) {
synchronized(lock) { graphics.remove(graphic) }
postInvalidate()
}
fun setImageSourceInfo(imageWidth: Int, imageHeight: Int, isFlipped: Boolean) {
Preconditions.checkState(imageWidth > 0, "image width must be positive")
Preconditions.checkState(imageHeight > 0, "image height must be positive")
synchronized(lock) {
this.imageWidth = imageWidth
this.imageHeight = imageHeight
isImageFlipped = isFlipped
needUpdateTransformation = true
}
postInvalidate()
}
fun getImageWidth(): Int {
return imageWidth
}
fun getImageHeight(): Int {
return imageHeight
}
private fun updateTransformationIfNeeded() {
if (!needUpdateTransformation || imageWidth <= 0 || imageHeight <= 0) {
return
}
val viewAspectRatio = width.toFloat() / height
val imageAspectRatio = imageWidth.toFloat() / imageHeight
postScaleWidthOffset = 0f
postScaleHeightOffset = 0f
if (viewAspectRatio > imageAspectRatio) {
// The image needs to be vertically cropped to be displayed in this view.
scaleFactor = width.toFloat() / imageWidth
postScaleHeightOffset = (width.toFloat() / imageAspectRatio - height) / 2
} else {
// The image needs to be horizontally cropped to be displayed in this view.
scaleFactor = height.toFloat() / imageHeight
postScaleWidthOffset = (height.toFloat() * imageAspectRatio - width) / 2
}
transformationMatrix.reset()
transformationMatrix.setScale(scaleFactor, scaleFactor)
transformationMatrix.postTranslate(-postScaleWidthOffset, -postScaleHeightOffset)
if (isImageFlipped) {
transformationMatrix.postScale(-1f, 1f, width / 2f, height / 2f)
}
needUpdateTransformation = false
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
synchronized(lock) {
updateTransformationIfNeeded()
for (graphic in graphics) {
graphic.draw(canvas)
}
}
}
}