2 * Simple Framebuffer Gfx/GUI lib
4 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
5 * Understanding is not required. Only obedience.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 /* *****************************************************************************
20 Anti-Grain Geometry - Version 2.1 Lite
21 Copyright (C) 2002-2003 Maxim Shemanarev (McSeem)
22 D port, additional work: Ketmar Dark
24 Permission to copy, use, modify, sell and distribute this software
25 is granted provided this copyright notice appears in all copies.
26 This software is provided "as is" without express or implied
27 warranty, and with no claim as to its suitability for any purpose.
29 The author gratefully acknowleges the support of David Turner,
30 Robert Wilhelm, and Werner Lemberg - the authors of the FreeType
31 libray - in producing this work. See http://www.freetype.org for details.
33 Initially the rendering algorithm was designed by David Turner and the
34 other authors of the FreeType library - see the above notice. I nearly
35 created a similar renderer, but still I was far from David's work.
36 I completely redesigned the original code and adapted it for Anti-Grain
37 ideas. Two functions - renderLine and renderScanLine are the core of
38 the algorithm - they calculate the exact coverage of each pixel cell
39 of the polygon. I left these functions almost as is, because there's
40 no way to improve the perfection - hats off to David and his group!
42 All other code is very different from the original.
43 ***************************************************************************** */
44 module iv
.egra
.gfx
.aggmini
.render
;
48 private import iv
.egra
.gfx
.aggmini
: DisableCopyingMixin
;
49 private import iv
.egra
.gfx
.aggmini
.enums
: FillRule
;
52 import iv
.egra
.gfx
.base
;
53 import iv
.egra
.gfx
.lowlevel
;
56 // ////////////////////////////////////////////////////////////////////////// //
58 align(1) struct Cell
{
61 //static uint packCoord (in int x, in int y) pure nothrow @safe @nogc { pragma(inline, true); return (y<<16)|(x&0xffff); }
69 static assert(x
.sizeof
== 2 && y
.sizeof
== 2, "oops");
70 static assert(y
.offsetof
== x
.offsetof
+2, "oops");
72 public nothrow @trusted @nogc:
73 this (in int cx
, in int cy
, in int c
, in int a
) pure {
75 //packedCoord = packCoord(cx, cy);
76 x
= cast(short)cx
; y
= cast(short)cy
;
81 bool isEqual (in int cx
, in int cy
) pure const {
83 //return (x == cx && y == cy);
84 return (((cast(short)cx
)^x
)|
((cast(short)cy
)^y
)) == 0;
87 //uint getPackedCoord () const pure nothrow @safe @nogc { pragma(inline, true); return (cast(uint)y<<16)|(cast(uint)cast(ushort)x); }
88 uint getPackedCoord () const pure nothrow @trusted @nogc { pragma(inline
, true); return *(cast(const uint*)&x
); }
90 bool isLessThan (in ref Cell other
) const pure nothrow @trusted @nogc {
92 return (y
< other
.y ||
(y
== other
.y
&& x
< other
.x
));
95 //@property int x () const pure { pragma(inline, true); return cast(int)cast(short)(packedCoord&0xffff); }
96 //@property int y () const pure { pragma(inline, true); return cast(int)cast(short)(packedCoord>>16); }
98 void setCover (in int c
, in int a
) { pragma(inline
, true); cover
= c
; area
= a
; }
99 void addCover (in int c
, in int a
) { pragma(inline
, true); cover
+= c
; area
+= a
; }
101 void setCoord (in int cx
, in int cy
) {
102 pragma(inline
, true);
103 //packedCoord = packCoord(cx, cy);
104 x
= cast(short)cx
; y
= cast(short)cy
;
107 void set (in int cx
, in int cy
, in int c
, in int a
) {
108 pragma(inline
, true);
109 //packedCoord = packCoord(cx, cy);
110 x
= cast(short)cx
; y
= cast(short)cy
;
117 // ////////////////////////////////////////////////////////////////////////// //
118 /* An internal class that implements the main rasterization algorithm.
119 Used in the rasterizer. Should not be used direcly.
122 private nothrow @trusted @nogc:
124 CellBlockShift
= 12U,
125 CellBlockSize
= cast(uint)(1<<CellBlockShift
),
126 CellBlockMask
= cast(uint)(CellBlockSize
-1),
127 CellBlockPool
= 256U,
128 CellBlockLimit
= 1024U,
131 enum QSortThreshold
= 9;
147 Cell mCurCell
= Cell(0x7fff, 0x7fff, 0, 0);
152 int mMinX
= 0x7fffffff;
153 int mMinY
= 0x7fffffff;
154 int mMaxX
= -0x7fffffff;
155 int mMaxY
= -0x7fffffff;
156 uint mFlags
= SortRequired
;
159 void allocateBlock () {
160 import core
.stdc
.stdlib
: realloc
;
161 if (mCurBlock
>= mNumBlocks
) {
162 import core
.stdc
.string
: memset
;
163 if (mNumBlocks
>= mMaxBlocks
) {
164 Cell
** newCells
= cast(Cell
**)realloc(mCells
, (mMaxBlocks
+CellBlockPool
)*(Cell
*).sizeof
);
165 if (newCells
is null) assert(0, "out of memory");
167 mMaxBlocks
+= CellBlockPool
;
169 auto cc
= cast(Cell
*)realloc(null, Cell
.sizeof
*CellBlockSize
);
170 if (cc
is null) assert(0, "out of memory");
171 memset(cc
, 0, Cell
.sizeof
*CellBlockSize
);
172 //foreach (ref c; cc[0..CellBlockSize]) c = Cell.init;
173 mCells
[mNumBlocks
++] = cc
;
175 mCurCellPtr
= mCells
[mCurBlock
++];
179 mixin(DisableCopyingMixin
);
182 import core
.stdc
.stdlib
: free
;
185 Cell
** ptr
= mCells
+mNumBlocks
-1;
186 while (mNumBlocks
--) {
197 mCurCell
.set(0x7fff, 0x7fff, 0, 0);
198 mFlags |
= SortRequired
;
199 mFlags
&= ~NotClosed
;
206 void moveTo (in int x
, in int y
) {
207 if ((mFlags
&SortRequired
) == 0) reset();
208 if (mFlags
&NotClosed
) lineTo(mCloseX
, mCloseY
);
209 setCurCell(x
>>PolyBaseShift
, y
>>PolyBaseShift
);
214 void lineTo (in int x
, in int y
) {
215 if ((mFlags
&SortRequired
) && ((mCurX^x
)|
(mCurY^y
))) {
216 int c
= mCurX
>>PolyBaseShift
;
217 if (c
< mMinX
) mMinX
= c
;
219 if (c
> mMaxX
) mMaxX
= c
;
221 c
= x
>>PolyBaseShift
;
222 if (c
< mMinX
) mMinX
= c
;
224 if (c
> mMaxX
) mMaxX
= c
;
226 renderLine(mCurX
, mCurY
, x
, y
);
233 @property float currX () const pure { pragma(inline
, true); return cast(float)mCurX
/cast(float)PolyBaseSize
; }
234 @property float currY () const pure { pragma(inline
, true); return cast(float)mCurY
/cast(float)PolyBaseSize
; }
236 @property int minX () const pure { pragma(inline
, true); return mMinX
; }
237 @property int minY () const pure { pragma(inline
, true); return mMinY
; }
238 @property int maxX () const pure { pragma(inline
, true); return mMaxX
; }
239 @property int maxY () const pure { pragma(inline
, true); return mMaxY
; }
241 @property uint numCells () const pure { pragma(inline
, true); return mNumCells
; }
243 const(Cell
)** cells () {
244 if (mFlags
&NotClosed
) {
245 lineTo(mCloseX
, mCloseY
);
246 mFlags
&= ~NotClosed
;
248 // perform sort only the first time
249 if (mFlags
&SortRequired
) {
251 if (mNumCells
== 0) return null;
253 mFlags
&= ~SortRequired
;
255 return cast(const(Cell
)**)mSortedCells
;
260 if (mCurCell
.area|mCurCell
.cover
) {
261 if ((mNumCells
&CellBlockMask
) == 0) {
262 if (mNumBlocks
>= CellBlockLimit
) return;
265 *mCurCellPtr
++ = mCurCell
;
270 void setCurCell (in int x
, in int y
) {
271 if (!mCurCell
.isEqual(x
, y
)) {
273 mCurCell
.set(x
, y
, 0, 0);
277 void renderScanLine (in int ey
, in int x1
, int y1
, in int x2
, in int y2
) {
278 immutable int ex2
= x2
>>PolyBaseShift
;
280 // trivial case; happens often
286 int ex1
= x1
>>PolyBaseShift
;
287 immutable int fx1
= x1
&PolyBaseMask
;
288 immutable int fx2
= x2
&PolyBaseMask
;
290 // everything is located in a single cell: that is easy!
292 immutable int delta
= y2
-y1
;
293 mCurCell
.addCover(delta
, (fx1
+fx2
)*delta
);
297 // ok, we'll have to render a run of adjacent cells on the same scanline...
298 int p
= (PolyBaseSize
-fx1
)*(y2
-y1
);
299 int first
= PolyBaseSize
;
313 if (mod
< 0) { --delta
; mod
+= dx
; }
315 mCurCell
.addCover(delta
, (fx1
+first
)*delta
);
322 p
= PolyBaseSize
*(y2
-y1
+delta
);
325 if (rem
< 0) { --lift
; rem
+= dx
; }
330 if (mod
>= 0) { mod
-= dx
; ++delta
; }
331 mCurCell
.addCover(delta
, PolyBaseSize
*delta
);
339 mCurCell
.addCover(delta
, (fx2
+PolyBaseSize
-first
)*delta
);
342 void renderLine (in int x1
, in int y1
, in int x2
, in int y2
) {
343 int ey1
= y1
>>PolyBaseShift
;
344 immutable int ey2
= y2
>>PolyBaseShift
;
346 if (ey1
< mMinY
) mMinY
= ey1
;
347 if (ey1
+1 > mMaxY
) mMaxY
= ey1
+1;
348 if (ey2
< mMinY
) mMinY
= ey2
;
349 if (ey2
+1 > mMaxY
) mMaxY
= ey2
+1;
351 immutable int fy1
= y1
&PolyBaseMask
;
352 immutable int fy2
= y2
&PolyBaseMask
;
354 // everything is on a single scanline
356 renderScanLine(ey1
, x1
, fy1
, x2
, fy2
);
364 //int p, rem, mod, lift, delta, first, incr;
366 // vertical line: we have to calculate start and end cells,
367 // and then the common values of the area and coverage for
368 // all cells of the line. we know exactly there's only one
369 // cell, so, we don't have to call renderScanLine().
372 int ex
= x1
>>PolyBaseShift
;
373 int twoFx
= (x1
-(ex
<<PolyBaseShift
))<<1;
376 int first
= PolyBaseSize
;
377 if (dy
< 0) { first
= 0; incr
= -1; }
379 immutable int xFrom
= x1
;
381 //renderScanLine(ey1, xFrom, fy1, xFrom, first);
382 int delta
= first
-fy1
;
383 mCurCell
.addCover(delta
, twoFx
*delta
);
388 delta
= first
+first
-PolyBaseSize
;
391 //renderScanLine(ey1, xFrom, PolyBaseSize - first, xFrom, first);
392 mCurCell
.setCover(delta
, area
);
396 //renderScanLine(ey1, xFrom, PolyBaseSize - first, xFrom, fy2);
397 delta
= fy2
-PolyBaseSize
+first
;
398 mCurCell
.addCover(delta
, twoFx
*delta
);
402 // ok, we have to render several scanlines
403 int p
= (PolyBaseSize
-fy1
)*dx
;
404 int first
= PolyBaseSize
;
416 if (mod
< 0) { --delta
; mod
+= dy
; }
418 int xFrom
= x1
+delta
;
419 renderScanLine(ey1
, x1
, fy1
, xFrom
, first
);
422 setCurCell(xFrom
>>PolyBaseShift
, ey1
);
429 if (rem
< 0) { --lift
; rem
+= dy
; }
435 if (mod
>= 0) { mod
-= dy
; ++delta
; }
437 immutable int xTo
= xFrom
+delta
;
438 renderScanLine(ey1
, xFrom
, PolyBaseSize
-first
, xTo
, first
);
442 setCurCell(xFrom
>>PolyBaseShift
, ey1
);
446 renderScanLine(ey1
, xFrom
, PolyBaseSize
-first
, x2
, fy2
);
450 static void qsortCells (Cell
** start
, uint num
) {
452 static void swapCells (Cell** a, Cell** b) nothrow @trusted @nogc {
453 pragma(inline, true);
459 static bool lessThan (in Cell** a, in Cell** b) pure nothrow @trusted @nogc {
460 pragma(inline, true);
462 return ((**a).packedCoord < (**b).packedCoord);
464 return (*a).isLessThan(**b);
470 static enum swapCellsMixin(string a
, string b
) = `
471 { auto temp_ = *(`~a
~`);
476 static enum lessThanMixin(string a
, string b
) = `(*(`~a
~`)).isLessThan(**(`~b
~`))`;
478 Cell
**[80] stack
= void;
488 immutable int len
= cast(int)(limit
-base
);
490 if (len
> QSortThreshold
) {
491 // we use base + len/2 as the pivot
492 //auto pivot = base+len/2;
493 auto pivot
= base
+(len
>>1);
494 mixin(swapCellsMixin
!("base", "pivot"));
499 // now ensure that *i <= *base <= *j
500 if (mixin(lessThanMixin
!("j", "i"))) mixin(swapCellsMixin
!("i", "j"));
501 if (mixin(lessThanMixin
!("base", "i"))) mixin(swapCellsMixin
!("base", "i"));
502 if (mixin(lessThanMixin
!("j", "base"))) mixin(swapCellsMixin
!("base", "j"));
505 do { ++i
; } while (mixin(lessThanMixin
!("i", "base")));
506 do { --j
; } while (mixin(lessThanMixin
!("base", "j")));
508 mixin(swapCellsMixin
!("i", "j"));
511 mixin(swapCellsMixin
!("base", "j"));
513 // now, push the largest sub-array
514 if (j
-base
> limit
-i
) {
525 // the sub-array is small, perform insertion sort
528 for (; i
< limit
; j
= i
, ++i
) {
529 for (; mixin(lessThanMixin
!("j+1", "j")); --j
) {
530 mixin(swapCellsMixin
!("j+1", "j"));
531 if (j
== base
) break;
534 if (top
> stack
.ptr
) {
546 if (mNumCells
== 0) return;
548 if (mNumCells
> mSortedSize
) {
549 import core
.stdc
.stdlib
: realloc
;
550 mSortedSize
= mNumCells
;
551 mSortedCells
= cast(Cell
**)realloc(mSortedCells
, (mNumCells
+1)*(Cell
*).sizeof
);
554 Cell
** sortedPtr
= mSortedCells
;
555 Cell
** blockPtr
= mCells
;
558 uint nb
= mNumCells
>>CellBlockShift
;
561 cellPtr
= *blockPtr
++;
562 uint i
= CellBlockSize
;
563 while (i
--) *sortedPtr
++ = cellPtr
++;
566 cellPtr
= *blockPtr
++;
567 uint i
= mNumCells
&CellBlockMask
;
568 while (i
--) *sortedPtr
++ = cellPtr
++;
569 mSortedCells
[mNumCells
] = null;
570 qsortCells(mSortedCells
, mNumCells
);
575 // ////////////////////////////////////////////////////////////////////////// //
576 /*This class is used to transfer data from class outline (or a similar one)
577 to the rendering buffer. It's organized very simple. The class stores
578 information of horizontal spans to render it into a pixel-map buffer.
579 Each span has initial X, length, and an array of bytes that determine the
580 alpha-values for each pixel. So, the restriction of using this class is 256
581 levels of Anti-Aliasing, which is quite enough for any practical purpose.
582 Before using this class you should know the minimal and maximal pixel
583 coordinates of your scanline. The protocol of using is:
585 2. addCell() / addSpan() - accumulate scanline. You pass Y-coordinate
586 into these functions in order to make scanline know the last Y. Before
587 calling addCell() / addSpan() you should check with method isReady(y)
588 if the last Y has changed. It also checks if the scanline is not empty.
589 When forming one scanline the next X coordinate must be always greater
590 than the last stored one, i.e. it works only with ordered coordinates.
591 3. If the current scanline isReady() you should render it and then call
592 resetSpans() before adding new cells/spans.
596 Scanline provides an iterator class that allows you to extract
597 the spans and the cover values for each pixel. Be aware that clipping
598 has not been done yet, so you should perform it yourself.
600 -------------------------------------------------------------------------
601 int baseX = sl.baseX; // base X. Should be added to the span's X
602 // "sl" is a const reference to the
603 // scanline passed in.
605 int y = sl.y; // Y-coordinate of the scanline
607 ************************************
608 ...Perform vertical clipping here...
609 ************************************
611 ubyte* row = mRBuf[y]; // the the address of the beginning of the current row
613 auto span = scanline.iterator;
615 uint spanCount = scanline.spanCount(); // number of spans; it's guaranteed that
616 // spanCount is always greater than 0
619 int x = span.next+baseX; // the beginning X of the span
621 const(ubyte)* covers = span.covers; // the array of the cover values
623 int pixelCount = span.pixelCount; // number of pixels of the span;
624 // always greater than 0, still we
625 // shoud use "int" instead of "uint"
626 // because it's more convenient for clipping
628 **************************************
629 ...Perform horizontal clipping here...
630 ...you have x, covers, and pixCount..
631 **************************************
633 ubyte* dst = row+x; // calculate the start address of the row;
634 // in this case we assume a simple
635 // grayscale image 1-byte per pixel
637 *dst++ = *covers++; // hypotetical rendering
638 } while (--pixelCount);
639 } while (--spanCount); // spanCount cannot be 0, so this loop is quite safe
640 ------------------------------------------------------------------------
642 The question is: why should we accumulate the whole scanline when we
643 could render just separate spans when they're ready?
644 That's because using the scaline is in general faster. When is consists
645 of more than one span the conditions for the processor cash system
646 are better, because switching between two different areas of memory
647 (that can be large ones) occures less frequently.
661 ubyte** mCurStartPtr
;
667 static struct Iterator
{
669 const(ubyte)* mCovers
;
670 const(ushort)* mCurCount
;
671 const(ubyte*)* mCurStartPtr
;
673 public nothrow @trusted @nogc:
674 mixin(DisableCopyingMixin
);
676 this (in ref ScanLine sl
) pure {
677 pragma(inline
, true);
678 mCovers
= sl
.mCovers
;
679 mCurCount
= sl
.mCounts
;
680 mCurStartPtr
= sl
.mStartPtrs
;
684 pragma(inline
, true);
687 return cast(int)(*mCurStartPtr
-mCovers
);
690 @property int pixelCount () const pure { pragma(inline
, true); return cast(int)(*mCurCount
); }
691 @property const(ubyte)* covers () const pure { pragma(inline
, true); return *mCurStartPtr
; }
694 public nothrow @trusted @nogc:
695 mixin(DisableCopyingMixin
);
698 import core
.stdc
.stdlib
: free
;
699 if (mCounts
!is null) free(mCounts
);
700 if (mStartPtrs
!is null) free(mStartPtrs
);
701 if (mCovers
!is null) free(mCovers
);
704 auto iterator () const pure { pragma(inline
, true); return Iterator(this); }
706 void reset (int minX
, int maxX
, int dx
=0, int dy
=0) {
707 uint maxLen
= maxX
-minX
+2;
708 if (maxLen
> mMaxLen
) {
709 import core
.stdc
.stdlib
: realloc
;
710 mCovers
= cast(ubyte*)realloc(mCovers
, maxLen
*mCovers
[0].sizeof
);
711 if (mCovers
is null) assert(0, "out of memory");
712 mStartPtrs
= cast(ubyte**)realloc(mStartPtrs
, mStartPtrs
[0].sizeof
*maxLen
);
713 if (mStartPtrs
is null) assert(0, "out of memory");
714 mCounts
= cast(ushort*)realloc(mCounts
, mCounts
[0].sizeof
*maxLen
);
715 if (mCounts
is null) assert(0, "out of memory");
724 mCurStartPtr
= mStartPtrs
;
729 pragma(inline
, true);
733 mCurStartPtr
= mStartPtrs
;
737 void addSpan (int x
, int y
, uint num
, uint cover
) {
738 import core
.stdc
.string
: memset
;
740 memset(mCovers
+x
, cover
, num
);
742 (*mCurCount
) += cast(ushort)num
;
744 *++mCurCount
= cast(ushort)num
;
745 *++mCurStartPtr
= mCovers
+x
;
752 void addCell (int x
, int y
, uint cover
) {
754 mCovers
[x
] = cast(ubyte)cover
;
759 *++mCurStartPtr
= mCovers
+x
;
766 @property bool isReady (int y
) const pure { pragma(inline
, true); return (mNumSpans
&& (y^mLastY
)); }
767 @property int baseX () const pure { pragma(inline
, true); return mMinX
+mDX
; }
768 @property int y () const pure { pragma(inline
, true); return mLastY
+mDY
; }
769 @property uint spanCount () const pure { pragma(inline
, true); return mNumSpans
; }
773 // ////////////////////////////////////////////////////////////////////////// //
774 /* This class template is used basically for rendering scanlines.
776 auto ras = Rasterizer();
784 ras.render(ren, Color(255, 127, 0));
786 public struct Renderer
{
787 public nothrow @trusted @nogc:
788 mixin(DisableCopyingMixin
);
790 static void render (in ref ScanLine sl
, in GxColor clr
, in ref GxRect clipRect
) {
791 if (clipRect
.size
.h
< 1 || clipRect
.size
.w
< 1 || sl
.y
< clipRect
.pos
.y || sl
.y
>= clipRect
.pos
.y
+clipRect
.size
.h
) return;
792 immutable GxColorU c
= GxColorU(clr
);
793 uint spanCount
= sl
.spanCount
;
794 immutable int baseX
= sl
.baseX
;
795 uint* row
= vglTexBuf
+cast(uint)sl
.y
*cast(uint)VBufWidth
;
796 auto span
= sl
.iterator
;
797 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "***RENDER: y=%d; spanCount=%u; baseX=%d\n", sl.y, spanCount, baseX); }
799 int x
= span
.next
+baseX
;
800 int pixelCount
= span
.pixelCount
;
802 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, " y=%d; x=%d; pixelCount=%d\n", sl.y, x, pixelCount); }
803 if (clipRect
.clipHStripe(ref x
, sl
.y
, ref pixelCount
, &leftSkip
)) {
804 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, " y=%d; x=%d; pixelCount=%d; leftSkip=%d\n", sl.y, x, pixelCount, leftSkip); }
805 const(ubyte)* covers
= span
.covers
+leftSkip
;
806 memBlendColorCoverage(row
+x
, covers
, clr
, pixelCount
);
808 } while (--spanCount
);
813 // ////////////////////////////////////////////////////////////////////////// //
814 /* These constants determine the subpixel accuracy, to be more precise,
815 the number of bits of the fractional part of the coordinates.
816 The possible coordinate capacity in bits can be calculated by formula:
817 sizeof(int) * 8 - PolyBaseShift * 2, i.e, for 32-bit integers and
818 8-bits fractional part the capacity is 16 bits or [-32768...32767].
822 PolyBaseSize
= cast(uint)(1<<PolyBaseShift
),
823 PolyBaseMask
= cast(uint)(PolyBaseSize
-1),
827 int polyCoord (in float c
) pure nothrow @safe @nogc { pragma(inline
, true); return (cast(int)(c
*(PolyBaseSize
*2))+1)/2; }
829 int dbl2fix (in float c
) pure nothrow @safe @nogc { pragma(inline
, true); return cast(int)(c
*PolyBaseSize
); }
830 float fix2dbl (in int c
) pure nothrow @safe @nogc { pragma(inline
, true); return cast(float)c
/cast(float)PolyBaseSize
; }
833 // ////////////////////////////////////////////////////////////////////////// //
834 /* Polygon rasterizer that is used to render filled polygons with
835 high-quality Anti-Aliasing. Internally, by default, the class uses
836 integer coordinates in format 24.8, i.e. 24 bits for integer part
837 and 8 bits for fractional - see PolyBaseShift. This class can be
838 used in the following way:
840 1. fillRule = FillRule.EvenOdd; // optional
841 2. gamma() - optional.
843 4. moveTo(x, y) / lineTo(x, y) - make the polygon. One can create
844 more than one contour, but each contour must consist of at least 3
845 vertices, i.e. moveTo(x1, y1); lineTo(x2, y2); lineTo(x3, y3);
846 is the absolute minimum of vertices that define a triangle.
847 The algorithm does not check either the number of vertices nor
848 coincidence of their coordinates, but in the worst case it just
850 The orger of the vertices (clockwise or counterclockwise)
851 is important when using the non-zero filling rule (FillNonZero).
852 In this case the vertex order of all the contours must be the same
853 if you want your intersecting polygons to be without "holes".
854 You actually can use different vertices order. If the contours do not
855 intersect each other the order is not important anyway. If they do,
856 contours with the same vertex order will be rendered without "holes"
857 while the intersecting contours with different orders will have "holes".
859 fillRule() and gamma() can be called anytime before "sweeping".
861 public struct Rasterizer
{
862 public nothrow @trusted @nogc:
864 AAShift
= ScanLine
.AAShift
,
874 FillRule mFillingRule
= FillRule
.NonZero
;
875 ubyte[256] mGamma
= DefaultGamma
[];
881 float gradMidP
= -1.0f;
885 mixin(DisableCopyingMixin
);
887 void reset () { mOutline
.reset(); }
889 @property FillRule
fillRule () const pure { pragma(inline
, true); return mFillingRule
; }
890 @property void fillRule (FillRule v
) { pragma(inline
, true); mFillingRule
= v
; }
892 void gamma (in float g
) {
893 foreach (immutable uint i
; 0..256) {
894 //import std.math : pow;
895 import core
.stdc
.math
: powf
;
896 mGamma
.ptr
[i
] = cast(ubyte)(powf(cast(float)i
/255.0f, g
)*255.0f);
900 void gamma (const(ubyte)[] g
) {
901 if (g
.length
!= 256) assert(0, "invalid gamma array");
902 mGamma
[] = g
[0..256];
905 @property float currX () const pure { pragma(inline
, true); return mOutline
.currX
; }
906 @property float currY () const pure { pragma(inline
, true); return mOutline
.currY
; }
908 void moveToFixed (int x
, int y
) {
909 if ((x
>>PolyBaseShift
) <= short.min
+8 ||
(x
>>PolyBaseShift
) >= short.max
-8 ||
910 (y
>>PolyBaseShift
) <= short.min
+8 ||
(y
>>PolyBaseShift
) >= short.max
-8) assert(0, "coordinates out of bounds");
911 mOutline
.moveTo(x
, y
);
914 void lineToFixed (int x
, int y
) {
915 if ((x
>>PolyBaseShift
) <= short.min
+8 ||
(x
>>PolyBaseShift
) >= short.max
-8 ||
916 (y
>>PolyBaseShift
) <= short.min
+8 ||
(y
>>PolyBaseShift
) >= short.max
-8) assert(0, "coordinates out of bounds");
917 mOutline
.lineTo(x
, y
);
920 void moveTo (in float x
, in float y
) { mOutline
.moveTo(polyCoord(x
), polyCoord(y
)); }
921 void lineTo (in float x
, in float y
) { mOutline
.lineTo(polyCoord(x
), polyCoord(y
)); }
923 @property int minX () const pure { pragma(inline
, true); return mOutline
.minX
; }
924 @property int minY () const pure { pragma(inline
, true); return mOutline
.minY
; }
925 @property int maxX () const pure { pragma(inline
, true); return mOutline
.maxX
; }
926 @property int maxY () const pure { pragma(inline
, true); return mOutline
.maxY
; }
928 private uint calculateAlpha (in int area
) const pure {
929 int cover
= area
>>(PolyBaseShift
*2+1-AAShift
);
930 if (cover
< 0) cover
= -cover
;
931 if (mFillingRule
== FillRule
.EvenOdd
) {
933 if (cover
> AANum
) cover
= AA2Num
-cover
;
935 if (cover
> AAMask
) cover
= AAMask
;
939 void render (in ref GxRect clipRect
, in GxColor c
, int dx
=0, int dy
=0) {
940 const(Cell
)** cells
= mOutline
.cells();
941 if (mOutline
.numCells() == 0) return;
943 immutable bool grad
= useGradient
;
944 float gmp
= gradMidP
;
945 bool useMidP
= false;
946 float gradY0
, gradY1
, gradTotal
;
954 if (gmp
== 0.0f) { gC1
= gCM
; gmp
= -1.0f; }
955 useMidP
= (gmp
> 0.0f && gmp
< 1.0f && minY
< maxY
);
956 gradTotal
= 1.0f/(gradY1
-gradY0
+1.0f);
958 if (gxIsTransparent(c
)) return;
961 void flushScanline () {
962 if (mScanline
.y
< clipRect
.pos
.y || mScanline
.y
> clipRect
.y1
) return;
965 Renderer
.render(mScanline
, c
, clipRect
);
970 immutable float gt
= (cast(float)mScanline
.y
-gradY0
)*gradTotal
;
973 clr
= gxInterpolateColor(gC0
, gCM
, gt
/gmp
);
975 clr
= gxInterpolateColor(gCM
, gC1
, (gt
-gmp
)/(1.0f-gmp
));
978 clr
= gxInterpolateColor(gC0
, gC1
, gt
);
980 Renderer
.render(mScanline
, clr
, clipRect
);
983 mScanline
.reset(mOutline
.minX
, mOutline
.maxX
, dx
, dy
);
986 const(Cell
)* curCell
= *cells
++;
989 const(Cell
)* startCell
= curCell
;
991 uint coord
= curCell
.getPackedCoord
;
995 int area
= startCell
.area
;
996 cover
+= startCell
.cover
;
998 // accumulate all start cells
999 while ((curCell
= *cells
++) !is null) {
1000 if (curCell
.getPackedCoord
!= coord
) break;
1001 area
+= curCell
.area
;
1002 cover
+= curCell
.cover
;
1006 int alpha
= calculateAlpha((cover
<<(PolyBaseShift
+1))-area
);
1008 if (mScanline
.isReady(y
)) {
1010 mScanline
.resetSpans();
1012 mScanline
.addCell(x
, y
, mGamma
.ptr
[alpha
]);
1017 if (!curCell
) break;
1019 if (curCell
.x
> x
) {
1020 int alpha
= calculateAlpha(cover
<<(PolyBaseShift
+1));
1022 if (mScanline
.isReady(y
)) {
1024 mScanline
.resetSpans();
1026 mScanline
.addSpan(x
, y
, curCell
.x
-x
, mGamma
.ptr
[alpha
]);
1031 if (mScanline
.spanCount
) flushScanline();
1034 // returns "gamma alpha" (0 means "no hit")
1035 ubyte hitTest (int tx
, int ty
) {
1036 const(Cell
)** cells
= mOutline
.cells();
1037 if (mOutline
.numCells
== 0) return 0;
1040 const(Cell
)* curCell
= *cells
++;
1043 const(Cell
)* startCell
= curCell
;
1045 uint coord
= curCell
.getPackedCoord
;
1049 if (y
> ty
) return false;
1051 int area
= startCell
.area
;
1052 cover
+= startCell
.cover
;
1054 while ((curCell
= *cells
++) !is null) {
1055 if (curCell
.getPackedCoord
!= coord
) break;
1056 area
+= curCell
.area
;
1057 cover
+= curCell
.cover
;
1061 immutable int alpha
= calculateAlpha((cover
<<(PolyBaseShift
+1))-area
);
1062 if (alpha
&& mGamma
.ptr
[alpha
]) {
1063 if (tx
== x
&& ty
== y
) return mGamma
.ptr
[alpha
];
1068 if (curCell
is null) break;
1070 if (curCell
.x
> x
) {
1071 immutable int alpha
= calculateAlpha(cover
<<(PolyBaseShift
+1));
1072 if (alpha
&& mGamma
.ptr
[alpha
]) {
1073 if (ty
== y
&& tx
>= x
&& tx
<= curCell
.x
) return mGamma
.ptr
[alpha
];
1082 static immutable ubyte[256] DefaultGamma
= [
1083 0, 0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 7, 7, 8, 8,
1084 9, 10, 10, 11, 11, 12, 13, 13, 14, 14, 15, 16, 16, 17, 18, 18,
1085 19, 19, 20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27, 27, 28,
1086 29, 29, 30, 30, 31, 32, 32, 33, 34, 34, 35, 36, 36, 37, 37, 38,
1087 39, 39, 40, 41, 41, 42, 43, 43, 44, 45, 45, 46, 47, 47, 48, 49,
1088 49, 50, 51, 51, 52, 53, 53, 54, 55, 55, 56, 57, 57, 58, 59, 60,
1089 60, 61, 62, 62, 63, 64, 65, 65, 66, 67, 68, 68, 69, 70, 71, 71,
1090 72, 73, 74, 74, 75, 76, 77, 78, 78, 79, 80, 81, 82, 83, 83, 84,
1091 85, 86, 87, 88, 89, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
1092 100,101,101,102,103,104,105,106,107,108,109,110,111,112,114,115,
1093 116,117,118,119,120,121,122,123,124,126,127,128,129,130,131,132,
1094 134,135,136,137,139,140,141,142,144,145,146,147,149,150,151,153,
1095 154,155,157,158,159,161,162,164,165,166,168,169,171,172,174,175,
1096 177,178,180,181,183,184,186,188,189,191,192,194,195,197,199,200,
1097 202,204,205,207,209,210,212,214,215,217,219,220,222,224,225,227,
1098 229,230,232,234,236,237,239,241,242,244,246,248,249,251,253,255