• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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 android.tools.common.datatypes
18 
19 import android.tools.common.datatypes.Region.Companion.from
20 import kotlin.js.JsExport
21 import kotlin.js.JsName
22 import kotlin.math.min
23 
24 /**
25  * Wrapper for RegionProto (frameworks/native/services/surfaceflinger/layerproto/common.proto)
26  *
27  * Implementation based android.graphics.Region's native implementation found in SkRegion.cpp
28  *
29  * This class is used by flicker and Winscope
30  *
31  * It has a single constructor and different [from] functions on its companion because JS doesn't
32  * support constructor overload
33  */
34 @JsExport
35 class Region(rects: Array<Rect> = arrayOf()) : DataType() {
36     private var fBounds = Rect.EMPTY
37     private var fRunHead: RunHead? = RunHead(isEmptyHead = true)
38 
39     init {
40         if (rects.isEmpty()) {
41             setEmpty()
42         } else {
43             for (rect in rects) {
44                 union(rect)
45             }
46         }
47     }
48 
49     @JsName("rects")
50     val rects
51         get() = getRectsFromString(toString())
52 
53     @JsName("width")
54     val width: Int
55         get() = bounds.width
56     @JsName("height")
57     val height: Int
58         get() = bounds.height
59 
60     // if null we are a rect not empty
61     override val isEmpty
62         get() = fRunHead?.isEmptyHead ?: false
63     @JsName("bounds")
64     val bounds
65         get() = fBounds
66 
67     /** Set the region to the empty region */
68     @JsName("setEmpty")
69     fun setEmpty(): Boolean {
70         fBounds = Rect.EMPTY
71         fRunHead = RunHead(isEmptyHead = true)
72 
73         return false
74     }
75 
76     /** Set the region to the specified region. */
77     @JsName("setRegion")
78     fun set(region: Region): Boolean {
79         fBounds = region.fBounds.clone()
80         fRunHead = region.fRunHead?.clone()
81         return !(fRunHead?.isEmptyHead ?: false)
82     }
83 
84     /** Set the region to the specified rectangle */
85     @JsName("setRect")
86     fun set(r: Rect): Boolean {
87         return if (r.isEmpty || RUN_TYPE_SENTINEL == r.right || RUN_TYPE_SENTINEL == r.bottom) {
88             this.setEmpty()
89         } else {
90             fBounds = r
91             fRunHead = null
92             true
93         }
94     }
95 
96     /** Set the region to the specified rectangle */
97     @JsName("set")
98     operator fun set(left: Int, top: Int, right: Int, bottom: Int): Boolean {
99         return set(Rect.withoutCache(left, top, right, bottom))
100     }
101 
102     @JsName("isRect")
103     fun isRect(): Boolean {
104         return fRunHead == null
105     }
106 
107     @JsName("isComplex")
108     fun isComplex(): Boolean {
109         return !this.isEmpty && !this.isRect()
110     }
111 
112     @JsName("contains")
113     fun contains(x: Int, y: Int): Boolean {
114         if (!fBounds.contains(x, y)) {
115             return false
116         }
117         if (this.isRect()) {
118             return true
119         }
120         require(this.isComplex())
121 
122         val runs = fRunHead!!.findScanline(y)
123 
124         // Skip the Bottom and IntervalCount
125         var runsIndex = 2
126 
127         // Just walk this scanline, checking each interval. The X-sentinel will
128         // appear as a left-interval (runs[0]) and should abort the search.
129         //
130         // We could do a bsearch, using interval-count (runs[1]), but need to time
131         // when that would be worthwhile.
132         //
133         while (true) {
134             if (x < runs[runsIndex]) {
135                 break
136             }
137             if (x < runs[runsIndex + 1]) {
138                 return true
139             }
140             runsIndex += 2
141         }
142         return false
143     }
144 
145     class Iterator(private val rgn: Region) {
146         private var done: Boolean
147         private var rect: Rect
148         private var fRuns: Array<Int>? = null
149         private var fRunsIndex = 0
150 
151         init {
152             fRunsIndex = 0
153             if (rgn.isEmpty) {
154                 rect = Rect.EMPTY
155                 done = true
156             } else {
157                 done = false
158                 if (rgn.isRect()) {
159                     rect = rgn.fBounds
160                     fRuns = null
161                 } else {
162                     fRuns = rgn.fRunHead!!.readonlyRuns
163                     rect = Rect.withoutCache(fRuns!![3], fRuns!![0], fRuns!![4], fRuns!![1])
164                     fRunsIndex = 5
165                 }
166             }
167         }
168 
169         fun next() {
170             if (done) {
171                 return
172             }
173 
174             if (fRuns == null) { // rect case
175                 done = true
176                 return
177             }
178 
179             val runs = fRuns!!
180             var runsIndex = fRunsIndex
181 
182             if (runs[runsIndex] < RUN_TYPE_SENTINEL) { // valid X value
183                 rect =
184                     Rect.withoutCache(runs[runsIndex], rect.top, runs[runsIndex + 1], rect.bottom)
185                 runsIndex += 2
186             } else { // we're at the end of a line
187                 runsIndex += 1
188                 if (runs[runsIndex] < RUN_TYPE_SENTINEL) { // valid Y value
189                     val intervals = runs[runsIndex + 1]
190                     if (0 == intervals) { // empty line
191                         rect =
192                             Rect.withoutCache(rect.left, runs[runsIndex], rect.right, rect.bottom)
193                         runsIndex += 3
194                     } else {
195                         rect = Rect.withoutCache(rect.left, rect.bottom, rect.right, rect.bottom)
196                     }
197 
198                     assertSentinel(runs[runsIndex + 2], false)
199                     assertSentinel(runs[runsIndex + 3], false)
200                     rect =
201                         Rect.withoutCache(
202                             runs[runsIndex + 2],
203                             rect.top,
204                             runs[runsIndex + 3],
205                             runs[runsIndex]
206                         )
207                     runsIndex += 4
208                 } else { // end of rgn
209                     done = true
210                 }
211             }
212             fRunsIndex = runsIndex
213         }
214 
215         @JsName("done")
216         fun done(): Boolean {
217             return done
218         }
219 
220         fun rect(): Rect {
221             return rect
222         }
223     }
224 
225     override fun doPrintValue(): String {
226         val iter = Iterator(this)
227         val result = StringBuilder("SkRegion(")
228         while (!iter.done()) {
229             val r = iter.rect()
230             result.append("(${r.left},${r.top},${r.right},${r.bottom})")
231             iter.next()
232         }
233         result.append(")")
234         return result.toString()
235     }
236 
237     // the native values for these must match up with the enum in SkRegion.h
238     enum class Op(val nativeInt: Int) {
239         DIFFERENCE(0),
240         INTERSECT(1),
241         UNION(2),
242         XOR(3),
243         REVERSE_DIFFERENCE(4),
244         REPLACE(5)
245     }
246 
247     fun union(r: Rect): Boolean {
248         return op(r, Op.UNION)
249     }
250 
251     fun toRectF(): RectF {
252         return bounds.toRectF()
253     }
254 
255     private fun oper(rgnA: Region, rgnB: Region, op: Op): Boolean {
256         // simple cases
257         when (op) {
258             Op.REPLACE -> {
259                 this.set(rgnB)
260                 return !rgnB.isEmpty
261             }
262             Op.REVERSE_DIFFERENCE -> {
263                 // collapse difference and reverse-difference into just difference
264                 return this.oper(rgnB, rgnA, Op.DIFFERENCE)
265             }
266             Op.DIFFERENCE -> {
267                 if (rgnA.isEmpty) {
268                     this.setEmpty()
269                     return false
270                 }
271                 if (rgnB.isEmpty || rgnA.bounds.intersection(rgnB.bounds).isEmpty) {
272                     this.set(rgnA)
273                     return !rgnA.isEmpty
274                 }
275                 if (rgnB.isRect() && rgnB.bounds.contains(rgnA.bounds)) {
276                     this.setEmpty()
277                     return false
278                 }
279             }
280             Op.INTERSECT -> {
281                 when {
282                     rgnA.isEmpty ||
283                         rgnB.isEmpty ||
284                         rgnA.bounds.intersection(rgnB.bounds).isEmpty -> {
285                         this.setEmpty()
286                         return false
287                     }
288                     rgnA.isRect() && rgnB.isRect() -> {
289                         val rectIntersection = rgnA.bounds.intersection(rgnB.bounds)
290                         this.set(rgnA.bounds.intersection(rgnB.bounds))
291                         return !rectIntersection.isEmpty
292                     }
293                     rgnA.isRect() && rgnA.bounds.contains(rgnB.bounds) -> {
294                         this.set(rgnB)
295                         return !rgnB.isEmpty
296                     }
297                     rgnB.isRect() && rgnB.bounds.contains(rgnA.bounds) -> {
298                         this.set(rgnA)
299                         return !rgnA.isEmpty
300                     }
301                 }
302             }
303             Op.UNION -> {
304                 when {
305                     rgnA.isEmpty -> {
306                         this.set(rgnB)
307                         return !rgnB.isEmpty
308                     }
309                     rgnB.isEmpty -> {
310                         this.set(rgnA)
311                         return !rgnA.isEmpty
312                     }
313                     rgnA.isRect() && rgnA.bounds.contains(rgnB.bounds) -> {
314                         this.set(rgnA)
315                         return !rgnA.isEmpty
316                     }
317                     rgnB.isRect() && rgnB.bounds.contains(rgnA.bounds) -> {
318                         this.set(rgnB)
319                         return !rgnB.isEmpty
320                     }
321                 }
322             }
323             Op.XOR -> {
324                 when {
325                     rgnA.isEmpty -> {
326                         this.set(rgnB)
327                         return !rgnB.isEmpty
328                     }
329                     rgnB.isEmpty -> {
330                         this.set(rgnA)
331                         return !rgnA.isEmpty
332                     }
333                 }
334             }
335         }
336 
337         val array = RunArray()
338         val count = operate(rgnA.getRuns(), rgnB.getRuns(), array, op)
339         require(count <= array.count)
340         return this.setRuns(array, count)
341     }
342 
343     class RunArray {
344         private val kRunArrayStackCount = 256
345         var runs: Array<Int> = Array(kRunArrayStackCount) { 0 }
346         private var fCount: Int = kRunArrayStackCount
347 
348         val count: Int
349             get() = fCount
350 
351         operator fun get(i: Int): Int {
352             return runs[i]
353         }
354 
355         fun resizeToAtLeast(_count: Int) {
356             var count = _count
357             if (count > fCount) {
358                 // leave at least 50% extra space for future growth.
359                 count += count shr 1
360                 val newRuns = Array(count) { 0 }
361                 runs.forEachIndexed { index, value -> newRuns[index] = value }
362                 runs = newRuns
363                 fCount = count
364             }
365         }
366 
367         operator fun set(i: Int, value: Int) {
368             runs[i] = value
369         }
370 
371         fun subList(startIndex: Int, stopIndex: Int): RunArray {
372             val subRuns = RunArray()
373             subRuns.resizeToAtLeast(this.fCount)
374             for (i in startIndex until stopIndex) {
375                 subRuns.runs[i - startIndex] = this.runs[i]
376             }
377             return subRuns
378         }
379 
380         fun clone(): RunArray {
381             val clone = RunArray()
382             clone.runs = runs.copyOf()
383             clone.fCount = fCount
384             return clone
385         }
386     }
387 
388     /**
389      * Set this region to the result of performing the Op on the specified regions. Return true if
390      * the result is not empty.
391      */
392     @JsName("opRegions")
393     fun op(rgnA: Region, rgnB: Region, op: Op): Boolean {
394         return this.oper(rgnA, rgnB, op)
395     }
396 
397     private fun getRuns(): Array<Int> {
398         val runs: Array<Int>
399         if (this.isEmpty) {
400             runs = Array(RECT_REGION_RUNS) { 0 }
401             runs[0] = RUN_TYPE_SENTINEL
402         } else if (this.isRect()) {
403             runs = buildRectRuns(fBounds)
404         } else {
405             runs = fRunHead!!.readonlyRuns
406         }
407 
408         return runs
409     }
410 
411     private fun buildRectRuns(bounds: Rect): Array<Int> {
412         val runs = Array(RECT_REGION_RUNS) { 0 }
413         runs[0] = bounds.top
414         runs[1] = bounds.bottom
415         runs[2] = 1 // 1 interval for this scanline
416         runs[3] = bounds.left
417         runs[4] = bounds.right
418         runs[5] = RUN_TYPE_SENTINEL
419         runs[6] = RUN_TYPE_SENTINEL
420         return runs
421     }
422 
423     class RunHead(val isEmptyHead: Boolean = false) {
424         fun setRuns(runs: RunArray, count: Int) {
425             this.runs = runs
426             this.fRunCount = count
427         }
428 
429         fun computeRunBounds(): Rect {
430             var runsIndex = 0
431             val top = runs[runsIndex]
432             runsIndex++
433 
434             var bot: Int
435             var ySpanCount = 0
436             var intervalCount = 0
437             var left = Int.MAX_VALUE
438             var right = Int.MIN_VALUE
439 
440             do {
441                 bot = runs[runsIndex]
442                 runsIndex++
443                 require(bot < RUN_TYPE_SENTINEL)
444                 ySpanCount += 1
445 
446                 val intervals = runs[runsIndex]
447                 runsIndex++
448                 require(intervals >= 0)
449                 require(intervals < RUN_TYPE_SENTINEL)
450 
451                 if (intervals > 0) {
452                     val L = runs[runsIndex]
453                     require(L < RUN_TYPE_SENTINEL)
454                     if (left > L) {
455                         left = L
456                     }
457 
458                     runsIndex += intervals * 2
459                     val R = runs[runsIndex - 1]
460                     require(R < RUN_TYPE_SENTINEL)
461                     if (right < R) {
462                         right = R
463                     }
464 
465                     intervalCount += intervals
466                 }
467                 require(RUN_TYPE_SENTINEL == runs[runsIndex])
468                 runsIndex += 1 // skip x-sentinel
469 
470                 // test Y-sentinel
471             } while (RUN_TYPE_SENTINEL > runs[runsIndex])
472 
473             fYSpanCount = ySpanCount
474             fIntervalCount = intervalCount
475 
476             return Rect.from(left, top, right, bot)
477         }
478 
479         fun clone(): RunHead {
480             val clone = RunHead(isEmptyHead)
481             clone.fIntervalCount = fIntervalCount
482             clone.fYSpanCount = fYSpanCount
483             clone.runs = runs.clone()
484             clone.fRunCount = fRunCount
485             return clone
486         }
487 
488         /**
489          * Return the scanline that contains the Y value. This requires that the Y value is already
490          * known to be contained within the bounds of the region, and so this routine never returns
491          * nullptr.
492          *
493          * It returns the beginning of the scanline, starting with its Bottom value.
494          */
495         fun findScanline(y: Int): Array<Int> {
496             val runs = readonlyRuns
497 
498             // if the top-check fails, we didn't do a quick check on the bounds
499             require(y >= runs[0])
500 
501             var runsIndex = 1 // skip top-Y
502             while (true) {
503                 val bottom = runs[runsIndex]
504                 // If we hit this, we've walked off the region, and our bounds check
505                 // failed.
506                 require(bottom < RUN_TYPE_SENTINEL)
507                 if (y < bottom) {
508                     break
509                 }
510                 runsIndex = skipEntireScanline(runsIndex)
511             }
512 
513             val newArray = Array(runs.size - runsIndex) { 0 }
514             runs.copyInto(
515                 newArray,
516                 destinationOffset = 0,
517                 startIndex = runsIndex,
518                 endIndex = runs.size - runsIndex
519             )
520             return newArray
521         }
522 
523         /**
524          * Given a scanline (including its Bottom value at runs[0]), return the next scanline.
525          * Asserts that there is one (i.e. runs[0] < Sentinel)
526          */
527         fun skipEntireScanline(_runsIndex: Int): Int {
528             var runsIndex = _runsIndex
529             // we are not the Y Sentinel
530             require(runs[runsIndex] < RUN_TYPE_SENTINEL)
531 
532             val intervals = runs[runsIndex + 1]
533             require(runs[runsIndex + 2 + intervals * 2] == RUN_TYPE_SENTINEL)
534 
535             // skip the entire line [B N [L R] S]
536             runsIndex += 1 + 1 + intervals * 2 + 1
537             return runsIndex
538         }
539 
540         private var fIntervalCount: Int = 0
541         private var fYSpanCount: Int = 0
542         var runs = RunArray()
543         var fRunCount: Int = 0
544 
545         val readonlyRuns: Array<Int>
546             get() = runs.runs
547     }
548 
549     private fun setRuns(runs: RunArray, _count: Int): Boolean {
550         require(_count > 0)
551 
552         var count = _count
553 
554         if (isRunCountEmpty(count)) {
555             assertSentinel(runs[count - 1], true)
556             return this.setEmpty()
557         }
558 
559         // trim off any empty spans from the top and bottom
560         // weird I should need this, perhaps op() could be smarter...
561         var trimmedRuns = runs
562         if (count > RECT_REGION_RUNS) {
563             var stopIndex = count
564             assertSentinel(runs[0], false) // top
565             assertSentinel(runs[1], false) // bottom
566             // runs[2] is uncomputed intervalCount
567 
568             var trimLeft = false
569             if (runs[3] == RUN_TYPE_SENTINEL) { // should be first left...
570                 trimLeft = true
571                 assertSentinel(runs[1], false) // bot: a sentinel would mean two in a row
572                 assertSentinel(runs[2], false) // interval count
573                 assertSentinel(runs[3], false) // left
574                 assertSentinel(runs[4], false) // right
575             }
576 
577             assertSentinel(runs[stopIndex - 1], true)
578             assertSentinel(runs[stopIndex - 2], true)
579 
580             var trimRight = false
581             // now check for a trailing empty span
582             if (runs[stopIndex - 5] == RUN_TYPE_SENTINEL) {
583                 // eek, stop[-4] was a bottom with no x-runs
584                 trimRight = true
585             }
586 
587             var startIndex = 0
588             if (trimLeft) {
589                 startIndex += 3
590                 trimmedRuns = trimmedRuns.subList(startIndex, count) // skip empty initial span
591                 trimmedRuns[0] = runs[1] // set new top to prev bottom
592             }
593             if (trimRight) {
594                 // kill empty last span
595                 trimmedRuns[stopIndex - 4] = RUN_TYPE_SENTINEL
596                 stopIndex -= 3
597                 assertSentinel(runs[stopIndex - 1], true) // last y-sentinel
598                 assertSentinel(runs[stopIndex - 2], true) // last x-sentinel
599                 assertSentinel(runs[stopIndex - 3], false) // last right
600                 assertSentinel(runs[stopIndex - 4], false) // last left
601                 assertSentinel(runs[stopIndex - 5], false) // last interval-count
602                 assertSentinel(runs[stopIndex - 6], false) // last bottom
603                 trimmedRuns = trimmedRuns.subList(startIndex, stopIndex)
604             }
605 
606             count = stopIndex - startIndex
607         }
608 
609         require(count >= RECT_REGION_RUNS)
610 
611         if (runsAreARect(trimmedRuns, count)) {
612             fBounds =
613                 Rect.withoutCache(trimmedRuns[3], trimmedRuns[0], trimmedRuns[4], trimmedRuns[1])
614             return this.setRect(fBounds)
615         }
616 
617         //  if we get here, we need to become a complex region
618         if (!this.isComplex() || fRunHead!!.fRunCount != count) {
619             fRunHead = RunHead()
620             fRunHead!!.fRunCount = count
621             require(this.isComplex())
622         }
623 
624         // must call this before we can write directly into runs()
625         // in case we are sharing the buffer with another region (copy on write)
626         // fRunHead = fRunHead->ensureWritable();
627         // memcpy(fRunHead, runs, count * sizeof(RunType))
628         fRunHead!!.setRuns(trimmedRuns, count)
629         fBounds = fRunHead!!.computeRunBounds()
630 
631         // Our computed bounds might be too large, so we have to check here.
632         if (fBounds.isEmpty) {
633             return this.setEmpty()
634         }
635 
636         return true
637     }
638 
639     private fun setRect(r: Rect): Boolean {
640         if (r.isEmpty || RUN_TYPE_SENTINEL == r.right || RUN_TYPE_SENTINEL == r.bottom) {
641             return this.setEmpty()
642         }
643         fBounds = r
644         fRunHead = null
645         return true
646     }
647 
648     private fun isRunCountEmpty(count: Int): Boolean {
649         return count <= 2
650     }
651 
652     private fun runsAreARect(runs: RunArray, count: Int): Boolean {
653         require(count >= RECT_REGION_RUNS)
654 
655         if (count == RECT_REGION_RUNS) {
656             assertSentinel(runs[1], false) // bottom
657             require(1 == runs[2])
658             assertSentinel(runs[3], false) // left
659             assertSentinel(runs[4], false) // right
660             assertSentinel(runs[5], true)
661             assertSentinel(runs[6], true)
662 
663             require(runs[0] < runs[1]) // valid height
664             require(runs[3] < runs[4]) // valid width
665 
666             return true
667         }
668         return false
669     }
670 
671     class RgnOper(var top: Int, private val runArray: RunArray, op: Op) {
672         private val fMin = gOpMinMax[op]!!.min
673         private val fMax = gOpMinMax[op]!!.max
674 
675         private var fStartDst = 0
676         private var fPrevDst = 1
677         private var fPrevLen = 0
678 
679         fun addSpan(
680             bottom: Int,
681             aRuns: Array<Int>,
682             bRuns: Array<Int>,
683             aRunsIndex: Int,
684             bRunsIndex: Int
685         ) {
686             // skip X values and slots for the next Y+intervalCount
687             val start = fPrevDst + fPrevLen + 2
688             // start points to beginning of dst interval
689             val stop =
690                 operateOnSpan(aRuns, bRuns, aRunsIndex, bRunsIndex, runArray, start, fMin, fMax)
691             val len = stop - start
692             require(len >= 1 && (len and 1) == 1)
693             require(RUN_TYPE_SENTINEL == runArray[stop - 1])
694 
695             // Assert memcmp won't exceed fArray->count().
696             require(runArray.count >= start + len - 1)
697             if (
698                 fPrevLen == len &&
699                     (1 == len ||
700                         runArray
701                             .subList(fPrevDst, fPrevDst + len)
702                             .runs
703                             .contentEquals(runArray.subList(start, start + len).runs))
704             ) {
705                 // update Y value
706                 runArray[fPrevDst - 2] = bottom
707             } else { // accept the new span
708                 if (len == 1 && fPrevLen == 0) {
709                     top = bottom // just update our bottom
710                 } else {
711                     runArray[start - 2] = bottom
712                     runArray[start - 1] = len / 2 // len shr 1
713                     fPrevDst = start
714                     fPrevLen = len
715                 }
716             }
717         }
718 
719         fun flush(): Int {
720             runArray[fStartDst] = top
721             // Previously reserved enough for TWO sentinals.
722             // SkASSERT(fArray->count() > SkToInt(fPrevDst + fPrevLen));
723             runArray[fPrevDst + fPrevLen] = RUN_TYPE_SENTINEL
724             return fPrevDst - fStartDst + fPrevLen + 1
725         }
726 
727         class SpanRect(
728             private val aRuns: Array<Int>,
729             private val bRuns: Array<Int>,
730             aIndex: Int,
731             bIndex: Int
732         ) {
733             var fLeft: Int = 0
734             var fRight: Int = 0
735             var fInside: Int = 0
736 
737             var fALeft: Int
738             var fARight: Int
739             var fBLeft: Int
740             var fBRight: Int
741             var fARuns: Int
742             var fBRuns: Int
743 
744             init {
745                 fALeft = aRuns[aIndex]
746                 fARight = aRuns[aIndex + 1]
747                 fBLeft = bRuns[bIndex]
748                 fBRight = bRuns[bIndex + 1]
749                 fARuns = aIndex + 2
750                 fBRuns = bIndex + 2
751             }
752 
753             fun done(): Boolean {
754                 require(fALeft <= RUN_TYPE_SENTINEL)
755                 require(fBLeft <= RUN_TYPE_SENTINEL)
756                 return fALeft == RUN_TYPE_SENTINEL && fBLeft == RUN_TYPE_SENTINEL
757             }
758 
759             fun next() {
760                 val inside: Int
761                 val left: Int
762                 var right = 0
763                 var aFlush = false
764                 var bFlush = false
765 
766                 var aLeft = fALeft
767                 var aRight = fARight
768                 var bLeft = fBLeft
769                 var bRight = fBRight
770 
771                 if (aLeft < bLeft) {
772                     inside = 1
773                     left = aLeft
774                     if (aRight <= bLeft) { // [...] <...>
775                         right = aRight
776                         aFlush = true
777                     } else { // [...<..]...> or [...<...>...]
778                         aLeft = bLeft
779                         right = bLeft
780                     }
781                 } else if (bLeft < aLeft) {
782                     inside = 2
783                     left = bLeft
784                     if (bRight <= aLeft) { // [...] <...>
785                         right = bRight
786                         bFlush = true
787                     } else { // [...<..]...> or [...<...>...]
788                         bLeft = aLeft
789                         right = aLeft
790                     }
791                 } else { // a_left == b_left
792                     inside = 3
793                     left = aLeft // or b_left
794                     if (aRight <= bRight) {
795                         bLeft = aRight
796                         right = aRight
797                         aFlush = true
798                     }
799                     if (bRight <= aRight) {
800                         aLeft = bRight
801                         right = bRight
802                         bFlush = true
803                     }
804                 }
805 
806                 if (aFlush) {
807                     aLeft = aRuns[fARuns]
808                     fARuns++
809                     aRight = aRuns[fARuns]
810                     fARuns++
811                 }
812                 if (bFlush) {
813                     bLeft = bRuns[fBRuns]
814                     fBRuns++
815                     bRight = bRuns[fBRuns]
816                     fBRuns++
817                 }
818 
819                 require(left <= right)
820 
821                 // now update our state
822                 fALeft = aLeft
823                 fARight = aRight
824                 fBLeft = bLeft
825                 fBRight = bRight
826 
827                 fLeft = left
828                 fRight = right
829                 fInside = inside
830             }
831         }
832 
833         private fun operateOnSpan(
834             a_runs: Array<Int>,
835             b_runs: Array<Int>,
836             a_run_index: Int,
837             b_run_index: Int,
838             array: RunArray,
839             dstOffset: Int,
840             min: Int,
841             max: Int
842         ): Int {
843             // This is a worst-case for this span plus two for TWO terminating sentinels.
844             array.resizeToAtLeast(
845                 dstOffset +
846                     distanceToSentinel(a_runs, a_run_index) +
847                     distanceToSentinel(b_runs, b_run_index) +
848                     2
849             )
850             var dstIndex = dstOffset
851 
852             val rec = SpanRect(a_runs, b_runs, a_run_index, b_run_index)
853             var firstInterval = true
854 
855             while (!rec.done()) {
856                 rec.next()
857 
858                 val left = rec.fLeft
859                 val right = rec.fRight
860 
861                 // add left,right to our dst buffer (checking for coincidence
862                 if (
863                     (rec.fInside - min).toUInt() <= (max - min).toUInt() && left < right
864                 ) { // skip if equal
865                     if (firstInterval || array[dstIndex - 1] < left) {
866                         array[dstIndex] = left
867                         dstIndex++
868                         array[dstIndex] = right
869                         dstIndex++
870                         firstInterval = false
871                     } else {
872                         // update the right edge
873                         array[dstIndex - 1] = right
874                     }
875                 }
876             }
877 
878             array[dstIndex] = RUN_TYPE_SENTINEL
879             dstIndex++
880             return dstIndex // dst - &(*array)[0]
881         }
882 
883         private fun distanceToSentinel(runs: Array<Int>, startIndex: Int): Int {
884             var index = startIndex
885             if (runs.size <= index) {
886                 println("We fucked up...")
887             }
888             while (runs[index] != RUN_TYPE_SENTINEL) {
889                 if (runs.size <= index + 2) {
890                     println("We fucked up...")
891                     return 256
892                 }
893                 index += 2
894             }
895             return index - startIndex
896         }
897     }
898 
899     private fun operate(
900         aRuns: Array<Int>,
901         bRuns: Array<Int>,
902         dst: RunArray,
903         op: Op,
904         _aRunsIndex: Int = 0,
905         _bRunsIndex: Int = 0
906     ): Int {
907         var aRunsIndex = _aRunsIndex
908         var bRunsIndex = _bRunsIndex
909 
910         var aTop = aRuns[aRunsIndex]
911         aRunsIndex++
912         var aBot = aRuns[aRunsIndex]
913         aRunsIndex++
914         var bTop = bRuns[bRunsIndex]
915         bRunsIndex++
916         var bBot = bRuns[bRunsIndex]
917         bRunsIndex++
918 
919         aRunsIndex++ // skip the intervalCount
920         bRunsIndex++ // skip the intervalCount
921 
922         val gEmptyScanline: Array<Int> =
923             arrayOf(
924                 0, // fake bottom value
925                 0, // zero intervals
926                 RUN_TYPE_SENTINEL,
927                 // just need a 2nd value, since spanRec.init() reads 2 values, even
928                 // though if the first value is the sentinel, it ignores the 2nd value.
929                 // w/o the 2nd value here, we might read uninitialized memory.
930                 // This happens when we are using gSentinel, which is pointing at
931                 // our sentinel value.
932                 0
933             )
934         val gSentinel = 2
935 
936         // Now aRuns and bRuns to their intervals (or sentinel)
937 
938         assertSentinel(aTop, false)
939         assertSentinel(aBot, false)
940         assertSentinel(bTop, false)
941         assertSentinel(bBot, false)
942 
943         val oper = RgnOper(min(aTop, bTop), dst, op)
944 
945         var prevBot = RUN_TYPE_SENTINEL // so we fail the first test
946 
947         while (aBot < RUN_TYPE_SENTINEL || bBot < RUN_TYPE_SENTINEL) {
948             var top: Int
949             var bot = 0
950 
951             var run0 = gEmptyScanline
952             var run0Index = gSentinel
953             var run1 = gEmptyScanline
954             var run1Index = gSentinel
955             var aFlush = false
956             var bFlush = false
957 
958             if (aTop < bTop) {
959                 top = aTop
960                 run0 = aRuns
961                 run0Index = aRunsIndex
962                 if (aBot <= bTop) { // [...] <...>
963                     bot = aBot
964                     aFlush = true
965                 } else { // [...<..]...> or [...<...>...]
966                     aTop = bTop
967                     bot = bTop
968                 }
969             } else if (bTop < aTop) {
970                 top = bTop
971                 run1 = bRuns
972                 run1Index = bRunsIndex
973                 if (bBot <= aTop) { // [...] <...>
974                     bot = bBot
975                     bFlush = true
976                 } else { // [...<..]...> or [...<...>...]
977                     bTop = aTop
978                     bot = aTop
979                 }
980             } else { // aTop == bTop
981                 top = aTop // or bTop
982                 run0 = aRuns
983                 run0Index = aRunsIndex
984                 run1 = bRuns
985                 run1Index = bRunsIndex
986                 if (aBot <= bBot) {
987                     bTop = aBot
988                     bot = aBot
989                     aFlush = true
990                 }
991                 if (bBot <= aBot) {
992                     aTop = bBot
993                     bot = bBot
994                     bFlush = true
995                 }
996             }
997 
998             if (top > prevBot) {
999                 oper.addSpan(top, gEmptyScanline, gEmptyScanline, gSentinel, gSentinel)
1000             }
1001             oper.addSpan(bot, run0, run1, run0Index, run1Index)
1002 
1003             if (aFlush) {
1004                 aRunsIndex = skipIntervals(aRuns, aRunsIndex)
1005                 aTop = aBot
1006                 aBot = aRuns[aRunsIndex]
1007                 aRunsIndex++ // skip to next index
1008                 aRunsIndex++ // skip uninitialized intervalCount
1009                 if (aBot == RUN_TYPE_SENTINEL) {
1010                     aTop = aBot
1011                 }
1012             }
1013             if (bFlush) {
1014                 bRunsIndex = skipIntervals(bRuns, bRunsIndex)
1015                 bTop = bBot
1016                 bBot = bRuns[bRunsIndex]
1017                 bRunsIndex++ // skip to next index
1018                 bRunsIndex++ // skip uninitialized intervalCount
1019                 if (bBot == RUN_TYPE_SENTINEL) {
1020                     bTop = bBot
1021                 }
1022             }
1023 
1024             prevBot = bot
1025         }
1026 
1027         return oper.flush()
1028     }
1029 
1030     private fun skipIntervals(runs: Array<Int>, index: Int): Int {
1031         val intervals = runs[index - 1]
1032         return index + intervals * 2 + 1
1033     }
1034 
1035     /**
1036      * Perform the specified Op on this region and the specified region. Return true if the result
1037      * of the op is not empty.
1038      */
1039     @JsName("opRegion")
1040     fun op(region: Region, op: Op): Boolean {
1041         return op(this, region, op)
1042     }
1043 
1044     /**
1045      * Perform the specified Op on this region and the specified rect. Return true if the result of
1046      * the op is not empty.
1047      */
1048     @JsName("op")
1049     fun op(left: Int, top: Int, right: Int, bottom: Int, op: Op): Boolean {
1050         return op(Rect.withoutCache(left, top, right, bottom), op)
1051     }
1052 
1053     /**
1054      * Perform the specified Op on this region and the specified rect. Return true if the result of
1055      * the op is not empty.
1056      */
1057     @JsName("opRect")
1058     fun op(r: Rect, op: Op): Boolean {
1059         return op(from(r), op)
1060     }
1061 
1062     /**
1063      * Set this region to the result of performing the Op on the specified rect and region. Return
1064      * true if the result is not empty.
1065      */
1066     @JsName("opAndSetRegion")
1067     fun op(rect: Rect, region: Region, op: Op): Boolean {
1068         return op(from(rect), region, op)
1069     }
1070 
1071     @JsName("minus")
1072     fun minus(other: Region): Region {
1073         val thisRegion = from(this)
1074         thisRegion.op(other, Op.XOR)
1075         return thisRegion
1076     }
1077 
1078     @JsName("coversAtMost")
1079     fun coversAtMost(testRegion: Region): Boolean {
1080         val testRect = testRegion.bounds
1081         val intersection = from(this)
1082         return intersection.op(testRect, Op.INTERSECT) && !intersection.op(this, Op.XOR)
1083     }
1084 
1085     @JsName("coversAtLeast")
1086     fun coversAtLeast(testRegion: Region): Boolean {
1087         val intersection = from(this)
1088         return intersection.op(testRegion, Op.INTERSECT) && !intersection.op(testRegion, Op.XOR)
1089     }
1090 
1091     @JsName("coversMoreThan")
1092     fun coversMoreThan(testRegion: Region): Boolean {
1093         return coversAtLeast(testRegion) && from(this).minus(testRegion).isNotEmpty
1094     }
1095 
1096     @JsName("outOfBoundsRegion")
1097     fun outOfBoundsRegion(testRegion: Region): Region {
1098         val testRect = testRegion.bounds
1099         val outOfBoundsRegion = from(this)
1100         outOfBoundsRegion.op(testRect, Op.INTERSECT) && outOfBoundsRegion.op(this, Op.XOR)
1101         return outOfBoundsRegion
1102     }
1103 
1104     @JsName("uncoveredRegion")
1105     fun uncoveredRegion(testRegion: Region): Region {
1106         val uncoveredRegion = from(this)
1107         uncoveredRegion.op(testRegion, Op.INTERSECT) && uncoveredRegion.op(testRegion, Op.XOR)
1108         return uncoveredRegion
1109     }
1110 
1111     companion object {
1112         @JsName("EMPTY")
1113         val EMPTY: Region
1114             get() = Region()
1115 
1116         private const val RUN_TYPE_SENTINEL = 0x7FFFFFFF
1117 
1118         private const val RECT_REGION_RUNS = 7
1119 
1120         private class MinMax(val min: Int, val max: Int)
1121 
1122         private val gOpMinMax =
1123             mapOf(
1124                 Op.DIFFERENCE to MinMax(1, 1),
1125                 Op.INTERSECT to MinMax(3, 3),
1126                 Op.UNION to MinMax(1, 3),
1127                 Op.XOR to MinMax(1, 2)
1128             )
1129 
1130         @JsName("from")
1131         fun from(left: Int, top: Int, right: Int, bottom: Int): Region =
1132             from(Rect.withoutCache(left, top, right, bottom))
1133 
1134         @JsName("fromRegion") fun from(region: Region): Region = Region().also { it.set(region) }
1135 
1136         @JsName("fromRect")
1137         fun from(rect: Rect? = null): Region =
1138             Region().also {
1139                 it.fRunHead = null
1140                 it.setRect(rect ?: Rect.EMPTY)
1141             }
1142 
1143         @JsName("fromRectF") fun from(rect: RectF?): Region = from(rect?.toRect())
1144 
1145         @JsName("fromEmpty") fun from(): Region = from(Rect.EMPTY)
1146 
1147         private fun skRegionValueIsSentinel(value: Int): Boolean {
1148             return value == RUN_TYPE_SENTINEL
1149         }
1150 
1151         private fun assertSentinel(value: Int, isSentinel: Boolean) {
1152             require(skRegionValueIsSentinel(value) == isSentinel)
1153         }
1154 
1155         private fun getRectsFromString(regionString: String): Array<Rect> {
1156             val rects = mutableListOf<Rect>()
1157 
1158             if (regionString == "SkRegion()") {
1159                 return rects.toTypedArray()
1160             }
1161 
1162             var nativeRegionString = regionString.replace("SkRegion", "")
1163             nativeRegionString = nativeRegionString.substring(2, nativeRegionString.length - 2)
1164             nativeRegionString = nativeRegionString.replace(")(", ",")
1165 
1166             var rect = Rect.EMPTY
1167             for ((i, coord) in nativeRegionString.split(",").withIndex()) {
1168                 when (i % 4) {
1169                     0 -> rect = Rect.withoutCache(coord.toInt(), 0, 0, 0)
1170                     1 -> rect = Rect.withoutCache(rect.left, coord.toInt(), 0, 0)
1171                     2 -> rect = Rect.withoutCache(rect.left, rect.top, coord.toInt(), 0)
1172                     3 -> {
1173                         rect = Rect.withoutCache(rect.left, rect.top, rect.right, coord.toInt())
1174                         rects.add(rect)
1175                     }
1176                 }
1177             }
1178 
1179             return rects.toTypedArray()
1180         }
1181     }
1182 }
1183