1 /** 2 * Drawing functions. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 * Guillaume Piolat <contact@auburnsounds.com> 13 */ 14 15 module dplug.graphics.draw; 16 17 import std.algorithm : sort, min; 18 import std.math; 19 20 import dplug.graphics.view; 21 22 version(unittest) import dplug.graphics.image; 23 24 // Constraints could be simpler if this was fixed: 25 // https://d.puremagic.com/issues/show_bug.cgi?id=12386 26 27 /// Get the pixel color at the specified coordinates, 28 /// or fall back to the specified default value if 29 /// the coordinates are out of bounds. 30 COLOR safeGet(V, COLOR)(auto ref V v, int x, int y, COLOR def) 31 if (isView!V && is(COLOR : ViewColor!V)) 32 { 33 if (x>=0 && y>=0 && x<v.w && y<v.h) 34 return v[x, y]; 35 else 36 return def; 37 } 38 39 unittest 40 { 41 auto v = onePixel(7); 42 assert(v.safeGet(0, 0, 0) == 7); 43 assert(v.safeGet(0, 1, 0) == 0); 44 } 45 46 /// Set the pixel color at the specified coordinates 47 /// if the coordinates are not out of bounds. 48 void safePut(V, COLOR)(auto ref V v, int x, int y, COLOR value) 49 if (isWritableView!V && is(COLOR : ViewColor!V)) 50 { 51 if (x>=0 && y>=0 && x<v.w && y<v.h) 52 v[x, y] = value; 53 } 54 55 56 /// Forwards to safePut or opIndex, depending on the 57 /// CHECKED parameter. Allows propagation of a 58 /// CHECKED parameter from other callers. 59 void putPixel(bool CHECKED, V, COLOR)(auto ref V v, int x, int y, COLOR value) 60 if (isWritableView!V && is(COLOR : ViewColor!V)) 61 { 62 static if (CHECKED) 63 v.safePut(x, y, value); 64 else 65 v[x, y] = value; 66 } 67 68 69 /// Gets a pixel's address from a direct view. 70 ViewColor!V* pixelPtr(V)(auto ref V v, int x, int y) 71 if (isDirectView!V) 72 { 73 return &v.scanline(y)[x]; 74 } 75 76 77 78 /// Fills a writable view with a solid color. 79 void fill(V, COLOR)(auto ref V v, COLOR c) 80 if (isWritableView!V 81 && is(COLOR : ViewColor!V)) 82 { 83 foreach (y; 0..v.h) 84 { 85 static if (isDirectView!V) 86 v.scanline(y)[] = c; 87 else 88 foreach (x; 0..v.w) 89 v[x, y] = c; 90 } 91 } 92 93 // *************************************************************************** 94 95 enum CheckHLine = 96 q{ 97 static if (CHECKED) 98 { 99 if (x1 >= v.w || x2 <= 0 || y < 0 || y >= v.h || x1 >= x2) return; 100 if (x1 < 0) x1 = 0; 101 if (x2 >= v.w) x2 = v.w; 102 } 103 assert(x1 <= x2); 104 }; 105 106 enum CheckVLine = 107 q{ 108 static if (CHECKED) 109 { 110 if (x < 0 || x >= v.w || y1 >= v.h || y2 <= 0 || y1 >= y2) return; 111 if (y1 < 0) y1 = 0; 112 if (y2 >= v.h) y2 = v.h; 113 } 114 assert(y1 <= y2); 115 }; 116 117 void hline(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int x2, int y, COLOR c) 118 if (isWritableView!V && is(COLOR : ViewColor!V)) 119 { 120 mixin(CheckHLine); 121 v.scanline(y)[x1..x2] = c; 122 } 123 124 void vline(bool CHECKED=true, V, COLOR)(auto ref V v, int x, int y1, int y2, COLOR c) 125 { 126 mixin(CheckVLine); 127 foreach (y; y1..y2) // FUTURE: optimize 128 v[x, y] = c; 129 } 130 131 /+ 132 void line(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR c) 133 if (isWritableView!V && is(COLOR : ViewColor!V)) 134 { 135 mixin FixMath; 136 137 enum DrawLine = q{ 138 // Axis-independent part. Mixin context: 139 // a0 .. a1 - longer side 140 // b0 .. b1 - shorter side 141 // DrawPixel - mixin to draw a pixel at coordinates (a, b) 142 143 if (a0 == a1) 144 return; 145 146 if (a0 > a1) 147 { 148 swap(a0, a1); 149 swap(b0, b1); 150 } 151 152 // Use fixed-point for b position and offset per 1 pixel along "a" axis 153 assert(b0 < (1L<<coordinateBits) && b1 < (1L<<coordinateBits)); 154 SignedBitsType!(coordinateBits*2) bPos = b0 << coordinateBits; 155 SignedBitsType!(coordinateBits*2) bOff = ((b1-b0) << coordinateBits) / (a1-a0); 156 157 foreach (a; a0..a1+1) 158 { 159 int b = (bPos += bOff) >> coordinateBits; 160 mixin(DrawPixel); 161 } 162 }; 163 164 if (abs(x2-x1) > abs(y2-y1)) 165 { 166 alias x1 a0; 167 alias x2 a1; 168 alias y1 b0; 169 alias y2 b1; 170 enum DrawPixel = q{ v.putPixel!CHECKED(a, b, c); }; 171 mixin(DrawLine); 172 } 173 else 174 { 175 alias y1 a0; 176 alias y2 a1; 177 alias x1 b0; 178 alias x2 b1; 179 enum DrawPixel = q{ v.putPixel!CHECKED(b, a, c); }; 180 mixin(DrawLine); 181 } 182 } 183 +/ 184 185 /// Draws a rectangle with a solid line. 186 /// The coordinates represent bounds (open on the right) for the outside of the rectangle. 187 void rect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR c) 188 if (isWritableView!V && is(COLOR : ViewColor!V)) 189 { 190 sort2(x1, x2); 191 sort2(y1, y2); 192 v.hline!CHECKED(x1, x2, y1 , c); 193 v.hline!CHECKED(x1, x2, y2-1, c); 194 v.vline!CHECKED(x1 , y1, y2, c); 195 v.vline!CHECKED(x2-1, y1, y2, c); 196 } 197 198 void fillRect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR b) // [) 199 if (isWritableView!V && is(COLOR : ViewColor!V)) 200 { 201 sort2(x1, x2); 202 sort2(y1, y2); 203 static if (CHECKED) 204 { 205 if (x1 >= v.w || y1 >= v.h || x2 <= 0 || y2 <= 0 || x1==x2 || y1==y2) return; 206 if (x1 < 0) x1 = 0; 207 if (y1 < 0) y1 = 0; 208 if (x2 >= v.w) x2 = v.w; 209 if (y2 >= v.h) y2 = v.h; 210 } 211 foreach (y; y1..y2) 212 v.scanline(y)[x1..x2] = b; 213 } 214 215 void fillRect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR c, COLOR b) // [) 216 if (isWritableView!V && is(COLOR : ViewColor!V)) 217 { 218 v.rect!CHECKED(x1, y1, x2, y2, c); 219 if (x2-x1>2 && y2-y1>2) 220 v.fillRect!CHECKED(x1+1, y1+1, x2-1, y2-1, b); 221 } 222 223 /// Unchecked! Make sure area is bounded. 224 void uncheckedFloodFill(V, COLOR)(auto ref V v, int x, int y, COLOR c) 225 if (isDirectView!V && is(COLOR : ViewColor!V)) 226 { 227 v.floodFillPtr(&v[x, y], c, v[x, y]); 228 } 229 230 private void floodFillPtr(V, COLOR)(auto ref V v, COLOR* pp, COLOR c, COLOR f) 231 if (isDirectView!V && is(COLOR : ViewColor!V)) 232 { 233 COLOR* p0 = pp; while (*p0==f) p0--; p0++; 234 COLOR* p1 = pp; while (*p1==f) p1++; p1--; 235 auto stride = v.scanline(1).ptr-v.scanline(0).ptr; 236 for (auto p=p0; p<=p1; p++) 237 *p = c; 238 p0 -= stride; p1 -= stride; 239 for (auto p=p0; p<=p1; p++) 240 if (*p == f) 241 v.floodFillPtr(p, c, f); 242 p0 += stride*2; p1 += stride*2; 243 for (auto p=p0; p<=p1; p++) 244 if (*p == f) 245 v.floodFillPtr(p, c, f); 246 } 247 248 void fillCircle(V, COLOR)(auto ref V v, int x, int y, int r, COLOR c) 249 if (isWritableView!V && is(COLOR : ViewColor!V)) 250 { 251 int x0 = x>r?x-r:0; 252 int y0 = y>r?y-r:0; 253 int x1 = min(x+r, v.w-1); 254 int y1 = min(y+r, v.h-1); 255 int rs = sqr(r); 256 // FUTURE: optimize 257 foreach (py; y0..y1+1) 258 foreach (px; x0..x1+1) 259 if (sqr(x-px) + sqr(y-py) < rs) 260 v[px, py] = c; 261 } 262 263 void fillSector(V, COLOR)(auto ref V v, int x, int y, int r0, int r1, real a0, real a1, COLOR c) 264 if (isWritableView!V && is(COLOR : ViewColor!V)) 265 { 266 int x0 = x>r1?x-r1:0; 267 int y0 = y>r1?y-r1:0; 268 int x1 = min(x+r1, v.w-1); 269 int y1 = min(y+r1, v.h-1); 270 int r0s = sqr(r0); 271 int r1s = sqr(r1); 272 if (a0 > a1) 273 a1 += (2 * PI); 274 foreach (py; y0..y1+1) 275 foreach (px; x0..x1+1) 276 { 277 int dx = px-x; 278 int dy = py-y; 279 int rs = sqr(dx) + sqr(dy); 280 if (r0s <= rs && rs < r1s) 281 { 282 real a = atan2(cast(real)dy, cast(real)dx); 283 if (a0 <= a && a <= a1) 284 v[px, py] = c; 285 else 286 { 287 a += 2 * PI; 288 if (a0 <= a && a <= a1) 289 v[px, py] = c; 290 } 291 } 292 } 293 } 294 295 struct Coord { int x, y; string toString() { import std.string; return format("%s", [this.tupleof]); } } 296 297 298 /+ 299 void fillPoly(V, COLOR)(auto ref V v, Coord[] coords, COLOR f) 300 if (isWritableView!V && is(COLOR : ViewColor!V)) 301 { 302 int minY, maxY; 303 minY = maxY = coords[0].y; 304 foreach (c; coords[1..$]) 305 minY = min(minY, c.y), 306 maxY = max(maxY, c.y); 307 308 foreach (y; minY..maxY+1) 309 { 310 int[] intersections; 311 for (uint i=0; i<coords.length; i++) 312 { 313 auto c0=coords[i], c1=coords[i==$-1?0:i+1]; 314 if (y==c0.y) 315 { 316 assert(y == coords[i%$].y); 317 int pi = i-1; int py; 318 while ((py=coords[(pi+$)%$].y)==y) 319 pi--; 320 int ni = i+1; int ny; 321 while ((ny=coords[ni%$].y)==y) 322 ni++; 323 if (ni > coords.length) 324 continue; 325 if ((py>y) == (y>ny)) 326 intersections ~= coords[i%$].x; 327 i = ni-1; 328 } 329 else 330 if (c0.y<y && y<c1.y) 331 intersections ~= itpl(c0.x, c1.x, y, c0.y, c1.y); 332 else 333 if (c1.y<y && y<c0.y) 334 intersections ~= itpl(c1.x, c0.x, y, c1.y, c0.y); 335 } 336 337 assert(intersections.length % 2==0); 338 intersections.sort(); 339 for (uint i=0; i<intersections.length; i+=2) 340 v.hline!true(intersections[i], intersections[i+1], y, f); 341 } 342 } 343 +/ 344 345 // No caps 346 void thickLine(V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, int r, COLOR c) 347 if (isWritableView!V && is(COLOR : ViewColor!V)) 348 { 349 int dx = x2-x1; 350 int dy = y2-y1; 351 int d = cast(int)sqrt(cast(float)(sqr(dx)+sqr(dy))); 352 if (d==0) return; 353 354 int nx = dx*r/d; 355 int ny = dy*r/d; 356 357 fillPoly([ 358 Coord(x1-ny, y1+nx), 359 Coord(x1+ny, y1-nx), 360 Coord(x2+ny, y2-nx), 361 Coord(x2-ny, y2+nx), 362 ], c); 363 } 364 365 // No caps 366 void thickLinePoly(V, COLOR)(auto ref V v, Coord[] coords, int r, COLOR c) 367 if (isWritableView!V && is(COLOR : ViewColor!V)) 368 { 369 foreach (i; 0..coords.length) 370 thickLine(coords[i].tupleof, coords[(i+1)%$].tupleof, r, c); 371 } 372 373 // ************************************************************************************************************************************ 374 375 mixin template FixMath(ubyte coordinateBitsParam = 16) 376 { 377 enum coordinateBits = coordinateBitsParam; 378 379 static assert(COLOR.homogenous, "Asymmetric color types not supported, fix me!"); 380 /// Fixed-point type, big enough to hold a coordinate, with fractionary precision corresponding to channel precision. 381 alias fix = SignedBitsType!(COLOR.channelBits + coordinateBits); 382 /// Type to hold temporary values for multiplication and division 383 alias fix2 = SignedBitsType!(COLOR.channelBits*2 + coordinateBits); 384 385 static assert(COLOR.channelBits < 32, "Shift operators are broken for shifts over 32 bits, fix me!"); 386 fix tofix(T:int )(T x) { return cast(fix) (x<<COLOR.channelBits); } 387 fix tofix(T:float)(T x) { return cast(fix) (x*(1<<COLOR.channelBits)); } 388 T fixto(T:int)(fix x) { return cast(T)(x>>COLOR.channelBits); } 389 390 fix fixsqr(fix x) { return cast(fix)((cast(fix2)x*x) >> COLOR.channelBits); } 391 fix fixmul(fix x, fix y) { return cast(fix)((cast(fix2)x*y) >> COLOR.channelBits); } 392 fix fixdiv(fix x, fix y) { return cast(fix)((cast(fix2)x << COLOR.channelBits)/y); } 393 394 static assert(COLOR.ChannelType.sizeof*8 == COLOR.channelBits, "COLORs with ChannelType not corresponding to native type not currently supported, fix me!"); 395 /// Type only large enough to hold a fractionary part of a "fix" (i.e. color channel precision). Used for alpha values, etc. 396 alias COLOR.ChannelType frac; 397 /// Type to hold temporary values for multiplication and division 398 alias UnsignedBitsType!(COLOR.channelBits*2) frac2; 399 400 frac tofrac(T:float)(T x) { return cast(frac) (x*(1<<COLOR.channelBits)); } 401 frac fixfpart(fix x) { return cast(frac)x; } 402 frac fracsqr(frac x ) { return cast(frac)((cast(frac2)x*x) >> COLOR.channelBits); } 403 frac fracmul(frac x, frac y) { return cast(frac)((cast(frac2)x*y) >> COLOR.channelBits); } 404 405 frac tofracBounded(T:float)(T x) { return cast(frac) bound(tofix(x), 0, frac.max); } 406 } 407 408 // ************************************************************************************************************************************ 409 410 void whiteNoise(V)(V v) 411 if (isWritableView!V) 412 { 413 import std.random; 414 alias COLOR = ViewColor!V; 415 416 for (int y=0;y<v.h/2;y++) 417 for (int x=0;x<v.w/2;x++) 418 v[x*2, y*2] = COLOR.monochrome(uniform!(COLOR.ChannelType)()); 419 420 // interpolate 421 enum AVERAGE = q{(a+b)/2}; 422 423 for (int y=0;y<v.h/2;y++) 424 for (int x=0;x<v.w/2-1;x++) 425 v[x*2+1, y*2 ] = COLOR.op!AVERAGE(v[x*2 , y*2], v[x*2+2, y*2 ]); 426 for (int y=0;y<v.h/2-1;y++) 427 for (int x=0;x<v.w/2;x++) 428 v[x*2 , y*2+1] = COLOR.op!AVERAGE(v[x*2 , y*2], v[x*2 , y*2+2]); 429 for (int y=0;y<v.h/2-1;y++) 430 for (int x=0;x<v.w/2-1;x++) 431 v[x*2+1, y*2+1] = COLOR.op!AVERAGE(v[x*2+1, y*2], v[x*2+2, y*2+2]); 432 } 433 434 private template softRoundShape(bool RING) 435 { 436 void softRoundShape(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float r2, COLOR color) 437 if (isWritableView!V && is(COLOR : ViewColor!V)) 438 { 439 mixin FixMath; 440 441 assert(r0 <= r1); 442 assert(r1 <= r2); 443 assert(r2 < 256); // precision constraint - see SqrType 444 //int ix = cast(int)x; 445 //int iy = cast(int)y; 446 //int ir1 = cast(int)sqr(r1-1); 447 //int ir2 = cast(int)sqr(r2+1); 448 int x1 = cast(int)(x-r2-1); if (x1<0) x1=0; 449 int y1 = cast(int)(y-r2-1); if (y1<0) y1=0; 450 int x2 = cast(int)(x+r2+1); if (x2>v.w) x2 = v.w; 451 int y2 = cast(int)(y+r2+1); if (y2>v.h) y2 = v.h; 452 453 static if (RING) 454 auto r0s = r0*r0; 455 auto r1s = r1*r1; 456 auto r2s = r2*r2; 457 //float rds = r2s - r1s; 458 459 fix fx = tofix(x); 460 fix fy = tofix(y); 461 462 static if (RING) 463 fix fr0s = tofix(r0s); 464 fix fr1s = tofix(r1s); 465 fix fr2s = tofix(r2s); 466 467 static if (RING) 468 fix fr10 = fr1s - fr0s; 469 fix fr21 = fr2s - fr1s; 470 471 for (int cy=y1;cy<y2;cy++) 472 { 473 auto row = v.scanline(cy); 474 for (int cx=x1;cx<x2;cx++) 475 { 476 alias SignedBitsType!(2*(8 + COLOR.channelBits)) SqrType; // fit the square of radius expressed as fixed-point 477 fix frs = cast(fix)((sqr(cast(SqrType)fx-tofix(cx)) + sqr(cast(SqrType)fy-tofix(cy))) >> COLOR.channelBits); // shift-right only once instead of once-per-sqr 478 479 //static frac alphafunc(frac x) { return fracsqr(x); } 480 static frac alphafunc(frac x) { return x; } 481 482 static if (RING) 483 { 484 if (frs<fr0s) 485 {} 486 else 487 if (frs<fr2s) 488 { 489 frac alpha; 490 if (frs<fr1s) 491 alpha = alphafunc(cast(frac)fixdiv(frs-fr0s, fr10)); 492 else 493 alpha = ~alphafunc(cast(frac)fixdiv(frs-fr1s, fr21)); 494 row[cx] = COLOR.op!q{.blend(a, b, c)}(color, row[cx], alpha); 495 } 496 } 497 else 498 { 499 if (frs<fr1s) 500 row[cx] = color; 501 else 502 if (frs<fr2s) 503 { 504 frac alpha = ~alphafunc(cast(frac)fixdiv(frs-fr1s, fr21)); 505 row[cx] = COLOR.op!q{.blend(a, b, c)}(color, row[cx], alpha); 506 } 507 } 508 } 509 } 510 } 511 } 512 513 void softRing(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float r2, COLOR color) 514 if (isWritableView!V && is(COLOR : ViewColor!V)) 515 { 516 v.softRoundShape!true(x, y, r0, r1, r2, color); 517 } 518 519 void softCircle(V, COLOR)(auto ref V v, float x, float y, float r1, float r2, COLOR color) 520 if (isWritableView!V && is(COLOR : ViewColor!V)) 521 { 522 v.softRoundShape!false(x, y, 0, r1, r2, color); 523 } 524 525 template aaPutPixel(bool CHECKED=true, bool USE_ALPHA=true) 526 { 527 void aaPutPixel(F:float, V, COLOR, frac)(auto ref V v, F x, F y, COLOR color, frac alpha) 528 if (isWritableView!V && is(COLOR : ViewColor!V)) 529 { 530 mixin FixMath; 531 532 void plot(bool CHECKED2)(int x, int y, frac f) 533 { 534 static if (CHECKED2) 535 if (x<0 || x>=v.w || y<0 || y>=v.h) 536 return; 537 538 COLOR* p = v.pixelPtr(x, y); 539 static if (USE_ALPHA) f = fracmul(f, cast(frac)alpha); 540 *p = COLOR.op!q{.blend(a, b, c)}(color, *p, f); 541 } 542 543 fix fx = tofix(x); 544 fix fy = tofix(y); 545 int ix = fixto!int(fx); 546 int iy = fixto!int(fy); 547 static if (CHECKED) 548 if (ix>=0 && iy>=0 && ix+1<v.w && iy+1<v.h) 549 { 550 plot!false(ix , iy , fracmul(~fixfpart(fx), ~fixfpart(fy))); 551 plot!false(ix , iy+1, fracmul(~fixfpart(fx), fixfpart(fy))); 552 plot!false(ix+1, iy , fracmul( fixfpart(fx), ~fixfpart(fy))); 553 plot!false(ix+1, iy+1, fracmul( fixfpart(fx), fixfpart(fy))); 554 return; 555 } 556 plot!CHECKED(ix , iy , fracmul(~fixfpart(fx), ~fixfpart(fy))); 557 plot!CHECKED(ix , iy+1, fracmul(~fixfpart(fx), fixfpart(fy))); 558 plot!CHECKED(ix+1, iy , fracmul( fixfpart(fx), ~fixfpart(fy))); 559 plot!CHECKED(ix+1, iy+1, fracmul( fixfpart(fx), fixfpart(fy))); 560 } 561 } 562 563 void aaPutPixel(bool CHECKED=true, F:float, V, COLOR)(auto ref V v, F x, F y, COLOR color) 564 if (isWritableView!V && is(COLOR : ViewColor!V)) 565 { 566 //aaPutPixel!(false, F)(x, y, color, 0); // doesn't work, wtf 567 alias aaPutPixel!(CHECKED, false) f; 568 f(v, x, y, color, 0); 569 } 570 571 void hline(bool CHECKED=true, V, COLOR, frac)(auto ref V v, int x1, int x2, int y, COLOR color, frac alpha) 572 if (isWritableView!V && is(COLOR : ViewColor!V)) 573 { 574 mixin(CheckHLine); 575 576 if (alpha==0) 577 return; 578 else 579 if (alpha==frac.max) 580 v.scanline(y)[x1..x2] = color; 581 else 582 foreach (ref p; v.scanline(y)[x1..x2]) 583 p = COLOR.op!q{.blend(a, b, c)}(color, p, alpha); 584 } 585 586 void vline(bool CHECKED=true, V, COLOR, frac)(auto ref V v, int x, int y1, int y2, COLOR color, frac alpha) 587 if (isWritableView!V && is(COLOR : ViewColor!V)) 588 { 589 mixin(CheckVLine); 590 591 if (alpha==0) 592 return; 593 else 594 if (alpha==frac.max) 595 foreach (y; y1..y2) 596 v[x, y] = color; 597 else 598 foreach (y; y1..y2) 599 { 600 auto p = v.pixelPtr(x, y); 601 *p = COLOR.op!q{.blend(a, b, c)}(color, *p, alpha); 602 } 603 } 604 605 void aaFillRect(bool CHECKED=true, F:float, V, COLOR)(auto ref V v, F x1, F y1, F x2, F y2, COLOR color) 606 if (isWritableView!V && is(COLOR : ViewColor!V)) 607 { 608 mixin FixMath; 609 610 sort2(x1, x2); 611 sort2(y1, y2); 612 fix x1f = tofix(x1); int x1i = fixto!int(x1f); 613 fix y1f = tofix(y1); int y1i = fixto!int(y1f); 614 fix x2f = tofix(x2); int x2i = fixto!int(x2f); 615 fix y2f = tofix(y2); int y2i = fixto!int(y2f); 616 617 v.vline!CHECKED(x1i, y1i+1, y2i, color, ~fixfpart(x1f)); 618 v.vline!CHECKED(x2i, y1i+1, y2i, color, fixfpart(x2f)); 619 v.hline!CHECKED(x1i+1, x2i, y1i, color, ~fixfpart(y1f)); 620 v.hline!CHECKED(x1i+1, x2i, y2i, color, fixfpart(y2f)); 621 v.aaPutPixel!CHECKED(x1i, y1i, color, fracmul(~fixfpart(x1f), ~fixfpart(y1f))); 622 v.aaPutPixel!CHECKED(x1i, y2i, color, fracmul(~fixfpart(x1f), fixfpart(y2f))); 623 v.aaPutPixel!CHECKED(x2i, y1i, color, fracmul( fixfpart(x2f), ~fixfpart(y1f))); 624 v.aaPutPixel!CHECKED(x2i, y2i, color, fracmul( fixfpart(x2f), fixfpart(y2f))); 625 626 v.fillRect!CHECKED(x1i+1, y1i+1, x2i, y2i, color); 627 } 628 629 void aaLine(bool CHECKED=true, V, COLOR)(auto ref V v, float x1, float y1, float x2, float y2, COLOR color) 630 if (isWritableView!V && is(COLOR : ViewColor!V)) 631 { 632 // Simplistic straight-forward implementation. FUTURE: optimize 633 if (abs(x1-x2) > abs(y1-y2)) 634 for (auto x=x1; sign(x1-x2)!=sign(x2-x); x += sign(x2-x1)) 635 v.aaPutPixel!CHECKED(x, itpl(y1, y2, x, x1, x2), color); 636 else 637 for (auto y=y1; sign(y1-y2)!=sign(y2-y); y += sign(y2-y1)) 638 v.aaPutPixel!CHECKED(itpl(x1, x2, y, y1, y2), y, color); 639 } 640 641 void aaLine(bool CHECKED=true, V, COLOR, frac)(auto ref V v, float x1, float y1, float x2, float y2, COLOR color, frac alpha) 642 if (isWritableView!V && is(COLOR : ViewColor!V)) 643 { 644 // ditto 645 if (abs(x1-x2) > abs(y1-y2)) 646 for (auto x=x1; sign(x1-x2)!=sign(x2-x); x += sign(x2-x1)) 647 v.aaPutPixel!CHECKED(x, itpl(y1, y2, x, x1, x2), color, alpha); 648 else 649 for (auto y=y1; sign(y1-y2)!=sign(y2-y); y += sign(y2-y1)) 650 v.aaPutPixel!CHECKED(itpl(x1, x2, y, y1, y2), y, color, alpha); 651 } 652 653 unittest 654 { 655 // Test instantiation 656 import dplug.graphics.color; 657 ImageRef!RGB i; 658 i.w = 100; 659 i.h = 100; 660 i.pitch = 100; 661 i.pixels = (new RGB[100 * 100]).ptr; 662 663 auto c = RGB(1, 2, 3); 664 i.whiteNoise(); 665 i.aaLine(10, 10, 20, 20, c); 666 i.aaLine(10f, 10f, 20f, 20f, c, 100); 667 i.rect(10, 10, 20, 20, c); 668 i.fillRect(10, 10, 20, 20, c); 669 i.aaFillRect(10, 10, 20, 20, c); 670 i.vline(10, 10, 20, c); 671 i.vline(10, 10, 20, c); 672 // i.line(10, 10, 20, 20, c); 673 i.fillCircle(10, 10, 10, c); 674 i.fillSector(10, 10, 10, 10, 0.0, (2 * PI), c); 675 i.softRing(50, 50, 10, 15, 20, c); 676 i.softCircle(50, 50, 10, 15, c); 677 // i.fillPoly([Coord(10, 10), Coord(10, 20), Coord(20, 20)], c); 678 i.uncheckedFloodFill(15, 15, RGB(4, 5, 6)); 679 } 680