egra: added radio button widget; use agg mini to draw buttons (check marks, default...
[iv.d.git] / egra / gfx / aggmini / render.d
blobe59cd89cff83b72f0c07404703d86e58dd5a6209
1 /*
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;
45 private:
46 nothrow @safe @nogc:
48 private import iv.egra.gfx.aggmini : DisableCopyingMixin;
49 private import iv.egra.gfx.aggmini.enums : FillRule;
51 import iv.bclamp;
52 import iv.egra.gfx.base;
53 import iv.egra.gfx.lowlevel;
56 // ////////////////////////////////////////////////////////////////////////// //
57 // a pixel cell
58 align(1) struct Cell {
59 align(1):
60 public:
61 //static uint packCoord (in int x, in int y) pure nothrow @safe @nogc { pragma(inline, true); return (y<<16)|(x&0xffff); }
63 public:
64 //uint packedCoord;
65 short x, y;
66 int cover;
67 int area;
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 {
74 pragma(inline, true);
75 //packedCoord = packCoord(cx, cy);
76 x = cast(short)cx; y = cast(short)cy;
77 cover = c;
78 area = a;
81 bool isEqual (in int cx, in int cy) pure const {
82 pragma(inline, true);
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 {
91 pragma(inline, true);
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;
111 cover = c;
112 area = a;
117 // ////////////////////////////////////////////////////////////////////////// //
118 /* An internal class that implements the main rasterization algorithm.
119 Used in the rasterizer. Should not be used direcly.
121 struct Outline {
122 private nothrow @trusted @nogc:
123 enum : uint {
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;
133 enum : uint {
134 NotClosed = 1U,
135 SortRequired = 2U,
138 private:
139 uint mNumBlocks;
140 uint mMaxBlocks;
141 uint mCurBlock;
142 uint mNumCells;
143 Cell** mCells;
144 Cell* mCurCellPtr;
145 Cell** mSortedCells;
146 uint mSortedSize;
147 Cell mCurCell = Cell(0x7fff, 0x7fff, 0, 0);
148 int mCurX;
149 int mCurY;
150 int mCloseX;
151 int mCloseY;
152 int mMinX = 0x7fffffff;
153 int mMinY = 0x7fffffff;
154 int mMaxX = -0x7fffffff;
155 int mMaxY = -0x7fffffff;
156 uint mFlags = SortRequired;
158 private:
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");
166 mCells = newCells;
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++];
178 public:
179 mixin(DisableCopyingMixin);
181 ~this () {
182 import core.stdc.stdlib : free;
183 free(mSortedCells);
184 if (mNumBlocks) {
185 Cell** ptr = mCells+mNumBlocks-1;
186 while (mNumBlocks--) {
187 free(*ptr);
188 --ptr;
190 free(mCells);
194 void reset () {
195 mNumCells = 0;
196 mCurBlock = 0;
197 mCurCell.set(0x7fff, 0x7fff, 0, 0);
198 mFlags |= SortRequired;
199 mFlags &= ~NotClosed;
200 mMinX = 0x7fffffff;
201 mMinY = 0x7fffffff;
202 mMaxX = -0x7fffffff;
203 mMaxY = -0x7fffffff;
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);
210 mCloseX = mCurX = x;
211 mCloseY = mCurY = y;
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;
218 ++c;
219 if (c > mMaxX) mMaxX = c;
221 c = x>>PolyBaseShift;
222 if (c < mMinX) mMinX = c;
223 ++c;
224 if (c > mMaxX) mMaxX = c;
226 renderLine(mCurX, mCurY, x, y);
227 mCurX = x;
228 mCurY = y;
229 mFlags |= NotClosed;
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) {
250 addCurCell();
251 if (mNumCells == 0) return null;
252 sortCells();
253 mFlags &= ~SortRequired;
255 return cast(const(Cell)**)mSortedCells;
258 private:
259 void addCurCell () {
260 if (mCurCell.area|mCurCell.cover) {
261 if ((mNumCells&CellBlockMask) == 0) {
262 if (mNumBlocks >= CellBlockLimit) return;
263 allocateBlock();
265 *mCurCellPtr++ = mCurCell;
266 ++mNumCells;
270 void setCurCell (in int x, in int y) {
271 if (!mCurCell.isEqual(x, y)) {
272 addCurCell();
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
281 if (y1 == y2) {
282 setCurCell(ex2, ey);
283 return;
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!
291 if (ex1 == ex2) {
292 immutable int delta = y2-y1;
293 mCurCell.addCover(delta, (fx1+fx2)*delta);
294 return;
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;
300 int incr = 1;
302 int dx = x2-x1;
304 if (dx < 0) {
305 p = fx1*(y2-y1);
306 first = 0;
307 incr = -1;
308 dx = -dx;
311 int delta = p/dx;
312 int mod = p%dx;
313 if (mod < 0) { --delta; mod += dx; }
315 mCurCell.addCover(delta, (fx1+first)*delta);
317 ex1 += incr;
318 setCurCell(ex1, ey);
319 y1 += delta;
321 if (ex1 != ex2) {
322 p = PolyBaseSize*(y2-y1+delta);
323 int lift = p/dx;
324 int rem = p%dx;
325 if (rem < 0) { --lift; rem += dx; }
326 mod -= dx;
327 while (ex1 != ex2) {
328 delta = lift;
329 mod += rem;
330 if (mod >= 0) { mod -= dx; ++delta; }
331 mCurCell.addCover(delta, PolyBaseSize*delta);
332 y1 += delta;
333 ex1 += incr;
334 setCurCell(ex1, ey);
338 delta = y2-y1;
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
355 if (ey1 == ey2) {
356 renderScanLine(ey1, x1, fy1, x2, fy2);
357 return;
360 int dx = x2-x1;
361 int dy = y2-y1;
363 //int xFrom, xTo;
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().
370 int incr = 1;
371 if (dx == 0) {
372 int ex = x1>>PolyBaseShift;
373 int twoFx = (x1-(ex<<PolyBaseShift))<<1;
374 int area;
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);
385 ey1 += incr;
386 setCurCell(ex, ey1);
388 delta = first+first-PolyBaseSize;
389 area = twoFx*delta;
390 while (ey1 != ey2) {
391 //renderScanLine(ey1, xFrom, PolyBaseSize - first, xFrom, first);
392 mCurCell.setCover(delta, area);
393 ey1 += incr;
394 setCurCell(ex, ey1);
396 //renderScanLine(ey1, xFrom, PolyBaseSize - first, xFrom, fy2);
397 delta = fy2-PolyBaseSize+first;
398 mCurCell.addCover(delta, twoFx*delta);
399 return;
402 // ok, we have to render several scanlines
403 int p = (PolyBaseSize-fy1)*dx;
404 int first = PolyBaseSize;
406 if (dy < 0) {
407 p = fy1*dx;
408 first = 0;
409 incr = -1;
410 dy = -dy;
413 int delta = p/dy;
414 int mod = p%dy;
416 if (mod < 0) { --delta; mod += dy; }
418 int xFrom = x1+delta;
419 renderScanLine(ey1, x1, fy1, xFrom, first);
421 ey1 += incr;
422 setCurCell(xFrom>>PolyBaseShift, ey1);
424 if (ey1 != ey2) {
425 p = PolyBaseSize*dx;
426 int lift = p/dy;
427 int rem = p%dy;
429 if (rem < 0) { --lift; rem += dy; }
430 mod -= dy;
432 while (ey1 != ey2) {
433 delta = lift;
434 mod += rem;
435 if (mod >= 0) { mod -= dy; ++delta; }
437 immutable int xTo = xFrom+delta;
438 renderScanLine(ey1, xFrom, PolyBaseSize-first, xTo, first);
439 xFrom = xTo;
441 ey1 += incr;
442 setCurCell(xFrom>>PolyBaseShift, ey1);
446 renderScanLine(ey1, xFrom, PolyBaseSize-first, x2, fy2);
449 private:
450 static void qsortCells (Cell** start, uint num) {
452 static void swapCells (Cell** a, Cell** b) nothrow @trusted @nogc {
453 pragma(inline, true);
454 auto temp = *a;
455 *a = *b;
456 *b = temp;
459 static bool lessThan (in Cell** a, in Cell** b) pure nothrow @trusted @nogc {
460 pragma(inline, true);
461 version(none) {
462 return ((**a).packedCoord < (**b).packedCoord);
463 } else {
464 return (*a).isLessThan(**b);
469 // two macros
470 static enum swapCellsMixin(string a, string b) = `
471 { auto temp_ = *(`~a~`);
472 *(`~a~`) = *(`~b~`);
473 *(`~b~`) = temp_; }
476 static enum lessThanMixin(string a, string b) = `(*(`~a~`)).isLessThan(**(`~b~`))`;
478 Cell**[80] stack = void;
479 Cell*** top;
480 Cell** limit;
481 Cell** base;
483 limit = start+num;
484 base = start;
485 top = stack.ptr;
487 for (;;) {
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"));
496 auto i = base+1;
497 auto j = limit-1;
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"));
504 for (;;) {
505 do { ++i; } while (mixin(lessThanMixin!("i", "base")));
506 do { --j; } while (mixin(lessThanMixin!("base", "j")));
507 if (i > j) break;
508 mixin(swapCellsMixin!("i", "j"));
511 mixin(swapCellsMixin!("base", "j"));
513 // now, push the largest sub-array
514 if (j-base > limit-i) {
515 top[0] = base;
516 top[1] = j;
517 base = i;
518 } else {
519 top[0] = i;
520 top[1] = limit;
521 limit = j;
523 top += 2;
524 } else {
525 // the sub-array is small, perform insertion sort
526 auto j = base;
527 auto i = j+1;
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) {
535 top -= 2;
536 base = top[0];
537 limit = top[1];
538 } else {
539 break;
545 void sortCells () {
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;
556 Cell* cellPtr;
558 uint nb = mNumCells>>CellBlockShift;
560 while (nb--) {
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:
584 1. reset(minX, maxX)
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.
594 4. Rendering:
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
618 do {
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
636 do {
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.
649 struct ScanLine {
650 private:
651 int mMinX;
652 uint mMaxLen;
653 int mDX;
654 int mDY;
655 int mLastX = 0x7fff;
656 int mLastY = 0x7fff;
657 ubyte* mCovers;
658 ubyte** mStartPtrs;
659 ushort* mCounts;
660 uint mNumSpans;
661 ubyte** mCurStartPtr;
662 ushort* mCurCount;
664 public:
665 enum AAShift = 8;
667 static struct Iterator {
668 private:
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;
683 int next () {
684 pragma(inline, true);
685 ++mCurCount;
686 ++mCurStartPtr;
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);
697 ~this () {
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");
716 mMaxLen = maxLen;
718 mDX = dx;
719 mDY = dy;
720 mLastX = 0x7fff;
721 mLastY = 0x7fff;
722 mMinX = minX;
723 mCurCount = mCounts;
724 mCurStartPtr = mStartPtrs;
725 mNumSpans = 0;
728 void resetSpans () {
729 pragma(inline, true);
730 mLastX = 0x7fff;
731 mLastY = 0x7fff;
732 mCurCount = mCounts;
733 mCurStartPtr = mStartPtrs;
734 mNumSpans = 0;
737 void addSpan (int x, int y, uint num, uint cover) {
738 import core.stdc.string : memset;
739 x -= mMinX;
740 memset(mCovers+x, cover, num);
741 if (x == mLastX+1) {
742 (*mCurCount) += cast(ushort)num;
743 } else {
744 *++mCurCount = cast(ushort)num;
745 *++mCurStartPtr = mCovers+x;
746 ++mNumSpans;
748 mLastX = x+num-1;
749 mLastY = y;
752 void addCell (int x, int y, uint cover) {
753 x -= mMinX;
754 mCovers[x] = cast(ubyte)cover;
755 if (x == mLastX+1) {
756 ++(*mCurCount);
757 } else {
758 *++mCurCount = 1;
759 *++mCurStartPtr = mCovers+x;
760 ++mNumSpans;
762 mLastX = x;
763 mLastY = y;
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.
775 Usage:
776 auto ras = Rasterizer();
778 // Making polygon
779 // ras.moveTo(...);
780 // ras.lineTo(...);
781 // ...
783 // Rendering
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); }
798 do {
799 int x = span.next+baseX;
800 int pixelCount = span.pixelCount;
801 int leftSkip = void;
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].
820 enum : uint {
821 PolyBaseShift = 8U,
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.
842 3. reset()
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
849 won't draw anything.
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:
863 enum : uint {
864 AAShift = ScanLine.AAShift,
865 AANum = 1<<AAShift,
866 AAMask = AANum-1,
867 AA2Num = AANum*2,
868 AA2Mask = AA2Num-1,
871 private:
872 Outline mOutline;
873 ScanLine mScanline;
874 FillRule mFillingRule = FillRule.NonZero;
875 ubyte[256] mGamma = DefaultGamma[];
877 public:
878 bool useGradient;
879 uint gradC0;
880 uint gradC1;
881 float gradMidP = -1.0f;
882 uint gradMidC;
884 public:
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) {
932 cover &= AA2Mask;
933 if (cover > AANum) cover = AA2Num-cover;
935 if (cover > AAMask) cover = AAMask;
936 return cover;
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;
947 uint gC0, gC1, gCM;
948 if (grad) {
949 gC0 = gradC0;
950 gC1 = gradC1;
951 gCM = gradMidC;
952 gradY0 = minY;
953 gradY1 = maxY;
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);
957 } else {
958 if (gxIsTransparent(c)) return;
961 void flushScanline () {
962 if (mScanline.y < clipRect.pos.y || mScanline.y > clipRect.y1) return;
963 if (!grad) {
964 // no gradient
965 Renderer.render(mScanline, c, clipRect);
966 return;
968 // vertical gradient
969 uint clr = void;
970 immutable float gt = (cast(float)mScanline.y-gradY0)*gradTotal;
971 if (useMidP) {
972 if (gt < gmp) {
973 clr = gxInterpolateColor(gC0, gCM, gt/gmp);
974 } else {
975 clr = gxInterpolateColor(gCM, gC1, (gt-gmp)/(1.0f-gmp));
977 } else {
978 clr = gxInterpolateColor(gC0, gC1, gt);
980 Renderer.render(mScanline, clr, clipRect);
983 mScanline.reset(mOutline.minX, mOutline.maxX, dx, dy);
985 int cover = 0;
986 const(Cell)* curCell = *cells++;
988 for (;;) {
989 const(Cell)* startCell = curCell;
991 uint coord = curCell.getPackedCoord;
992 int x = curCell.x;
993 int y = curCell.y;
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;
1005 if (area) {
1006 int alpha = calculateAlpha((cover<<(PolyBaseShift+1))-area);
1007 if (alpha) {
1008 if (mScanline.isReady(y)) {
1009 flushScanline();
1010 mScanline.resetSpans();
1012 mScanline.addCell(x, y, mGamma.ptr[alpha]);
1014 ++x;
1017 if (!curCell) break;
1019 if (curCell.x > x) {
1020 int alpha = calculateAlpha(cover<<(PolyBaseShift+1));
1021 if (alpha) {
1022 if (mScanline.isReady(y)) {
1023 flushScanline();
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;
1039 int cover = 0;
1040 const(Cell)* curCell = *cells++;
1042 for (;;) {
1043 const(Cell)* startCell = curCell;
1045 uint coord = curCell.getPackedCoord;
1046 int x = curCell.x;
1047 int y = curCell.y;
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;
1060 if (area) {
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];
1065 ++x;
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];
1078 return 0;
1081 private:
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