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