@@ -3,6 +3,8 @@ package com.kik.kikx.kikcodes.implementation
33import android.content.Context
44import android.graphics.Bitmap
55import android.graphics.Rect
6+ import android.icu.text.DateFormat
7+ import android.icu.text.SimpleDateFormat
68import android.net.Uri
79import android.os.Environment
810import androidx.camera.core.ImageAnalysis
@@ -22,8 +24,6 @@ import kotlinx.coroutines.Dispatchers
2224import kotlinx.coroutines.launch
2325import kotlinx.coroutines.withContext
2426import java.io.File
25- import java.text.DateFormat
26- import java.text.SimpleDateFormat
2727import java.util.Date
2828import java.util.Locale
2929import javax.inject.Inject
@@ -99,7 +99,6 @@ class KikCodeAnalyzer @Inject constructor(
9999
100100 private suspend fun detectCodeInImage (
101101 bitmap : Bitmap ,
102- minSectionSize : Int = 100,
103102 scan : suspend (Bitmap ) -> Result <ScannableKikCode >
104103 ): Result <ScannableKikCode > = withContext(Dispatchers .Default ) {
105104 val destinationRoot =
@@ -111,75 +110,177 @@ class KikCodeAnalyzer @Inject constructor(
111110 }
112111
113112 // Start the recursive division and scanning process
114- return @withContext divideAndScan (bitmap, destination, minSectionSize , scan)
113+ return @withContext search (bitmap, destination, 100 , scan)
115114 }
116115
117- private suspend fun divideAndScan (
116+ private suspend fun search (
118117 bitmap : Bitmap ,
119118 destination : File ,
120119 minSectionSize : Int ,
121120 scan : suspend (Bitmap ) -> Result <ScannableKikCode >,
122121 ): Result <ScannableKikCode > {
122+ // try scanning raw
123+ val raw = scan(bitmap)
124+ if (raw.isSuccess) {
125+ debugPrint(" Code found raw" )
126+ bitmap.recycle()
127+ return raw
128+ } else {
129+ debugPrint(" No Code found via raw" )
130+ }
131+
132+ // attempt quick lookup by recursively splitting image into quadrants, with increasing zoom levels
123133 val zoomLevels = listOf (1.0 , 2.0 , 5.0 , 10.0 )
134+ val recursiveSearch = processBitmapRecursively(
135+ bitmap,
136+ destination,
137+ minSectionSize,
138+ scan,
139+ zoomLevels,
140+ " "
141+ )
142+
143+ if (recursiveSearch.isSuccess) {
144+ debugPrint(" Code found via recursive lookup" )
145+ bitmap.recycle()
146+ return recursiveSearch
147+ } else {
148+ debugPrint(" No Code found via recursive lookup" )
149+ }
150+
151+ val result = slidingWindowSearch(
152+ bitmap = bitmap,
153+ windowSize = 300 ,
154+ stepSize = 150 ,
155+ scan = scan,
156+ zoomLevels = zoomLevels
157+ )
158+
159+ if (result.isSuccess) {
160+ debugPrint(" Code found via sliding window" )
161+ }
162+
163+ bitmap.recycle()
164+ return result
165+ }
166+
167+ private suspend fun slidingWindowSearch (
168+ bitmap : Bitmap ,
169+ windowSize : Int ,
170+ stepSize : Int ,
171+ scan : suspend (Bitmap ) -> Result <ScannableKikCode >,
172+ zoomLevels : List <Double >
173+ ): Result <ScannableKikCode > {
174+ val w = bitmap.width
175+ val h = bitmap.height
176+
177+ debugPrint(" search: original ${w} x${h} " )
178+
179+ for (zoomLevel in zoomLevels) {
180+ val windowWidth = (windowSize * zoomLevel).toInt()
181+ val windowHeight = (windowSize * zoomLevel).toInt()
182+
183+ for (i in 0 until w step stepSize) {
184+ for (j in 0 until h step stepSize) {
185+ val x = i.coerceAtMost(w - windowWidth)
186+ val y = j.coerceAtMost(h - windowHeight)
187+ val width = windowWidth.coerceAtMost(w - x)
188+ val height = windowHeight.coerceAtMost(h - y)
189+ val windowBitmap = Bitmap .createBitmap(
190+ bitmap,
191+ x, y,
192+ width, height
193+ )
194+
195+ debugPrint(" search: checking {x: $x , y: $y , w: $width , h: $height } @ $zoomLevel " )
196+ val result = scan(windowBitmap)
197+ windowBitmap.recycle()
124198
125- return processBitmapRecursively(bitmap, destination, minSectionSize, scan, zoomLevels)
199+ if (result.isSuccess) {
200+ debugPrint(" search: SUCCESS in {x: $x , y: $y , w: $width , h: $height } @ $zoomLevel " )
201+ return result
202+ }
203+ }
204+ }
205+ }
206+
207+ return Result .failure(KikCodeScanner .NoKikCodeFoundException ())
126208 }
127209
128210 private suspend fun processBitmapRecursively (
129211 bitmap : Bitmap ,
130212 destination : File ,
131213 minSectionSize : Int ,
132214 scan : suspend (Bitmap ) -> Result <ScannableKikCode >,
133- zoomLevels : List <Double >
215+ zoomLevels : List <Double >,
216+ regionName : String
134217 ): Result <ScannableKikCode > {
135218 val width = bitmap.width
136219 val height = bitmap.height
137220
138221 // Base case: If the bitmap is smaller than the minimum section size, process it directly
139222 if (width <= minSectionSize || height <= minSectionSize) {
140- return scanWithZoomLevels(bitmap, destination, scan, zoomLevels)
223+ return scanWithZoomLevels(bitmap, destination, scan, zoomLevels, regionName )
141224 }
142225
143- // Scan the center section first
144226 val centerRect = calculateCenterRect(width, height)
145227 val centerBitmap = Bitmap .createBitmap(bitmap, centerRect.left, centerRect.top, centerRect.width(), centerRect.height())
146228
147- val centerResult = scanWithZoomLevels(centerBitmap, destination, scan, zoomLevels)
229+ val centerResult = scanWithZoomLevels(centerBitmap, destination, scan, zoomLevels, " center " )
148230 centerBitmap.recycle()
149231
150232 if (centerResult.isSuccess) {
151233 return centerResult
152234 }
153235
154- // Divide the bitmap into left and right halves and process recursively
155- val leftHalf = Bitmap .createBitmap(bitmap, 0 , 0 , width / 2 , height)
156- val rightHalf = Bitmap .createBitmap(bitmap, width / 2 , 0 , width / 2 , height)
236+ val quadrants = splitIntoQuadrants(bitmap)
157237
158- val leftResult = processBitmapRecursively(leftHalf, destination, minSectionSize, scan, zoomLevels)
159- leftHalf.recycle()
238+ // Process each quadrant recursively
239+ for ((quadrantBitmap, name) in quadrants) {
240+ val quadrantResult = processBitmapRecursively(quadrantBitmap, destination, minSectionSize, scan, zoomLevels, name)
241+ quadrantBitmap.recycle()
160242
161- if (leftResult .isSuccess) {
162- rightHalf.recycle()
163- return leftResult
243+ if (quadrantResult .isSuccess) {
244+ return quadrantResult
245+ }
164246 }
165247
166- val rightResult = processBitmapRecursively(rightHalf, destination, minSectionSize, scan, zoomLevels )
167- rightHalf.recycle()
248+ return Result .failure( KikCodeScanner . NoKikCodeFoundException () )
249+ }
168250
169- return rightResult
251+ private fun splitIntoQuadrants (bitmap : Bitmap ): List <Pair <Bitmap , String >> {
252+ val width = bitmap.width
253+ val height = bitmap.height
254+ val halfWidth = width / 2
255+ val halfHeight = height / 2
256+
257+ val topLeft = Bitmap .createBitmap(bitmap, 0 , 0 , halfWidth, halfHeight)
258+ val topRight = Bitmap .createBitmap(bitmap, halfWidth, 0 , halfWidth, halfHeight)
259+ val bottomLeft = Bitmap .createBitmap(bitmap, 0 , halfHeight, halfWidth, halfHeight)
260+ val bottomRight = Bitmap .createBitmap(bitmap, halfWidth, halfHeight, halfWidth, halfHeight)
261+
262+ return listOf (
263+ topLeft to " topLeft" ,
264+ topRight to " topRight" ,
265+ bottomLeft to " bottomLeft" ,
266+ bottomRight to " bottomRight"
267+ )
170268 }
171269
172270 private suspend fun scanWithZoomLevels (
173271 bitmap : Bitmap ,
174272 destination : File ,
175273 scan : suspend (Bitmap ) -> Result <ScannableKikCode >,
176- zoomLevels : List <Double >
274+ zoomLevels : List <Double >,
275+ regionName : String // Use the region name to give unique filenames
177276 ): Result <ScannableKikCode > {
178277 for (zoomLevel in zoomLevels) {
179278 val zoomedBitmap = zoomBitmap(bitmap, zoomLevel)
180279 saveSegment(zoomedBitmap, destination) {
181- " section_${zoomedBitmap.width} x${zoomedBitmap.height} _zoom${zoomLevel} .png"
280+ val prefix = regionName.ifEmpty { null }?.let { " ${it} _" }
281+ " $prefix${zoomedBitmap.width} x${zoomedBitmap.height} _zoom${zoomLevel} .png"
182282 }
283+
183284 val result = scan(zoomedBitmap)
184285
185286 zoomedBitmap.recycle()
@@ -226,4 +327,8 @@ class KikCodeAnalyzer @Inject constructor(
226327 }
227328}
228329
330+ private fun debugPrint (message : String ) {
331+ if (DEBUG ) println (message)
332+ }
333+
229334private const val DEBUG = false
0 commit comments