2 * Pixel Graphics Library
3 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
4 * Understanding is not required. Only obedience.
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, version 3 of the License ONLY.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module iv
.sdpy
.region
/*is aliced*/;
22 // ////////////////////////////////////////////////////////////////////////// //
23 // regions are copy-on-write shared, yay
25 alias SpanType
= ushort; // you probably will never need this, but...
27 /// combine operation for region combiner %-)
31 Xor
, /// logic exclusive or
32 Cut
, /// cut solid parts
33 NCut
, /// cut empty parts
36 @property pure const nothrow @safe @nogc {
37 int width () { pragma(inline
, true); return (rdatap ? rdata
.rwdt
: 0); }
38 int height () { pragma(inline
, true); return (rdatap ? rdata
.rhgt
: 0); }
39 bool solid () { pragma(inline
, true); return (rdatap ? rdata
.simple
&& rdata
.rwdt
> 0 && rdata
.rhgt
> 0 && rdata
.simpleSolid
: false); }
40 bool empty () { pragma(inline
, true); return (rdatap ? rdata
.rwdt
< 1 || rdata
.rhgt
< 1 ||
(rdata
.simple
&& !rdata
.simpleSolid
) : true); }
43 @property uint[] getData () const nothrow @safe {
46 res
~= 1|
(rdata
.simpleSolid ?
2 : 0);
50 res
[0] |
= cast(int)(SpanType
.sizeof
<<4);
55 foreach (SpanType d
; rdata
.data
) res
~= d
;
60 void setData (const(int)[] data
) nothrow @safe {
62 if (data
.length
< 3 ||
(data
[0]>>4) != SpanType
.sizeof
) assert(0, "invalid region data");
65 rdata
.simple
= ((data
[0]&0x01) != 0);
69 rdata
.simpleSolid
= ((data
[0]&0x02) != 0);
71 foreach (int d
; data
[3..3+rdata
.rhgt
]) rdata
.lineofs
~= d
;
72 foreach (int d
; data
[3+rdata
.rhgt
..$]) rdata
.data
~= cast(SpanType
)d
;
76 // this creates solid region
77 this (int awidth
, int aheight
, bool solid
=true) nothrow @safe @nogc { setSize(awidth
, aheight
, solid
); }
78 ~this () nothrow @safe @nogc { decRC(); } // release this region data
79 this (this) nothrow @safe @nogc { if (rdatap
) ++rdata
.rc
; } // share this region data
81 void setSize (int awidth
, int aheight
, bool solid
=true) nothrow @safe @nogc {
82 if (awidth
<= 0 || aheight
<= 0) awidth
= aheight
= 0;
83 if (awidth
> SpanType
.max
-1 || aheight
> SpanType
.max
-1) assert(0, "Region internal error: region dimensions are too big");
87 rdata
.simpleSolid
= solid
;
92 /// is given point visible?
93 bool visible (int x
, int y
) const pure nothrow @safe @nogc {
95 if (!rdatap
) return false;
96 if (rdata
.rwdt
< 1 || rdata
.rhgt
< 1) return false;
97 if (x
< 0 || y
< 0 || x
>= rdata
.rwdt || y
>= rdata
.rhgt
) return false;
98 if (rdata
.simple
) return true; // ok, easy case here
100 immutable ldofs
= rdata
.lineofs
[y
];
101 immutable len
= rdata
.data
[ldofs
];
102 debug assert(len
> 1);
103 auto line
= rdata
.data
[ldofs
+1..ldofs
+len
];
104 int idx
= void; // will be initied in mixin
105 mixin(FindSpanMixinStr
!"idx");
106 debug assert(idx
< line
.length
); // too far (the thing that should not be)
107 return ((idx
+(line
[idx
] == x
))%2 == 0);
111 void punch (int x
, int y
, int w
=1, int h
=1) nothrow @trusted { pragma(inline
, true); doPunchPatch
!"punch"(x
, y
, w
, h
); }
114 void patch (int x
, int y
, int w
=1, int h
=1) nothrow @trusted { pragma(inline
, true); doPunchPatch
!"patch"(x
, y
, w
, h
); }
116 // ////////////////////////////////////////////////////////////////////////// //
117 enum State
{ Mixed
= -1, Empty
, Solid
} //WARNING! don't change the order!
119 /// return span state %-)
120 State
spanState (int y
, int x0
, int x1
) const pure nothrow @safe @nogc {
121 if (rdata
is null) return State
.Empty
;
122 if (y
< 0 || y
>= rdata
.rhgt || x1
< 0 || x0
>= rdata
.rwdt || x1
< x0
) return State
.Empty
;
124 // if our span is not fully inside, it can be either Empty or Mixed
125 if (rdata
.simpleSolid
) {
126 return (x0
>= 0 && x1
< rdata
.rwdt ? State
.Solid
: State
.Mixed
);
131 immutable ldofs
= rdata
.lineofs
[y
];
132 immutable len
= rdata
.data
[ldofs
];
133 debug assert(len
> 1);
134 auto line
= rdata
.data
[ldofs
+1..ldofs
+len
];
135 int idx
= void; // will be initied in mixin
136 immutable x
= (x0
>= 0 ? x0
: 0);
137 mixin(FindSpanMixinStr
!"idx");
138 debug assert(idx
< line
.length
); // too far (the thing that should not be)
139 // move to "real" span
140 if (line
[idx
] == x
) ++idx
;
141 // now, sx is line[idx-1], ex is line[idx]
142 if (x1
>= (idx
< line
.length ? line
[idx
] : rdata
.rwdt
)) return State
.Mixed
;
143 idx
= (idx^
1)&1; // we are interested only in last bit, and we converted it to State here
144 // if our span is not fully inside, it can be either Empty or Mixed
145 if (idx
== State
.Solid
&& x0
< 0) return State
.Mixed
;
146 return cast(State
)idx
;
149 static private template IsGoodSDG(T
) {
150 private import std
.traits
;
151 static private template IsGoodRT(T
) { enum IsGoodRT
= is(T
== void) ||
is(T
== bool) ||
is(T
: int); }
152 static private template IsGoodAT(T
) { enum IsGoodAT
= is(T
== int) ||
is(T
== long) ||
is(T
== uint) ||
is(T
== ulong); }
153 enum IsGoodSDG
= isCallable
!T
&& IsGoodRT
!(ReturnType
!T
) && (variadicFunctionStyle
!T
== Variadic
.no
) &&
154 Parameters
!T
.length
== 2 && IsGoodAT
!(Parameters
!T
[0]) && IsGoodAT
!(Parameters
!T
[1]);
157 /// call delegate for each solid or empty span
158 /// for non-void returning delegates, return !0 to exit
159 auto spans(bool solids
=true, DG
) (int y
, int x0
, int x1
, scope DG dg
) const if (IsGoodSDG
!DG
) { return spansEnumerator
!(DG
, solids
)(y
, 0, x0
, x1
, dg
); }
161 /// call delegate for each solid or empty span
162 /// for non-void returning delegates, return !0 to exit
163 /// `ofsx` will be automatically subtracted from `x0` and `x1` args, and added to `x0` and `x1` delegate args
164 auto spans(bool solids
=true, DG
) (int y
, int ofsx
, int x0
, int x1
, scope DG dg
) const if (IsGoodSDG
!DG
) { return spansEnumerator
!(DG
, solids
)(y
, ofsx
, x0
, x1
, dg
); }
166 /// element of span range
167 static struct XPair
{ int x0
, x1
; }
169 /// get range of spans
170 auto spanRange(bool solids
=true) (int y
, int x0
, int x1
) nothrow @safe @nogc { return spanRange
!solids(y
, 0, x0
, x1
); }
172 auto spanRange(bool solids
=true) (int y
, int ofsx
, int x0
, int x1
) nothrow @safe @nogc {
173 static struct SpanRange(bool solids
) {
174 int ofsx
, x0
, x1
, rwdt
, idx
;
175 ubyte eosNM
; // empty(bit0), nomore(bit1) ;-)
176 XPair fpair
; // front
177 const(SpanType
)[] line
;
179 nothrow @trusted @nogc:
180 this (ref Region reg
, int y
, int aofsx
, int ax0
, int ax1
) {
184 if (x0
> x1
) { eosNM
= 0x01; return; }
185 if (reg
.rdata
is null) {
186 static if (!solids
) {
195 rwdt
= reg
.rdata
.rwdt
;
198 if (y
< 0 || y
>= reg
.rdata
.rhgt || x1
< 0 || x0
>= rwdt || x1
< x0
) {
199 static if (!solids
) {
208 if (reg
.rdata
.simple
) {
209 if (reg
.rdata
.simpleSolid
) {
212 if (x1
>= rwdt
) x1
= rwdt
-1;
224 eosNM
= (x1
< rwdt ?
0x02 : 0x04);
227 fpair
.x0
= rwdt
+ofsx
;
236 static if (!solids
) {
246 // edge cases are checked
247 immutable ldofs
= reg
.rdata
.lineofs
[y
];
248 immutable len
= reg
.rdata
.data
[ldofs
];
249 debug assert(len
> 1);
250 line
= reg
.rdata
.data
[ldofs
+1..ldofs
+len
];
251 // beyond left border? move to first solid span
254 int ex
= (line
[0] == 0 ? line
[1]-1 : -1);
255 // is first span empty too?
257 static if (!solids
) {
266 static if (!solids
) {
271 //import iv.writer; writeln("*");
276 static if (solids
) { if (x1
>= rwdt
) x1
= rwdt
-1; }
277 //int idx = void; // will be initied in mixin
278 alias x
= x0
; // for mixin
279 mixin(FindSpanMixinStr
!"idx");
280 debug assert(idx
< line
.length
); // too far (the thing that should not be)
281 // move to "real" span, so sx is line[idx-1], ex+1 is line[idx]
282 if (line
[idx
] == x
) ++idx
;
283 if (!hasOne
) popFront();
286 @property auto save () pure {
287 SpanRange
!solids res
;
299 @property bool empty () const pure { return ((eosNM
&0x01) != 0); }
300 @property XPair
front () const pure { return XPair(fpair
.x0
, fpair
.x1
); }
303 if (eosNM
&0x02) eosNM
= 0x01;
304 if (eosNM
&0x01) return;
308 static if (!solids
) {
309 fpair
.x0
= rwdt
+ofsx
;
323 int ex
= line
[idx
]-1;
324 int cex
= (ex
< x1 ? ex
: x1
); // clipped ex
325 // emit part from x0 to ex if necessary
327 // current span is solid?
334 // current span is empty?
337 fpair
.x1
= (ex
< rwdt
-1 ? cex
: x1
)+ofsx
;
339 if (ex
== rwdt
-1) { eosNM
= 0x02; return; }
341 if (ex
== rwdt
-1) { x0
= rwdt
; break; }
349 static if (!solids
) {
361 return SpanRange
!solids(this, y
, ofsx
, x0
, x1
);
365 // ////////////////////////////////////////////////////////////////////////// //
366 auto spansEnumerator(DG
, bool solids
) (int y
, int ofsx
, int x0
, int x1
, scope DG dg
) const {
367 import std
.traits
: ReturnType
;
368 static if (is(ReturnType
!DG
== void)) {
369 enum ReturnFail
= "return;";
370 enum DgCall(string args
) = "dg("~args
~");";
372 static if (is(ReturnType
!DG
== bool)) enum ReturnFail
= "return false;"; else enum ReturnFail
= "return 0;";
373 enum DgCall(string args
) = "if (auto xres = dg("~args
~")) return xres;";
375 if (x0
> x1 || dg
is null) mixin(ReturnFail
);
377 static if (!solids
) dg(x0
, x1
);
382 if (y
< 0 || y
>= rdata
.rhgt || x1
< 0 || x0
>= rdata
.rwdt || x1
< x0
) {
383 static if (!solids
) { mixin(DgCall
!"x0+ofsx, x1+ofsx"); }
387 if (rdata
.simpleSolid
) {
390 if (x1
>= rdata
.rwdt
) x1
= rdata
.rwdt
-1;
391 if (x0
<= x1
) { mixin(DgCall
!"x0+ofsx, x1+ofsx"); }
393 if (x0
< 0) { mixin(DgCall
!"x0+ofsx, -1+ofsx"); }
394 if (x1
>= rdata
.rwdt
) { mixin(DgCall
!"rdata.rwdt+ofsx, x1+ofsx"); }
397 static if (!solids
) { mixin(DgCall
!"x0+ofsx, x1+ofsx"); }
401 immutable ldofs
= rdata
.lineofs
[y
];
402 immutable len
= rdata
.data
[ldofs
];
403 debug assert(len
> 1);
404 auto line
= rdata
.data
[ldofs
+1..ldofs
+len
];
405 // beyond left border? move to first solid span
407 int ex
= (rdata
.data
[ldofs
+1] == 0 ? rdata
.data
[ldofs
+2]-1 : -1);
408 // is first span empty too?
409 if (ex
>= x1
) { static if (!solids
) { mixin(DgCall
!"x0+ofsx, x1+ofsx"); } mixin(ReturnFail
); }
410 static if (!solids
) { mixin(DgCall
!"x0+ofsx, ex+ofsx"); }
413 static if (solids
) { if (x1
>= rdata
.rwdt
) x1
= rdata
.rwdt
-1; }
414 int idx
= void; // will be initied in mixin
415 alias x
= x0
; // for mixin
416 mixin(FindSpanMixinStr
!"idx");
417 debug assert(idx
< line
.length
); // too far (the thing that should not be)
418 // move to "real" span, so sx is line[idx-1], ex+1 is line[idx]
419 if (line
[idx
] == x
) ++idx
;
422 int ex
= line
[idx
]-1;
423 int cex
= (ex
< x1 ? ex
: x1
); // clipped ex
424 // emit part from x0 to ex if necessary
426 // current span is solid?
427 if (idx
%2 == 0) { mixin(DgCall
!"x0+ofsx, cex+ofsx"); }
429 // current span is empty?
431 { mixin(DgCall
!"x0+ofsx, (ex < rdata.rwdt-1 ? cex : x1)+ofsx"); }
432 if (ex
== rdata
.rwdt
-1) mixin(ReturnFail
);
434 if (ex
== rdata
.rwdt
-1) { x0
= rdata
.rwdt
; break; }
439 //static if (!solids) { if (x0 == rdata.rwdt) break; }
441 static if (!solids
) { if (x0
<= x1
) { mixin(DgCall
!"x0+ofsx, x1+ofsx"); } }
445 // ////////////////////////////////////////////////////////////////////////// //
446 // find element index
447 // always returns index of key which is >= `x`
448 private enum FindSpanMixinStr(string minAndRes
) = "{
450 int max = cast(int)line.length;
451 while (("~minAndRes
~") < max) {
452 int mid = (("~minAndRes
~")+max)>>1; // ignore possible overflow, it can't happen here
453 debug assert(mid < max);
454 if (line[mid] < x) ("~minAndRes
~") = mid+1; else max = mid;
456 //return ("~minAndRes
~"); // actually, the key is found if (max == min/*always*/ && min < line.length && line[min] == x)
459 // ////////////////////////////////////////////////////////////////////////// //
460 // punch a hole, patch a hole
461 // mode: "punch", "patch"
463 void doPunchPatch(string mode
) (int x
, int y
, int w
=1, int h
=1) nothrow @trusted {
464 static assert(mode
== "punch" || mode
== "patch", "Region: invalid mode: "~mode
);
465 if (rdata
is null) return;
466 static if (mode
== "punch") {
471 if (w
< 1 || h
< 1) return;
472 if (x
>= rdata
.rwdt || y
>= rdata
.rhgt
) return;
473 //TODO: overflow check
475 if (x
+w
<= 0) return;
480 if (y
+h
<= 0) return;
485 if (x1
>= rdata
.rwdt
) x1
= rdata
.rwdt
-1;
486 debug assert(x
<= x1
);
488 if (y1
>= rdata
.rhgt
) y1
= rdata
.rhgt
-1;
489 debug assert(y
<= y1
);
490 foreach (int cy
; y
..y1
+1) doPunchPatchLine
!mode(cy
, x
, x1
);
494 // ////////////////////////////////////////////////////////////////////////// //
495 void makeRoom (usize ofs
, ssize count
) nothrow @trusted {
496 import core
.stdc
.string
: memmove
;
497 debug assert(ofs
<= rdata
.data
.length
);
500 // `assumeSafeAppend` was already called in caller
501 //rdata.data.assumeSafeAppend.length += count;
502 rdata
.data
.length
+= count
;
503 if (ofs
+count
< rdata
.data
.length
) memmove(rdata
.data
.ptr
+ofs
+count
, rdata
.data
.ptr
+ofs
, SpanType
.sizeof
*(rdata
.data
.length
-ofs
-count
));
504 } else if (count
< 0) {
507 debug assert(ofs
+count
<= rdata
.data
.length
);
508 if (ofs
+count
== rdata
.data
.length
) {
509 rdata
.data
.length
= ofs
;
511 immutable auto left
= rdata
.data
.length
-ofs
-count
;
512 memmove(rdata
.data
.ptr
+ofs
, rdata
.data
.ptr
+ofs
+count
, SpanType
.sizeof
*(rdata
.data
.length
-ofs
-count
));
513 rdata
.data
.length
-= count
;
515 //rdata.data.assumeSafeAppend; // in case we will want to grow later
519 // replace span data at plofs with another data from spofs, return # of bytes added (or removed, if negative)
520 ssize
replaceSpanData (usize plofs
, usize spofs
) nothrow @trusted {
521 //import core.stdc.string : memcpy;
522 debug assert(spofs
< rdata
.data
.length
&& spofs
+rdata
.data
[spofs
] == rdata
.data
.length
);
523 debug assert(plofs
<= spofs
&& plofs
+rdata
.data
[plofs
] <= spofs
);
524 if (plofs
== spofs
) return 0; // nothing to do; just in case
525 auto oldlen
= rdata
.data
[plofs
];
526 auto newlen
= rdata
.data
[spofs
];
528 ssize
ins = cast(ssize
)newlen
-cast(ssize
)oldlen
;
530 makeRoom(plofs
, ins);
533 if (newlen
> 0) rdata
.data
[plofs
..plofs
+newlen
] = rdata
.data
[spofs
..spofs
+newlen
]; //memcpy(rdata.data.ptr+plofs, rdata.data.ptr+spofs, SpanType.sizeof*newlen);
537 // insert span data from spofs at plofs
538 void insertSpanData (usize plofs
, usize spofs
) nothrow @trusted {
539 //import core.stdc.string : memcpy;
540 debug assert(spofs
< rdata
.data
.length
&& spofs
+rdata
.data
[spofs
] == rdata
.data
.length
);
541 debug assert(plofs
<= spofs
);
542 if (plofs
== spofs
) return; // nothing to do; just in case
543 auto newlen
= rdata
.data
[spofs
];
544 makeRoom(plofs
, newlen
);
546 rdata
.data
[plofs
..plofs
+newlen
] = rdata
.data
[spofs
..spofs
+newlen
];
547 //memcpy(rdata.data.ptr+plofs, rdata.data.ptr+spofs, SpanType.sizeof*newlen);
550 bool isEqualLines (int y0
, int y1
) nothrow @trusted @nogc {
551 import core
.stdc
.string
: memcmp
;
552 if (y0
< 0 || y1
< 0 || y0
>= rdata
.rhgt || y1
>= rdata
.rhgt
) return false;
553 auto ofs0
= rdata
.lineofs
[y0
];
554 auto ofs1
= rdata
.lineofs
[y1
];
555 if (rdata
.data
[ofs0
] != rdata
.data
[ofs1
]) return false;
556 return (memcmp(rdata
.data
.ptr
+ofs0
, rdata
.data
.ptr
+ofs1
, SpanType
.sizeof
*rdata
.data
[ofs0
]) == 0);
559 // all args must be valid
561 void doPunchPatchLine(string mode
) (int y
, int x0
, int x1
) nothrow @trusted {
562 static if (mode
== "patch") {
563 if (rdata
.simple
&& rdata
.simpleSolid
) return; // no need to patch completely solid region
565 if (rdata
.simple
&& !rdata
.simpleSolid
) return; // no need to patch completely empty region
568 // check if we really have to do anything here
569 static if (mode
== "patch") {
570 if (spanState(y
, x0
, x1
) == State
.Solid
) return;
571 //enum psmode = true;
572 enum op
= CombineOp
.Or
;
574 if (spanState(y
, x0
, x1
) == State
.Empty
) return;
575 //enum psmode = false;
576 enum op
= CombineOp
.Cut
;
579 doCombine(y
, (uint lofs
, ref SpanType
[] dptr
) nothrow @trusted {
580 // note that after `assumeSafeAppend` we can increase length without further `assumeSafeAppend` calls
581 debug(region_more_prints
) { import core
.stdc
.stdio
: printf
; printf("op=%d; x0=%d; x1=%d; rwdt=%d\n", cast(int)op
, x0
, x1
, rdata
.rwdt
); }
582 SpanType dsp
= cast(SpanType
)(x1
-x0
+1);
583 debug(region_more_prints
) {
584 import core
.stdc
.stdio
: printf
;
585 auto cspd
= CSPD(op
, &dsp
, x1
-x0
+1, x0
);
586 while (!cspd
.empty
) {
587 printf(" (%d,%d,%d)", cspd
.sx
, cspd
.ex
, (cspd
.solid ?
1 : 0));
593 (int x
) nothrow @trusted { dptr
~= cast(SpanType
)x
; },
594 CSPD(CombineOp
.Or
, rdata
.data
.ptr
+lofs
+1, rdata
.rwdt
), // base span
595 CSPD(op
, &dsp
, x1
-x0
+1, x0
),
601 static void combineSpans(SPR...) (scope void delegate (int x) nothrow @safe putX, auto ref SPR spans) if (SPR.length > 1) {
602 // all args must be valid
603 void doPunchPatchLine(SPR...) (CombineOp op, int y, auto ref SPR spans) nothrow @trusted if (SPR.length > 0) {
605 if (op == CombineOp.Or && rdata.simpleSolid) return; // no need to patch completely solid region
606 if ((op == CombineOp.And || op == CombineOp.Cut || op == op == CombineOp.NCut) && !rdata.simpleSolid) return; // no need to patch completely empty region
611 // `combine`: `lofs` is starting index in `rdata.data` for base line (i.e. length element)
612 // it should build a new valid line data, starting from `rdata.data.length`, not including line length tho
613 // all args must be valid
614 void doCombine() (int y
, scope void delegate (uint lofs
, ref SpanType
[] dptr
) nothrow @trusted combine
) nothrow @trusted {
615 // bad luck, build new line
618 // build complex region rdata.data
619 if (rdata
.lineofs
is null) {
620 rdata
.lineofs
.length
= rdata
.rhgt
; // allocate and clear
622 if (rdata
.lineofs
.length
< rdata
.rhgt
) rdata
.lineofs
.assumeSafeAppend
;
623 rdata
.lineofs
.length
= rdata
.rhgt
;
624 rdata
.lineofs
[] = 0; // clear
626 rdata
.data
.length
= 0;
627 if (rdata
.simpleSolid
) {
628 rdata
.data
.assumeSafeAppend
~= 2; // length
630 rdata
.data
.assumeSafeAppend
~= 3; // length
631 rdata
.data
~= 0; // dummy solid
633 rdata
.data
~= cast(SpanType
)rdata
.rwdt
; // the only span
634 rdata
.simple
= false;
637 auto lofs
= rdata
.lineofs
[y
]; // current line offset
638 int lsize
= rdata
.data
.ptr
[lofs
]; // current line size
639 auto tmppos
= cast(uint)rdata
.data
.length
; // starting position of the new line data
641 //patchSpan!psmode(lofs+1, x0, x1);
642 rdata
.data
.assumeSafeAppend
~= 0; // length
643 combine(lofs
, rdata
.data
);
644 debug(region_more_prints
) { import core
.stdc
.stdio
: printf
; printf("LEN=%d\n", cast(int)(rdata
.data
.length
-tmppos
)); }
645 if (rdata
.data
.length
-tmppos
> SpanType
.max
) assert(0, "region internal error: line data too big");
646 rdata
.data
.ptr
[tmppos
] = cast(SpanType
)(rdata
.data
.length
-tmppos
);
648 debug(region_more_prints
) {
649 import core
.stdc
.stdio
: printf
;
650 foreach (SpanType t
; rdata
.data
[tmppos
..$]) printf(" %u", cast(uint)t
);
654 int newsize
= rdata
.data
[tmppos
]; // size of the new line
656 // was this line first in slab?
657 auto prevofs
= (y
> 0 ? rdata
.lineofs
[y
-1] : -1);
658 auto nextofs
= (y
+1 < rdata
.rhgt ? rdata
.lineofs
[y
+1] : -2);
660 // place new line data, breaking span if necessary
661 if (prevofs
!= lofs
&& nextofs
!= lofs
) {
662 // we were a slab on our own?
664 auto delta
= replaceSpanData(lofs
, tmppos
);
666 if (delta
) foreach (ref ofs
; rdata
.lineofs
[y
+1..$]) ofs
+= delta
;
667 } else if (prevofs
!= lofs
&& nextofs
== lofs
) {
668 // we were a slab start
670 insertSpanData(lofs
, tmppos
);
672 foreach (ref ofs
; rdata
.lineofs
[y
+1..$]) ofs
+= newsize
;
673 } else if (prevofs
== lofs
&& nextofs
!= lofs
) {
674 // we were a slab end
677 insertSpanData(lofs
, tmppos
);
679 rdata
.lineofs
[y
] = lofs
;
680 foreach (ref ofs
; rdata
.lineofs
[y
+1..$]) ofs
+= newsize
;
682 //import core.stdc.string : memcpy;
683 // we were a slab brick
684 debug assert(prevofs
== lofs
&& nextofs
== lofs
);
685 // the most complex case
686 // insert us after lofs, insert slab start after us, fix slab and offsets
689 insertSpanData(lofs
, tmppos
);
691 rdata
.lineofs
[y
] = lofs
;
692 // insert old slab start
694 lsize
= rdata
.data
[prevofs
];
695 makeRoom(lofs
, lsize
);
696 //memcpy(rdata.data.ptr+lofs, rdata.data.ptr+prevofs, SpanType.sizeof*lsize);
697 rdata
.data
[lofs
..lofs
+lsize
] = rdata
.data
[prevofs
..prevofs
+lsize
];
700 while (ny
< rdata
.rhgt
&& rdata
.lineofs
[ny
] == prevofs
) rdata
.lineofs
[ny
++] = lofs
;
702 newsize
+= lsize
; // simple optimization
703 while (ny
< rdata
.rhgt
) rdata
.lineofs
[ny
++] += newsize
;
708 lofs
= rdata
.lineofs
[$-1];
709 rdata
.data
.length
= lofs
+rdata
.data
[lofs
];
711 // now check if we can join slabs
712 // of course, this is somewhat wasteful, but if we'll combine this
713 // check with previous code, the whole thing will explode to an
714 // unmaintainable mess; anyway, we aren't on ZX Spectrum
716 bool upequ
= isEqualLines(y
-1, y
);
717 bool dnequ
= isEqualLines(y
+1, y
);
718 if (upequ || dnequ
) {
719 rdata
.data
.assumeSafeAppend
; // we have to call it after shrinking
720 lofs
= rdata
.lineofs
[y
];
721 debug assert(rdata
.data
[lofs
] == newsize
);
722 makeRoom(lofs
, -newsize
); // drop us
723 if (upequ
&& dnequ
) {
724 // join prev and next slabs by removing two lines...
725 auto pofs
= rdata
.lineofs
[y
-1];
726 makeRoom(lofs
, -newsize
); // drop next line
728 // and fixing offsets
729 rdata
.lineofs
[y
++] = pofs
;
730 auto sofs
= rdata
.lineofs
[y
];
731 while (y
< rdata
.rhgt
&& rdata
.lineofs
[y
] == sofs
) rdata
.lineofs
[y
++] = pofs
;
734 rdata
.lineofs
[y
] = rdata
.lineofs
[y
-1];
738 auto sofs
= rdata
.lineofs
[++y
];
739 while (y
< rdata
.rhgt
&& rdata
.lineofs
[y
] == sofs
) rdata
.lineofs
[y
++] = lofs
;
742 foreach (ref ofs
; rdata
.lineofs
[y
..$]) ofs
-= newsize
;
746 // check if we can collapse this region
747 if (rdata
.data
.length
== 2) {
748 if (rdata
.data
.ptr
[0] != 2 || rdata
.data
.ptr
[1] != rdata
.rwdt
) return;
749 } else if (rdata
.data
.length
== 3) {
750 if (rdata
.data
.ptr
[0] != 3 || rdata
.data
.ptr
[1] != 0 || rdata
.data
.ptr
[2] != rdata
.rwdt
) return;
754 foreach (immutable ofs
; rdata
.lineofs
[1..$]) if (ofs
!= 0) return;
757 //static if (mode == "patch") rdata.simpleSolid = true; else rdata.simpleSolid = false;
758 rdata
.simpleSolid
= (rdata
.data
.length
== 2);
759 rdata
.lineofs
.length
= 0; // we may need it later, so keep it
763 nothrow @trusted @nogc:
764 const(SpanType
)* data
;
765 int width
; // to detect span end
767 CombineOp op
; // operation
768 bool dsolid
; // current span
771 this (CombineOp aop
, const(SpanType
)* adata
, int awdt
, int axofs
=0) {
772 // if first span is zero-sized, this region starts with empty span
785 this() (CombineOp aop
, auto ref Region rg
, int axofs
=0) {
786 this(aop
, rg
.ldata
.ptr
, rg
.width
, axofs
);
789 @disable this (this); // no copies
791 @property bool empty () const pure { pragma(inline
, true); return (data
is null); }
792 @property bool solid () const pure { pragma(inline
, true); return dsolid
; }
793 @property int sx () const pure { pragma(inline
, true); return xofs
+csx
; }
794 @property int ex () const pure { pragma(inline
, true); return xofs
+(*data
)-1; }
796 pragma(inline
, true);
798 if (csx
>= width
) data
= null;
803 // spans[0] should have `int .width`, `empty`, `popFront`, `sx`, `ex`, `solid`
804 // others sould have: `empty`, `popFront`, `sx`, `ex`, `solid`, `op`
805 // spans[0] should always start at 0 (i.e. it is alpha and omega)
806 static void combineSpans(SPR
...) (scope void delegate (int x
) nothrow @safe putX
, auto ref SPR spans
) if (SPR
.length
> 1) {
807 bool lastsolid
= true; // it's ok
808 int lastsx
= 0; // it's ok
810 void pushSpan() (int ex
, bool solid
) {
811 debug(region_more_prints
) {} else pragma(inline
, true);
812 //debug(region_more_prints) { import core.stdc.stdio : printf; printf(" ex=%d; solid=%d; lastsx=%d; lastsolid=%d\n", ex, (solid ? 1 : 0), lastsx, (lastsolid ? 1 : 0)); }
813 //debug if (ex <= lastsx) { import core.stdc.stdio : printf; printf("ex=%d; lastsx=%d\n", ex, lastsx); }
814 debug assert(ex
>= lastsx
);
815 if (solid
!= lastsolid
) {
817 putX(lastsx
); // new span starts here
818 debug(region_more_prints
) { import core
.stdc
.stdio
: printf
; printf(" EMIT: %d\n", lastsx
); }
823 debug assert(!spans
[0].empty
);
824 debug assert(spans
[0].sx
== 0);
825 immutable sp0w
= spans
[0].width
;
827 while (!spans
[0].empty
) {
828 // process other spans
829 bool seenAliveSpan
= false;
830 bool nsolid
= spans
[0].solid
;
831 int nex
= spans
[0].ex
;
832 foreach (ref sp
; spans
[1..$]) {
833 while (!sp
.empty
&& sp
.ex
< cursx
) sp
.popFront();
834 if (sp
.empty
) continue;
835 seenAliveSpan
= true;
836 debug(region_more_prints
) { import core
.stdc
.stdio
: printf
; printf(" cursx=%d; nex=%d; nsolid=%d; sp.sx=%d; sp.ex=%d; sp.solid=%d\n", cursx
, nex
, (nsolid ?
1 : 0), sp
.sx
, sp
.ex
, (sp
.solid ?
1 : 0)); }
837 //debug if (sp.sx > cursx) { import core.stdc.stdio : printf; printf("cursx=%d; sp.sx=%d; sp.ex=%d; sp.solid=%d\n", cursx, sp.sx, sp.ex, (sp.solid ? 1 : 0)); }
838 //debug assert(sp.sx <= cursx);
839 if (sp
.sx
> nex
) continue; // too far
840 if (sp
.sx
> cursx
) { nex
= sp
.sx
-1; continue; } // partial
842 final switch (sp
.op
) {
843 case CombineOp
.Or
: nsolid
= nsolid || sp
.solid
; break;
844 case CombineOp
.And
: nsolid
= nsolid
&& sp
.solid
; break;
845 case CombineOp
.Xor
: if (sp
.solid
) nsolid
= !nsolid
; break;
846 case CombineOp
.Cut
: if (sp
.solid
) nsolid
= false; break;
847 case CombineOp
.NCut
: if (!sp
.solid
) nsolid
= false; break;
849 if (sp
.ex
< nex
) nex
= sp
.ex
;
851 pushSpan(nex
, nsolid
);
852 if (!seenAliveSpan
) {
853 // no more alive spans, process span0 till the end
854 debug(region_more_prints
) { import core
.stdc
.stdio
: printf
; printf(" NM!\n"); }
855 if (nex
< spans
[0].ex
) pushSpan(spans
[0].ex
, spans
[0].solid
); // finish current span
858 if (spans
[0].empty
) break;
859 pushSpan(spans
[0].ex
, spans
[0].solid
);
862 debug assert(lastsx
<= sp0w
);
866 if (nex
< spans
[0].ex
) {
867 // something was done, and first slab of span0 is not completely eaten
870 // either no alive spans, or first slab of span0 is completely eaten
872 if (spans
[0].empty
) { putX(sp0w
); return; } // done
877 debug assert(lastsx
<= sp0w
);
882 usize rdatap
= 0; // hide from GC
884 @property inout(RData
)* rdata () inout pure const nothrow @trusted @nogc { static if (__VERSION__
> 2067) pragma(inline
, true); return cast(RData
*)rdatap
; }
886 void decRC () nothrow @trusted @nogc {
888 if (--rdata
.rc
== 0) {
889 import core
.memory
: GC
;
890 import core
.stdc
.stdlib
: free
;
891 GC
.removeRange(rdata
);
894 rdatap
= 0; // just in case
898 // copy-on-write mechanics
899 void cow(bool doCopyData
) () nothrow @trusted {
901 if (srcd
is null || srcd
.rc
!= 1) {
902 import core
.memory
: GC
;
903 import core
.stdc
.stdlib
: malloc
, free
;
904 import core
.stdc
.string
: memcpy
;
905 auto dstd
= cast(RData
*)malloc(RData
.sizeof
);
906 if (dstd
is null) assert(0, "Region: out of memory"); // this is unlikely, and hey, just crash
907 // init with default values
908 //*dstd = RData.init;
909 static immutable RData initr
= RData
.init
;
910 memcpy(dstd
, &initr
, RData
.sizeof
);
914 dstd
.rwdt
= srcd
.rwdt
;
915 dstd
.rhgt
= srcd
.rhgt
;
916 dstd
.simple
= srcd
.simple
;
917 dstd
.simpleSolid
= srcd
.simpleSolid
;
921 static if (doCopyData
) {
923 // copy complex region
924 if (srcd
.lineofs
.length
) dstd
.lineofs
= srcd
.lineofs
.dup
;
925 if (srcd
.data
.length
) dstd
.data
= srcd
.data
.dup
;
931 rdatap
= cast(usize
)dstd
;
932 GC
.addRange(rdata
, RData
.sizeof
, typeid(RData
));
937 // all data is here, so passing region struct around is painless
938 static struct RData
{
939 int rwdt
, rhgt
; // width and height
940 bool simple
= true; // is this region a simple one (i.e. rectangular, without holes)?
941 bool simpleSolid
= true; // if it is simple, is it solid or empty?
942 //WARNING! the following arrays should NEVER be shared!
943 uint[] lineofs
; // line data offset in data[]
945 // data format for each line:
947 // line items: list of increasing x coords; each coord marks start of next region
948 // all even regions are solid, i.e.
949 // 0..line[0]-1: solid region
950 // line[0]..line[1]-1: transparent (empty) region
952 // note that line[$-1] is always rwdt; it's used as sentinel too
953 // `line[0] == 0` means that first span is transparent (empty)
954 // (i.e. region starts from transparent span)
955 int rc
= 1; // refcount
960 // ////////////////////////////////////////////////////////////////////////// //
961 version(sdpy_region_test
) {
966 void dumpData (ref Region reg
) {
968 if (reg
.rdata
.simple
) { writeln("simple ", (reg
.rdata
.simpleSolid ?
"solid" : "empty"), " region"); return; }
969 foreach (immutable y
, uint ofs
; reg
.rdata
.lineofs
) {
970 if (y
> 0 && reg
.rdata
.lineofs
[y
-1] == ofs
) {
971 writefln
!"%5s:%3s: ditto"(ofs
, y
);
973 writef
!"%5s:%3s: len="(ofs
, y
);
974 write(reg
.rdata
.data
[ofs
]);
975 auto end
= ofs
+reg
.rdata
.data
[ofs
];
977 while (ofs
< end
) write("; ", reg
.rdata
.data
[ofs
++]);
984 void checkLineOffsets (ref Region reg
) {
985 if (reg
.rdata
.simple
) return;
986 foreach (immutable idx
; 1..reg
.rdata
.lineofs
.length
) {
987 if (reg
.rdata
.lineofs
[idx
-1] > reg
.rdata
.lineofs
[idx
]) assert(0, "invalid line offset data");
988 // check for two equal, but unmerged lines
989 if (reg
.rdata
.lineofs
[idx
-1] != reg
.rdata
.lineofs
[idx
]) {
990 import core
.stdc
.string
: memcmp
;
991 if (reg
.rdata
.data
[reg
.rdata
.lineofs
[idx
-1]] == reg
.rdata
.data
[reg
.rdata
.lineofs
[idx
]] &&
992 memcmp(reg
.rdata
.data
.ptr
+reg
.rdata
.lineofs
[idx
-1], reg
.rdata
.data
.ptr
+reg
.rdata
.lineofs
[idx
], reg
.SpanType
.sizeof
*reg
.rdata
.data
[reg
.rdata
.lineofs
[idx
]]) == 0)
995 assert(0, "found two identical, but not merged lines");
997 if (reg
.rdata
.data
[reg
.rdata
.lineofs
[idx
-1]] < 2) assert(0, "bad data (0)");
998 if (reg
.rdata
.data
[reg
.rdata
.lineofs
[idx
]] < 2) assert(0, "bad data (1)");
1004 void buildBitmap (ref Region reg
, int[] bmp
) {
1005 if (reg
.rdata
.simple
) {
1006 bmp
[0..reg
.width
*reg
.height
] = (reg
.rdata
.simpleSolid ?
1 : 0);
1009 bmp
[0..reg
.width
*reg
.height
] = 42;
1010 foreach (immutable y
, uint ofs
; reg
.rdata
.lineofs
) {
1011 usize a
= y
*reg
.width
;
1012 usize len
= reg
.rdata
.data
[ofs
++];
1013 if (len
< 1) assert(0, "invalid span");
1016 if (reg
.rdata
.data
[ofs
] == 0) { solid
= false; ++ofs
; }
1017 while (sx
!= reg
.width
) {
1018 // we should not have two consecutive zero-width spans
1019 if (reg
.rdata
.data
[ofs
] == 0 || reg
.rdata
.data
[ofs
] <= sx
) {
1020 //foreach (immutable idx; 0..reg.rdata.data.length) if (reg.rdata.data[idx] >= 0) writeln(idx, ": ", reg.rdata.data[idx]); else break;
1021 //assert(reg.rdata.data[ofs+1] != 0);
1022 assert(0, "invalid span");
1024 int ex
= reg
.rdata
.data
[ofs
++];
1025 bmp
[a
+sx
..a
+ex
] = (solid ?
1 : 0);
1029 debug assert(sx
== reg
.width
);
1031 foreach (immutable v
; bmp
[0..reg
.width
*reg
.height
]) if (v
== 42) assert(0, "invalid region data");
1035 int[] buildCoords (int[] bmp
, int type
, int x0
, int x1
) {
1036 bool isSolid (int x
) { return (x
>= 0 && x
< bmp
.length
&& bmp
[x
] != 0); }
1039 while (x0
<= x1
&& type
!= isSolid(x0
)) ++x0
;
1042 while (x0
<= x1
&& type
== isSolid(x0
)) ++x0
;
1049 void fuzzyEnumerator () {
1051 auto reg
= Region(uniform
!"[]"(1, 128), 1);
1053 bmp
.length
= reg
.width
*reg
.height
;
1054 ebmp
.length
= reg
.width
*reg
.height
;
1055 if (uniform
!"[]"(0, 1)) {
1056 reg
.rdata
.simpleSolid
= false;
1057 ebmp
[] = 0; // default is empty
1059 ebmp
[] = 1; // default is solid
1061 foreach (immutable tx0
; 0..1000) {
1062 checkLineOffsets(reg
);
1063 buildBitmap(reg
, bmp
[]);
1064 debug(region_more_prints
) {
1065 if (1/*bmp[] != ebmp[]*/) {
1066 assert(bmp
.length
== ebmp
.length
);
1068 foreach (immutable idx
; 0..bmp
.length
) write(bmp
[idx
]); writeln
;
1069 foreach (immutable idx
; 0..ebmp
.length
) write(ebmp
[idx
]); writeln
;
1072 assert(bmp
[] == ebmp
[]);
1073 foreach (immutable trx
; 0..200) {
1075 int x0
= uniform
!"[)"(-10, reg
.width
+10);
1076 int x1
= uniform
!"[)"(-10, reg
.width
+10);
1077 if (x0
> x1
) { auto t
= x0
; x0
= x1
; x1
= t
; }
1079 int type
= uniform
!"[]"(0, 1);
1081 reg
.spans
!false(0, x0
, x1
, (int x0
, int x1
) { coords
~= x0
; coords
~= x1
; });
1083 reg
.spans
!true(0, x0
, x1
, (int x0
, int x1
) { coords
~= x0
; coords
~= x1
; return 0; });
1085 auto ecr
= buildCoords(bmp
[], type
, x0
, x1
);
1086 assert(ecr
[] == coords
[]);
1087 // now check enumerator range
1090 foreach (ref pair
; reg
.spanRange
!false(0, x0
, x1
)) { coords
~= pair
.x0
; coords
~= pair
.x1
; }
1092 foreach (ref pair
; reg
.spanRange
!true(0, x0
, x1
)) { coords
~= pair
.x0
; coords
~= pair
.x1
; }
1094 if (ecr
[] != coords
[]) {
1095 import std
.stdio
: writeln
;
1096 writeln("\ntype=", type
);
1097 writeln("ecr=", ecr
);
1098 writeln("crd=", coords
);
1100 assert(ecr
[] == coords
[]);
1102 // now do random punch/patch
1104 int x
= uniform
!"[)"(0, reg
.width
);
1105 int y
= uniform
!"[)"(0, reg
.height
);
1106 int w
= uniform
!"[]"(0, reg
.width
);
1107 int h
= uniform
!"[]"(0, reg
.height
);
1108 int patch
= uniform
!"[]"(0, 1);
1109 debug(region_more_prints
) { import core
.stdc
.stdio
: printf
; printf(":x0=%d; x1=%d; w=%d; solid=%d\n", x
, x
+w
-1, w
, patch
); }
1110 if (patch
) reg
.patch(x
, y
, w
, h
); else reg
.punch(x
, y
, w
, h
);
1112 foreach (int dy
; y
..y
+h
) {
1113 if (dy
< 0) continue;
1114 if (dy
>= reg
.height
) break;
1115 foreach (int dx
; x
..x
+w
) {
1116 if (dx
< 0) continue;
1117 if (dx
>= reg
.width
) break;
1118 ebmp
[dy
*reg
.width
+dx
] = patch
;
1126 //enum OneSeed = 1586553857;
1131 foreach (immutable trycount
; 0..1000) {
1133 auto seed
= unpredictableSeed
;
1134 static if (is(typeof(OneSeed
))) seed
= OneSeed
;
1136 write("try: ", trycount
, "; seed = ", seed
, " ... ");
1140 static if (is(typeof(OneSeed
))) break;