1 /** 2 Drawing functions. Port of ae.utils.graphics. 3 In many many cases, you should use dplug:canvas instead. 4 5 License: 6 This Source Code Form is subject to the terms of 7 the Mozilla Public License, v. 2.0. If a copy of 8 the MPL was not distributed with this file, You 9 can obtain one at http://mozilla.org/MPL/2.0/. 10 11 Copyright: Vladimir Panteleev <vladimir@thecybershadow.net> 12 Copyright: Guillaume Piolat <contact@auburnsounds.com> 13 */ 14 15 module dplug.graphics.draw; 16 17 import core.stdc.math: floorf, ceilf; 18 19 import std.math; 20 21 import dplug.math.box; 22 23 import dplug.core.nogc; 24 import dplug.core.vec; 25 import dplug.graphics.image; 26 27 28 nothrow: 29 @nogc: 30 31 // Constraints could be simpler if this was fixed: 32 // https://d.puremagic.com/issues/show_bug.cgi?id=12386 33 34 /// Get the pixel color at the specified coordinates, 35 /// or fall back to the specified default value if 36 /// the coordinates are out of bounds. 37 COLOR safeGet(V, COLOR)(auto ref V v, int x, int y, COLOR def) 38 if (isView!V && is(COLOR : ViewColor!V)) 39 { 40 if (x>=0 && y>=0 && x<v.w && y<v.h) 41 return v[x, y]; 42 else 43 return def; 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 void fillAll(V, COLOR)(auto ref V v, COLOR c) 77 if (isWritableView!V 78 && is(COLOR : ViewColor!V)) 79 { 80 foreach (y; 0..v.h) 81 { 82 static if (isDirectView!V) 83 v.scanline(y)[] = c; 84 else 85 foreach (x; 0..v.w) 86 v[x, y] = c; 87 } 88 } 89 90 // *************************************************************************** 91 92 enum CheckHLine = 93 q{ 94 static if (CHECKED) 95 { 96 if (x1 >= v.w || x2 <= 0 || y < 0 || y >= v.h || x1 >= x2) return; 97 if (x1 < 0) x1 = 0; 98 if (x2 >= v.w) x2 = v.w; 99 } 100 assert(x1 <= x2); 101 }; 102 103 enum CheckVLine = 104 q{ 105 static if (CHECKED) 106 { 107 if (x < 0 || x >= v.w || y1 >= v.h || y2 <= 0 || y1 >= y2) return; 108 if (y1 < 0) y1 = 0; 109 if (y2 >= v.h) y2 = v.h; 110 } 111 assert(y1 <= y2); 112 }; 113 114 void hline(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int x2, int y, COLOR c) 115 if (isWritableView!V && is(COLOR : ViewColor!V)) 116 { 117 mixin(CheckHLine); 118 v.scanline(y)[x1..x2] = c; 119 } 120 121 void vline(bool CHECKED=true, V, COLOR)(auto ref V v, int x, int y1, int y2, COLOR c) 122 { 123 mixin(CheckVLine); 124 foreach (y; y1..y2) // FUTURE: optimize 125 v[x, y] = c; 126 } 127 128 /// Draws a rectangle with a solid line. 129 /// The coordinates represent bounds (open on the right) for the outside of the rectangle. 130 void rect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR c) 131 if (isWritableView!V && is(COLOR : ViewColor!V)) 132 { 133 sort2(x1, x2); 134 sort2(y1, y2); 135 v.hline!CHECKED(x1, x2, y1 , c); 136 v.hline!CHECKED(x1, x2, y2-1, c); 137 v.vline!CHECKED(x1 , y1, y2, c); 138 v.vline!CHECKED(x2-1, y1, y2, c); 139 } 140 141 void fillRect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR b) // [) 142 if (isWritableView!V && is(COLOR : ViewColor!V)) 143 { 144 sort2(x1, x2); 145 sort2(y1, y2); 146 static if (CHECKED) 147 { 148 if (x1 >= v.w || y1 >= v.h || x2 <= 0 || y2 <= 0 || x1==x2 || y1==y2) return; 149 if (x1 < 0) x1 = 0; 150 if (y1 < 0) y1 = 0; 151 if (x2 >= v.w) x2 = v.w; 152 if (y2 >= v.h) y2 = v.h; 153 } 154 foreach (y; y1..y2) 155 v.scanline(y)[x1..x2] = b; 156 } 157 158 void fillRect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR c, COLOR b) // [) 159 if (isWritableView!V && is(COLOR : ViewColor!V)) 160 { 161 v.rect!CHECKED(x1, y1, x2, y2, c); 162 if (x2-x1>2 && y2-y1>2) 163 v.fillRect!CHECKED(x1+1, y1+1, x2-1, y2-1, b); 164 } 165 166 void fillCircle(V, COLOR)(auto ref V v, int x, int y, int r, COLOR c) 167 if (isWritableView!V && is(COLOR : ViewColor!V)) 168 { 169 int x0 = x > r ? x-r : 0; 170 int y0 = y > r ? y-r : 0; 171 int x1 = x+r; 172 if (x1 > v.w-1) 173 x1 = v.w-1; 174 int y1 = y+r; 175 if (y1 > v.h-1) 176 y1 = v.h-1; 177 int rs = sqr(r); 178 // FUTURE: optimize 179 foreach (py; y0..y1+1) 180 foreach (px; x0..x1+1) 181 if (sqr(x-px) + sqr(y-py) < rs) 182 v[px, py] = c; 183 } 184 185 void fillSector(V, COLOR)(auto ref V v, int x, int y, int r0, int r1, real a0, real a1, COLOR c) 186 if (isWritableView!V && is(COLOR : ViewColor!V)) 187 { 188 int x0 = x>r1?x-r1:0; 189 int y0 = y>r1?y-r1:0; 190 int x1 = x+r1; 191 if (x1 > v.w-1) 192 x1 = v.w-1; 193 int y1 = y+r1; 194 if (y1 > v.h-1) 195 y1 = v.h-1; 196 197 198 int r0s = sqr(r0); 199 int r1s = sqr(r1); 200 if (a0 > a1) 201 a1 += (2 * PI); 202 foreach (py; y0..y1+1) 203 foreach (px; x0..x1+1) 204 { 205 int dx = px-x; 206 int dy = py-y; 207 int rs = sqr(dx) + sqr(dy); 208 if (r0s <= rs && rs < r1s) 209 { 210 real a = atan2(cast(real)dy, cast(real)dx); 211 if (a0 <= a && a <= a1) 212 v[px, py] = c; 213 else 214 { 215 a += 2 * PI; 216 if (a0 <= a && a <= a1) 217 v[px, py] = c; 218 } 219 } 220 } 221 } 222 223 mixin template FixMath(ubyte coordinateBitsParam = 16) 224 { 225 enum coordinateBits = coordinateBitsParam; 226 227 static assert(COLOR.homogenous, "Asymmetric color types not supported, fix me!"); 228 /// Fixed-point type, big enough to hold a coordinate, with fractionary precision corresponding to channel precision. 229 alias fix = SignedBitsType!(COLOR.channelBits + coordinateBits); 230 /// Type to hold temporary values for multiplication and division 231 alias fix2 = SignedBitsType!(COLOR.channelBits*2 + coordinateBits); 232 233 static assert(COLOR.channelBits < 32, "Shift operators are broken for shifts over 32 bits, fix me!"); 234 fix tofix(T:int )(T x) { return cast(fix) (x<<COLOR.channelBits); } 235 fix tofix(T:float)(T x) { return cast(fix) (x*(1<<COLOR.channelBits)); } 236 T fixto(T:int)(fix x) { return cast(T)(x>>COLOR.channelBits); } 237 238 fix fixsqr(fix x) { return cast(fix)((cast(fix2)x*x) >> COLOR.channelBits); } 239 fix fixmul(fix x, fix y) { return cast(fix)((cast(fix2)x*y) >> COLOR.channelBits); } 240 fix fixdiv(fix x, fix y) { return cast(fix)((cast(fix2)x << COLOR.channelBits)/y); } 241 242 static assert(COLOR.ChannelType.sizeof*8 == COLOR.channelBits, "COLORs with ChannelType not corresponding to native type not currently supported, fix me!"); 243 /// Type only large enough to hold a fractionary part of a "fix" (i.e. color channel precision). Used for alpha values, etc. 244 alias COLOR.ChannelType frac; 245 /// Type to hold temporary values for multiplication and division 246 alias UnsignedBitsType!(COLOR.channelBits*2) frac2; 247 248 frac tofrac(T:float)(T x) { return cast(frac) (x*(1<<COLOR.channelBits)); } 249 frac fixfpart(fix x) { return cast(frac)x; } 250 frac fracsqr(frac x ) { return cast(frac)((cast(frac2)x*x) >> COLOR.channelBits); } 251 frac fracmul(frac x, frac y) { return cast(frac)((cast(frac2)x*y) >> COLOR.channelBits); } 252 253 frac tofracBounded(T:float)(T x) { return cast(frac) bound(tofix(x), 0, frac.max); } 254 } 255 256 // ************************************************************************************************************************************ 257 258 private template softRoundShape(bool RING) 259 { 260 void softRoundShape(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float r2, COLOR color) 261 if (isWritableView!V && is(COLOR : ViewColor!V)) 262 { 263 mixin FixMath; 264 265 assert(r0 <= r1); 266 assert(r1 <= r2); 267 assert(r2 < 256); // precision constraint - see SqrType 268 //int ix = cast(int)x; 269 //int iy = cast(int)y; 270 //int ir1 = cast(int)sqr(r1-1); 271 //int ir2 = cast(int)sqr(r2+1); 272 int x1 = cast(int)(x-r2-1); if (x1<0) x1=0; 273 int y1 = cast(int)(y-r2-1); if (y1<0) y1=0; 274 int x2 = cast(int)(x+r2+1); if (x2>v.w) x2 = v.w; 275 int y2 = cast(int)(y+r2+1); if (y2>v.h) y2 = v.h; 276 277 static if (RING) 278 auto r0s = r0*r0; 279 auto r1s = r1*r1; 280 auto r2s = r2*r2; 281 //float rds = r2s - r1s; 282 283 fix fx = tofix(x); 284 fix fy = tofix(y); 285 286 static if (RING) 287 fix fr0s = tofix(r0s); 288 fix fr1s = tofix(r1s); 289 fix fr2s = tofix(r2s); 290 291 static if (RING) 292 fix fr10 = fr1s - fr0s; 293 fix fr21 = fr2s - fr1s; 294 295 for (int cy=y1;cy<y2;cy++) 296 { 297 auto row = v.scanline(cy); 298 for (int cx=x1;cx<x2;cx++) 299 { 300 alias SignedBitsType!(2*(8 + COLOR.channelBits)) SqrType; // fit the square of radius expressed as fixed-point 301 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 302 303 //static frac alphafunc(frac x) { return fracsqr(x); } 304 static frac alphafunc(frac x) { return x; } 305 306 static if (RING) 307 { 308 if (frs<fr0s) 309 {} 310 else 311 if (frs<fr2s) 312 { 313 frac alpha; 314 if (frs<fr1s) 315 alpha = alphafunc(cast(frac)fixdiv(frs-fr0s, fr10)); 316 else 317 alpha = cast(ubyte)(~cast(int)alphafunc(cast(frac)fixdiv(frs-fr1s, fr21))); 318 row[cx] = blendColor(color, row[cx], alpha); 319 } 320 } 321 else 322 { 323 if (frs<fr1s) 324 row[cx] = color; 325 else 326 if (frs<fr2s) 327 { 328 frac alpha = cast(ubyte)(~cast(int)alphafunc(cast(frac)fixdiv(frs-fr1s, fr21))); 329 row[cx] = blendColor(color, row[cx], alpha); 330 } 331 } 332 } 333 } 334 } 335 } 336 337 void softRing(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float r2, COLOR color) 338 if (isWritableView!V && is(COLOR : ViewColor!V)) 339 { 340 v.softRoundShape!true(x, y, r0, r1, r2, color); 341 } 342 343 void softCircle(V, COLOR)(auto ref V v, float x, float y, float r1, float r2, COLOR color) 344 if (isWritableView!V && is(COLOR : ViewColor!V)) 345 { 346 v.softRoundShape!false(x, y, 0, r1, r2, color); 347 } 348 349 template aaPutPixel(bool CHECKED=true, bool USE_ALPHA=true) 350 { 351 void aaPutPixel(F:float, V, COLOR, frac)(auto ref V v, F x, F y, COLOR color, frac alpha) 352 if (isWritableView!V && is(COLOR : ViewColor!V)) 353 { 354 mixin FixMath; 355 356 void plot(bool CHECKED2)(int x, int y, frac f) 357 { 358 static if (CHECKED2) 359 if (x<0 || x>=v.w || y<0 || y>=v.h) 360 return; 361 362 COLOR* p = v.pixelPtr(x, y); 363 static if (USE_ALPHA) f = fracmul(f, cast(frac)alpha); 364 *p = blendColor(color, *p, f); 365 } 366 367 fix fx = tofix(x); 368 fix fy = tofix(y); 369 int ix = fixto!int(fx); 370 int iy = fixto!int(fy); 371 static if (CHECKED) 372 if (ix>=0 && iy>=0 && ix+1<v.w && iy+1<v.h) 373 { 374 plot!false(ix , iy , fracmul(cast(ubyte)(~cast(int)fixfpart(fx)), cast(ubyte)(~cast(int)fixfpart(fy)))); 375 plot!false(ix , iy+1, fracmul(cast(ubyte)(~cast(int)fixfpart(fx)), fixfpart(fy))); 376 plot!false(ix+1, iy , fracmul( fixfpart(fx), cast(ubyte)(~cast(int)fixfpart(fy)))); 377 plot!false(ix+1, iy+1, fracmul( fixfpart(fx), fixfpart(fy))); 378 return; 379 } 380 plot!CHECKED(ix , iy , fracmul(cast(ubyte)(~cast(int)fixfpart(fx)), cast(ubyte)(~cast(int)fixfpart(fy)))); 381 plot!CHECKED(ix , iy+1, fracmul(cast(ubyte)(~cast(int)fixfpart(fx)), fixfpart(fy))); 382 plot!CHECKED(ix+1, iy , fracmul( fixfpart(fx), cast(ubyte)(~cast(int)fixfpart(fy)))); 383 plot!CHECKED(ix+1, iy+1, fracmul( fixfpart(fx), fixfpart(fy))); 384 } 385 } 386 387 void aaPutPixel(bool CHECKED=true, F:float, V, COLOR)(auto ref V v, F x, F y, COLOR color) 388 if (isWritableView!V && is(COLOR : ViewColor!V)) 389 { 390 //aaPutPixel!(false, F)(x, y, color, 0); // doesn't work, wtf 391 alias aaPutPixel!(CHECKED, false) f; 392 f(v, x, y, color, 0); 393 } 394 395 void hline(bool CHECKED=true, V, COLOR, frac)(auto ref V v, int x1, int x2, int y, COLOR color, frac alpha) 396 if (isWritableView!V && is(COLOR : ViewColor!V)) 397 { 398 mixin(CheckHLine); 399 400 if (alpha==0) 401 return; 402 else 403 if (alpha==frac.max) 404 v.scanline(y)[x1..x2] = color; 405 else 406 foreach (ref p; v.scanline(y)[x1..x2]) 407 p = blendColor(color, p, alpha); 408 } 409 410 void vline(bool CHECKED=true, V, COLOR, frac)(auto ref V v, int x, int y1, int y2, COLOR color, frac alpha) 411 if (isWritableView!V && is(COLOR : ViewColor!V)) 412 { 413 mixin(CheckVLine); 414 415 if (alpha==0) 416 return; 417 else 418 if (alpha==frac.max) 419 foreach (y; y1..y2) 420 v[x, y] = color; 421 else 422 foreach (y; y1..y2) 423 { 424 auto p = v.pixelPtr(x, y); 425 *p = blendColor(color, *p, alpha); 426 } 427 } 428 429 void aaFillRect(bool CHECKED=true, F:float, V, COLOR)(auto ref V v, F x1, F y1, F x2, F y2, COLOR color) 430 if (isWritableView!V && is(COLOR : ViewColor!V)) 431 { 432 mixin FixMath; 433 434 sort2(x1, x2); 435 sort2(y1, y2); 436 fix x1f = tofix(x1); int x1i = fixto!int(x1f); 437 fix y1f = tofix(y1); int y1i = fixto!int(y1f); 438 fix x2f = tofix(x2); int x2i = fixto!int(x2f); 439 fix y2f = tofix(y2); int y2i = fixto!int(y2f); 440 441 v.vline!CHECKED(x1i, y1i+1, y2i, color, cast(ubyte)(~cast(int)fixfpart(x1f))); 442 v.vline!CHECKED(x2i, y1i+1, y2i, color, fixfpart(x2f)); 443 v.hline!CHECKED(x1i+1, x2i, y1i, color, cast(ubyte)(~cast(int)fixfpart(y1f))); 444 v.hline!CHECKED(x1i+1, x2i, y2i, color, fixfpart(y2f)); 445 v.aaPutPixel!CHECKED(x1i, y1i, color, fracmul(cast(ubyte)(~cast(int)fixfpart(x1f)) , 446 cast(ubyte)(~cast(int)fixfpart(y1f))) ); 447 v.aaPutPixel!CHECKED(x1i, y2i, color, fracmul(cast(ubyte)(~cast(int)fixfpart(x1f)) , fixfpart(y2f))); 448 v.aaPutPixel!CHECKED(x2i, y1i, color, fracmul( fixfpart(x2f), cast(ubyte)(~cast(int)fixfpart(y1f))) ); 449 v.aaPutPixel!CHECKED(x2i, y2i, color, fracmul( fixfpart(x2f), fixfpart(y2f))); 450 451 v.fillRect!CHECKED(x1i+1, y1i+1, x2i, y2i, color); 452 } 453 454 unittest 455 { 456 // Test instantiation 457 ImageRef!RGB i; 458 i.w = 100; 459 i.h = 100; 460 i.pitch = 100; 461 RGB[] rgb; 462 rgb.reallocBuffer(100*100); 463 scope(exit) rgb.reallocBuffer(0); 464 i.pixels = rgb.ptr; 465 466 auto c = RGB(1, 2, 3); 467 i.rect(10, 10, 20, 20, c); 468 i.fillRect(10, 10, 20, 20, c); 469 i.aaFillRect(10, 10, 20, 20, c); 470 i.vline(10, 10, 20, c); 471 i.vline(10, 10, 20, c); 472 i.fillCircle(10, 10, 10, c); 473 i.fillSector(10, 10, 10, 10, 0.0, (2 * PI), c); 474 i.softRing(50, 50, 10, 15, 20, c); 475 i.softCircle(50, 50, 10, 15, c); 476 } 477 478 479 480 /// Rough anti-aliased fillsector 481 void aaFillSector(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float a0, float a1, COLOR c) 482 if (isWritableView!V && is(COLOR : ViewColor!V)) 483 { 484 alias ChannelType = COLOR.ChannelType; 485 486 if (a0 == a1) 487 return; 488 489 int x0 = cast(int)floorf(x - r1 - 1); 490 int x1 = cast(int)ceilf(x + r1 + 1); 491 492 int y0 = cast(int)floorf(y - r1 - 1); 493 int y1 = cast(int)ceilf(y + r1 + 1); 494 495 float r0s = r0-1; 496 if (r0s < 0) r0s = 0; 497 r0s = r0s * r0s; 498 float r1s = (r1 + 1) * (r1 + 1); 499 500 if (a0 > a1) 501 a1 += 2 * PI; 502 503 if (a0 < -PI || a1 < -PI) 504 { 505 // else atan2 will never produce angles below PI 506 a0 += 2 * PI; 507 a1 += 2 * PI; 508 } 509 510 int xmin = x0; 511 int xmax = x1+1; 512 int ymin = y0; 513 int ymax = y1+1; 514 515 // avoids to draw out of bounds 516 if (xmin < 0) 517 xmin = 0; 518 if (ymin < 0) 519 ymin = 0; 520 if (xmax > v.w) 521 xmax = v.w; 522 if (ymax > v.h) 523 ymax = v.h; 524 525 foreach (py; ymin .. ymax) 526 { 527 foreach (px; xmin .. xmax) 528 { 529 float dx = px-x; 530 float dy = py-y; 531 float rsq = dx * dx + dy * dy; 532 533 if(r0s <= rsq && rsq <= r1s) 534 { 535 float rs = sqrt(rsq); 536 537 // How much angle is one pixel at this radius? 538 // It's actually rule of 3. 539 // 2*pi radians => 2*pi*radius pixels 540 // ??? => 1 pixel 541 float aTransition = 1.0f / rs; 542 543 544 if (r0 <= rs && rs < r1) 545 { 546 float alpha = 1.0f; 547 if (r0 + 1 > rs) 548 alpha = rs - r0; 549 if (rs + 1 > r1) 550 alpha = r1 - rs; 551 552 float a = atan2(dy, dx); 553 bool inSector = (a0 <= a && a <= a1); 554 if (inSector) 555 { 556 float alpha2 = alpha; 557 if (a0 + aTransition > a) 558 alpha2 *= (a-a0) / aTransition; 559 else if (a + aTransition > a1) 560 alpha2 *= (a1 - a)/aTransition; 561 562 auto p = v.pixelPtr(px, py); 563 *p = blendColor(c, *p, cast(ChannelType)(0.5f + alpha2 * ChannelType.max)); 564 } 565 else 566 { 567 a += 2 * PI; 568 bool inSector2 = (a0 <= a && a <= a1); 569 if(inSector2 ) 570 { 571 float alpha2 = alpha; 572 if (a0 + aTransition > a) 573 alpha2 *= (a-a0) / aTransition; 574 else if (a + aTransition > a1) 575 alpha2 *= (a1 - a)/aTransition; 576 577 auto p = v.pixelPtr(px, py); 578 *p = blendColor(c, *p, cast(ChannelType)(0.5f + alpha2 * ChannelType.max)); 579 } 580 } 581 } 582 } 583 } 584 } 585 } 586 587 /** 588 Fill rectangle while interpolating a `COLOR` (can be depth) horiontally. 589 Params: 590 v The surface to write to. That be clipped by a dirtyRect. 591 rect The bounds of the slopped plane. The drawing itself will be 592 clipped to its limit, and the limits of the surface. 593 Should NOT be clipped by the dirtyRect. 594 c0 Color at left edge. 595 c1 Color at right edge. 596 */ 597 void horizontalSlope(float curvature = 1.0f, V, COLOR)(auto ref V v, 598 box2i rect, 599 COLOR c0, 600 COLOR c1) 601 if (isWritableView!V && is(COLOR : ViewColor!V)) 602 { 603 alias Type = COLOR.ChannelType; 604 605 box2i inter = box2i(0, 0, v.w, v.h).intersection(rect); 606 if (inter.empty) 607 return; 608 609 int x0 = rect.min.x; 610 int x1 = rect.max.x; 611 immutable float invX1mX0 = 1.0f / (x1 - x0); 612 613 foreach (px; inter.min.x .. inter.max.x) 614 { 615 float fAlpha = (px - x0) * invX1mX0; 616 static if (curvature != 1.0f) 617 fAlpha = fAlpha ^^ curvature; 618 Type alpha = cast(Type)( 0.5f + Type.max * fAlpha ); 619 COLOR c = blendColor(c1, c0, alpha); 620 vline!false(v, px, inter.min.y, inter.max.y, c); 621 } 622 } 623 624 /** 625 Fill rectangle while interpolating a `COLOR` (can be depth) vertically. 626 627 Params: 628 v The surface to write to. That be clipped by a dirtyRect. 629 rect The bounds of the slopped plane. The drawing itself will be 630 clipped to its limit, and the limits of the surface. 631 Should NOT be clipped by the dirtyRect. 632 c0 Color at top edge. 633 c1 Color at bottom edge. 634 */ 635 void verticalSlope(float curvature = 1.0f, V, COLOR)(auto ref V v, box2i rect, COLOR c0, COLOR c1) 636 if (isWritableView!V && is(COLOR : ViewColor!V)) 637 { 638 alias Type = COLOR.ChannelType; 639 640 box2i inter = box2i(0, 0, v.w, v.h).intersection(rect); 641 if (inter.empty) 642 return; 643 644 int x0 = rect.min.x; 645 int y0 = rect.min.y; 646 int x1 = rect.max.x; 647 int y1 = rect.max.y; 648 649 immutable float invY1mY0 = 1.0f / (y1 - y0); 650 651 foreach (py; inter.min.y .. inter.max.y) 652 { 653 float fAlpha = (py - y0) * invY1mY0; 654 static if (curvature != 1.0f) 655 fAlpha = fAlpha ^^ curvature; 656 Type alpha = cast(Type)( 0.5f + Type.max * fAlpha ); 657 COLOR c = blendColor(c1, c0, alpha); 658 hline!false(v, inter.min.x, inter.max.x, py, c); 659 } 660 } 661 662 663 void aaSoftDisc(float curvature = 1.0f, V, COLOR)(auto ref V v, float x, float y, float r1, float r2, COLOR color, float globalAlpha = 1.0f) 664 if (isWritableView!V && is(COLOR : ViewColor!V)) 665 { 666 alias ChannelType = COLOR.ChannelType; 667 assert(r1 <= r2); 668 int x1 = cast(int)(x-r2-1); if (x1<0) x1=0; 669 int y1 = cast(int)(y-r2-1); if (y1<0) y1=0; 670 int x2 = cast(int)(x+r2+1); if (x2>v.w) x2 = v.w; 671 int y2 = cast(int)(y+r2+1); if (y2>v.h) y2 = v.h; 672 673 auto r1s = r1*r1; 674 auto r2s = r2*r2; 675 676 float fx = x; 677 float fy = y; 678 679 immutable float fr1s = r1s; 680 immutable float fr2s = r2s; 681 682 immutable float fr21 = fr2s - fr1s; 683 immutable float invfr21 = 1 / fr21; 684 685 for (int cy=y1;cy<y2;cy++) 686 { 687 auto row = v.scanline(cy); 688 for (int cx=x1;cx<x2;cx++) 689 { 690 float dx = (fx - cx); 691 float dy = (fy - cy); 692 float frs = dx*dx + dy*dy; 693 694 if (frs<fr1s) 695 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * globalAlpha)); 696 else 697 { 698 if (frs<fr2s) 699 { 700 float alpha = (frs-fr1s) * invfr21; 701 static if (curvature != 1.0f) 702 alpha = alpha ^^ curvature; 703 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha)); 704 } 705 } 706 } 707 } 708 } 709 710 void aaSoftEllipse(float curvature = 1.0f, V, COLOR)(auto ref V v, float x, float y, float r1, float r2, float scaleX, float scaleY, COLOR color, float globalAlpha = 1.0f) 711 if (isWritableView!V && is(COLOR : ViewColor!V)) 712 { 713 alias ChannelType = COLOR.ChannelType; 714 assert(r1 <= r2); 715 int x1 = cast(int)(x-r2*scaleX-1); if (x1<0) x1=0; 716 int y1 = cast(int)(y-r2*scaleY-1); if (y1<0) y1=0; 717 int x2 = cast(int)(x+r2*scaleX+1); if (x2>v.w) x2 = v.w; 718 int y2 = cast(int)(y+r2*scaleY+1); if (y2>v.h) y2 = v.h; 719 720 float invScaleX = 1 / scaleX; 721 float invScaleY = 1 / scaleY; 722 723 auto r1s = r1*r1; 724 auto r2s = r2*r2; 725 726 float fx = x; 727 float fy = y; 728 729 immutable float fr1s = r1s; 730 immutable float fr2s = r2s; 731 732 immutable float fr21 = fr2s - fr1s; 733 immutable float invfr21 = 1 / fr21; 734 735 for (int cy=y1;cy<y2;cy++) 736 { 737 auto row = v.scanline(cy); 738 for (int cx=x1;cx<x2;cx++) 739 { 740 float dx = (fx - cx) * invScaleX; 741 float dy = (fy - cy) * invScaleY; 742 float frs = dx*dx + dy*dy; 743 744 if (frs<fr1s) 745 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * globalAlpha)); 746 else 747 { 748 if (frs<fr2s) 749 { 750 float alpha = (frs-fr1s) * invfr21; 751 static if (curvature != 1.0f) 752 alpha = alpha ^^ curvature; 753 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha)); 754 } 755 } 756 } 757 } 758 } 759 760 /// Draw a circle gradually fading in between r1 and r2 and fading out between r2 and r3 761 void aaSoftCircle(float curvature = 1.0f, V, COLOR)(auto ref V v, float x, float y, float r1, float r2, float r3, COLOR color, float globalAlpha = 1.0f) 762 if (isWritableView!V && is(COLOR : ViewColor!V)) 763 { 764 alias ChannelType = COLOR.ChannelType; 765 assert(r1 <= r2); 766 assert(r2 <= r3); 767 int x1 = cast(int)(x-r3-1); if (x1<0) x1=0; 768 int y1 = cast(int)(y-r3-1); if (y1<0) y1=0; 769 int x2 = cast(int)(x+r3+1); if (x2>v.w) x2 = v.w; 770 int y2 = cast(int)(y+r3+1); if (y2>v.h) y2 = v.h; 771 772 auto r1s = r1*r1; 773 auto r2s = r2*r2; 774 auto r3s = r3*r3; 775 776 float fx = x; 777 float fy = y; 778 779 immutable float fr1s = r1s; 780 immutable float fr2s = r2s; 781 immutable float fr3s = r3s; 782 783 immutable float fr21 = fr2s - fr1s; 784 immutable float fr32 = fr3s - fr2s; 785 immutable float invfr21 = 1 / fr21; 786 immutable float invfr32 = 1 / fr32; 787 788 for (int cy=y1;cy<y2;cy++) 789 { 790 auto row = v.scanline(cy); 791 for (int cx=x1;cx<x2;cx++) 792 { 793 float frs = (fx - cx)*(fx - cx) + (fy - cy)*(fy - cy); 794 795 if (frs >= fr1s) 796 { 797 if (frs < fr3s) 798 { 799 float alpha = void; 800 if (frs >= fr2s) 801 alpha = (frs - fr2s) * invfr32; 802 else 803 alpha = 1 - (frs - fr1s) * invfr21; 804 805 static if (curvature != 1.0f) 806 alpha = alpha ^^ curvature; 807 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha)); 808 } 809 } 810 } 811 } 812 } 813 814 815 void aaFillRectFloat(bool CHECKED=true, V, COLOR)(auto ref V v, float x1, float y1, float x2, float y2, COLOR color, float globalAlpha = 1.0f) 816 if (isWritableView!V && is(COLOR : ViewColor!V)) 817 { 818 if (globalAlpha == 0) 819 return; 820 821 alias ChannelType = COLOR.ChannelType; 822 823 sort2(x1, x2); 824 sort2(y1, y2); 825 826 int ix1 = cast(int)(floorf(x1)); 827 int iy1 = cast(int)(floorf(y1)); 828 int ix2 = cast(int)(floorf(x2)); 829 int iy2 = cast(int)(floorf(y2)); 830 float fx1 = x1 - ix1; 831 float fy1 = y1 - iy1; 832 float fx2 = x2 - ix2; 833 float fy2 = y2 - iy2; 834 835 static ChannelType toAlpha(float fraction) pure nothrow @nogc 836 { 837 return cast(ChannelType)(cast(int)(0.5f + ChannelType.max * fraction)); 838 } 839 840 v.aaPutPixelFloat!CHECKED(ix1, iy1, color, toAlpha(globalAlpha * (1-fx1) * (1-fy1) )); 841 v.hline!CHECKED(ix1+1, ix2, iy1, color, toAlpha(globalAlpha * (1 - fy1) )); 842 v.aaPutPixelFloat!CHECKED(ix2, iy1, color, toAlpha(globalAlpha * fx2 * (1-fy1) )); 843 844 v.vline!CHECKED(ix1, iy1+1, iy2, color, toAlpha(globalAlpha * (1 - fx1))); 845 v.vline!CHECKED(ix2, iy1+1, iy2, color, toAlpha(globalAlpha * fx2)); 846 847 v.aaPutPixelFloat!CHECKED(ix1, iy2, color, toAlpha(globalAlpha * (1-fx1) * fy2 )); 848 v.hline!CHECKED(ix1+1, ix2, iy2, color, toAlpha(globalAlpha * fy2)); 849 v.aaPutPixelFloat!CHECKED(ix2, iy2, color, toAlpha(globalAlpha * fx2 * fy2 )); 850 851 v.fillRectFloat!CHECKED(ix1+1, iy1+1, ix2, iy2, color, globalAlpha); 852 } 853 854 void fillRectFloat(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR b, float globalAlpha = 1.0f) // [) 855 if (isWritableView!V && is(COLOR : ViewColor!V)) 856 { 857 if (globalAlpha == 0) 858 return; 859 860 sort2(x1, x2); 861 sort2(y1, y2); 862 static if (CHECKED) 863 { 864 if (x1 >= v.w || y1 >= v.h || x2 <= 0 || y2 <= 0 || x1==x2 || y1==y2) return; 865 if (x1 < 0) x1 = 0; 866 if (y1 < 0) y1 = 0; 867 if (x2 >= v.w) x2 = v.w; 868 if (y2 >= v.h) y2 = v.h; 869 } 870 871 if (globalAlpha == 1) 872 { 873 foreach (y; y1..y2) 874 v.scanline(y)[x1..x2] = b; 875 } 876 else 877 { 878 alias ChannelType = COLOR.ChannelType; 879 static ChannelType toAlpha(float fraction) pure nothrow @nogc 880 { 881 return cast(ChannelType)(cast(int)(0.5f + ChannelType.max * fraction)); 882 } 883 884 ChannelType alpha = toAlpha(globalAlpha); 885 886 foreach (y; y1..y2) 887 { 888 COLOR[] scan = v.scanline(y); 889 foreach (x; x1..x2) 890 { 891 scan[x] = blendColor(b, scan[x], alpha); 892 } 893 } 894 } 895 } 896 897 void aaPutPixelFloat(bool CHECKED=true, V, COLOR, A)(auto ref V v, int x, int y, COLOR color, A alpha) 898 if (is(COLOR.ChannelType == A)) 899 { 900 static if (CHECKED) 901 if (x<0 || x>=v.w || y<0 || y>=v.h) 902 return; 903 904 COLOR* p = v.pixelPtr(x, y); 905 *p = blendColor(color, *p, alpha); 906 } 907 908 909 /// Blits a view onto another. 910 /// The views must have the same size. 911 /// PERF: optimize that 912 void blendWithAlpha(SRC, DST)(auto ref SRC srcView, 913 auto ref DST dstView, 914 auto ref ImageRef!L8 alphaView) 915 { 916 static assert(isDirectView!SRC); 917 static assert(isDirectView!DST); 918 static assert(isWritableView!DST); 919 920 alias COLOR = ViewColor!DST; 921 assert(srcView.w == dstView.w && srcView.h == dstView.h, "View size mismatch"); 922 923 foreach (y; 0..srcView.h) 924 { 925 COLOR* srcScan = srcView.scanline(y).ptr; 926 COLOR* dstScan = dstView.scanline(y).ptr; 927 L8* alphaScan = alphaView.scanline(y).ptr; 928 929 foreach (x; 0..srcView.w) 930 { 931 ubyte alpha = alphaScan[x].l; 932 if (alpha == 0) 933 continue; 934 static if (is(COLOR == RGBA)) 935 { 936 dstScan[x] = blendColor(srcScan[x], dstScan[x], alpha); 937 } 938 else static if (is(COLOR == L16)) 939 { 940 ushort alpha16 = (alpha << 8) | alpha; 941 dstScan[x] = blendColor(srcScan[x], dstScan[x], alpha16); 942 } 943 else 944 static assert(false); 945 } 946 } 947 } 948