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