1 /* 2 * Copyright 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.camera.integration.diagnose 18 19 import android.graphics.PointF 20 import android.graphics.RectF 21 import android.util.Log 22 import android.util.Size 23 import com.google.mlkit.vision.barcode.common.Barcode 24 25 /** 26 * Calibration object that checks camera alignment with list of {@link Barcode}. 27 * 28 * @param previewViewSize - size of the devices' preview view. 29 */ 30 class Calibration(private val previewViewSize: Size) { 31 32 // barcodeCoordinates 33 private lateinit var barCodes: List<Barcode> 34 private var topLeft: PointF? = null 35 private var topRight: PointF? = null 36 private var bottomLeft: PointF? = null 37 private var bottomRight: PointF? = null 38 39 // gridlines to be drawn 40 var topGrid: Pair<PointF, PointF>? = null 41 var bottomGrid: Pair<PointF, PointF>? = null 42 var leftGrid: Pair<PointF, PointF>? = null 43 var rightGrid: Pair<PointF, PointF>? = null 44 45 var topY: Float? = null 46 var bottomY: Float? = null 47 var leftX: Float? = null 48 var rightX: Float? = null 49 50 // threshold box 51 // TODO: switch back to private if I don't need to draw threshold box 52 var thresholdTopLeft: RectF? = null 53 var thresholdTopRight: RectF? = null 54 var thresholdBottomLeft: RectF? = null 55 var thresholdBottomRight: RectF? = null 56 57 var isAligned: Boolean = false 58 analyzenull59 fun analyze(barCodes: List<Barcode>) { 60 setBarcodes(barCodes) 61 calculateBarcodeCoordinates() 62 calculateGridLines() 63 isAligned = checkAlignment() 64 Log.d(TAG, "isAligned = $isAligned") 65 } 66 setBarcodesnull67 private fun setBarcodes(barCodes: List<Barcode>) { 68 this.barCodes = barCodes 69 } 70 calculateBarcodeCoordinatesnull71 private fun calculateBarcodeCoordinates() { 72 topLeft = findCenterPoint("top-left") 73 topRight = findCenterPoint("top-right") 74 bottomLeft = findCenterPoint("bottom-left") 75 bottomRight = findCenterPoint("bottom-right") 76 } 77 78 /** 79 * Calculate the target grid lines based on the average distance between x & y coordinates 80 * corresponding {@link Barcode}. 81 */ calculateGridLinesnull82 private fun calculateGridLines() { 83 if (!hasBarcodes()) { 84 Log.d(TAG, " hasBarcodes = ${hasBarcodes()}, has ${barCodes.size} : $barCodes") 85 return 86 } 87 Log.d(TAG, "has all barcodes") 88 89 // calculate grid 90 topY = (topLeft!!.y + topRight!!.y) / 2 91 topGrid = Pair(PointF(0F, topY!!), PointF(previewViewSize.width.toFloat(), topY!!)) 92 bottomY = (bottomLeft!!.y + bottomRight!!.y) / 2 93 bottomGrid = Pair(PointF(0F, bottomY!!), PointF(previewViewSize.width.toFloat(), bottomY!!)) 94 leftX = (topLeft!!.x + bottomLeft!!.x) / 2 95 leftGrid = Pair(PointF(leftX!!, 0F), PointF(leftX!!, previewViewSize.height.toFloat())) 96 rightX = (topRight!!.x + bottomRight!!.x) / 2 97 rightGrid = Pair(PointF(rightX!!, 0F), PointF(rightX!!, previewViewSize.height.toFloat())) 98 } 99 getThresholdBoxnull100 private fun getThresholdBox(x: Float, y: Float): RectF { 101 return RectF(x - THRESHOLD, y - THRESHOLD, x + THRESHOLD, y + THRESHOLD) 102 } 103 containPointnull104 private fun containPoint(point: PointF?, thresholdBox: RectF?): Boolean { 105 return point?.let { thresholdBox?.contains(it.x, it.y) } == true 106 } 107 108 /** 109 * @return true if all {@link Barcode} center points are found within the corresponding 110 * threshold boxes calculated around intersection points of the target grid line. 111 */ checkAlignmentnull112 private fun checkAlignment(): Boolean { 113 // create threshold boxes around the grid's intersection points 114 topY?.let { 115 thresholdTopLeft = leftX?.let { getThresholdBox(it, topY!!) } 116 thresholdTopRight = rightX?.let { getThresholdBox(it, topY!!) } 117 } 118 bottomY?.let { 119 thresholdBottomLeft = leftX?.let { getThresholdBox(it, bottomY!!) } 120 thresholdBottomRight = rightX?.let { getThresholdBox(it, bottomY!!) } 121 } 122 // check if all barcode center points are within threshold 123 return containPoint(topLeft, thresholdTopLeft) && 124 containPoint(topRight, thresholdTopRight) && 125 containPoint(bottomLeft, thresholdBottomLeft) && 126 containPoint(bottomRight, thresholdBottomRight) 127 } 128 129 // check if all 4 barcodes are detected hasBarcodesnull130 private fun hasBarcodes(): Boolean { 131 return topLeft != null && topRight != null && bottomLeft != null && bottomRight != null 132 } 133 findCenterPointnull134 private fun findCenterPoint(position: String): PointF? { 135 for (barcode in barCodes) { 136 val boundingBox = barcode.boundingBox 137 if (barcode.rawValue == position && boundingBox != null) { 138 return PointF(boundingBox.exactCenterX(), boundingBox.exactCenterY()) 139 } 140 } 141 return null 142 } 143 144 companion object { 145 private const val TAG = "Calibration" 146 private const val THRESHOLD = 4F 147 } 148 } 149