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 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 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 i.uncheckedFloodFill(15, 15, RGB(4, 5, 6)); 502 } 503 504 505 506 /// Rough anti-aliased fillsector 507 void aaFillSector(V, COLOR)(auto ref V v, float x, float y, float r0, float r1, float a0, float a1, COLOR c) 508 if (isWritableView!V && is(COLOR : ViewColor!V)) 509 { 510 alias ChannelType = COLOR.ChannelType; 511 512 if (a0 == a1) 513 return; 514 515 int x0 = cast(int)floorf(x - r1 - 1); 516 int x1 = cast(int)ceilf(x + r1 + 1); 517 518 int y0 = cast(int)floorf(y - r1 - 1); 519 int y1 = cast(int)ceilf(y + r1 + 1); 520 521 float r0s = r0-1; 522 if (r0s < 0) r0s = 0; 523 r0s = r0s * r0s; 524 float r1s = (r1 + 1) * (r1 + 1); 525 526 if (a0 > a1) 527 a1 += 2 * PI; 528 529 if (a0 < -PI || a1 < -PI) 530 { 531 // else atan2 will never produce angles below PI 532 a0 += 2 * PI; 533 a1 += 2 * PI; 534 } 535 536 int xmin = x0; 537 int xmax = x1+1; 538 int ymin = y0; 539 int ymax = y1+1; 540 541 // avoids to draw out of bounds 542 if (xmin < 0) 543 xmin = 0; 544 if (ymin < 0) 545 ymin = 0; 546 if (xmax > v.w) 547 xmax = v.w; 548 if (ymax > v.h) 549 ymax = v.h; 550 551 foreach (py; ymin .. ymax) 552 { 553 foreach (px; xmin .. xmax) 554 { 555 float dx = px-x; 556 float dy = py-y; 557 float rsq = dx * dx + dy * dy; 558 559 if(r0s <= rsq && rsq <= r1s) 560 { 561 float rs = sqrt(rsq); 562 563 // How much angle is one pixel at this radius? 564 // It's actually rule of 3. 565 // 2*pi radians => 2*pi*radius pixels 566 // ??? => 1 pixel 567 float aTransition = 1.0f / rs; 568 569 570 if (r0 <= rs && rs < r1) 571 { 572 float alpha = 1.0f; 573 if (r0 + 1 > rs) 574 alpha = rs - r0; 575 if (rs + 1 > r1) 576 alpha = r1 - rs; 577 578 float a = atan2(dy, dx); 579 bool inSector = (a0 <= a && a <= a1); 580 if (inSector) 581 { 582 float alpha2 = alpha; 583 if (a0 + aTransition > a) 584 alpha2 *= (a-a0) / aTransition; 585 else if (a + aTransition > a1) 586 alpha2 *= (a1 - a)/aTransition; 587 588 auto p = v.pixelPtr(px, py); 589 *p = blendColor(c, *p, cast(ChannelType)(0.5f + alpha2 * ChannelType.max)); 590 } 591 else 592 { 593 a += 2 * PI; 594 bool inSector2 = (a0 <= a && a <= a1); 595 if(inSector2 ) 596 { 597 float alpha2 = alpha; 598 if (a0 + aTransition > a) 599 alpha2 *= (a-a0) / aTransition; 600 else if (a + aTransition > a1) 601 alpha2 *= (a1 - a)/aTransition; 602 603 auto p = v.pixelPtr(px, py); 604 *p = blendColor(c, *p, cast(ChannelType)(0.5f + alpha2 * ChannelType.max)); 605 } 606 } 607 } 608 } 609 } 610 } 611 } 612 613 /// Fill rectangle while interpolating a color horiontally 614 void horizontalSlope(float curvature = 1.0f, V, COLOR)(auto ref V v, box2i rect, COLOR c0, COLOR c1) 615 if (isWritableView!V && is(COLOR : ViewColor!V)) 616 { 617 alias ChannelType = COLOR.ChannelType; 618 619 box2i inter = box2i(0, 0, v.w, v.h).intersection(rect); 620 621 int x0 = rect.min.x; 622 int x1 = rect.max.x; 623 immutable float invX1mX0 = 1.0f / (x1 - x0); 624 625 foreach (px; inter.min.x .. inter.max.x) 626 { 627 float fAlpha = (px - x0) * invX1mX0; 628 static if (curvature != 1.0f) 629 fAlpha = fAlpha ^^ curvature; 630 ChannelType alpha = cast(ChannelType)( 0.5f + ChannelType.max * fAlpha ); // Not being generic here 631 COLOR c = blendColor(c1, c0, alpha); // warning .blend is confusing, c1 comes first 632 vline(v, px, inter.min.y, inter.max.y, c); 633 } 634 } 635 636 void verticalSlope(float curvature = 1.0f, V, COLOR)(auto ref V v, box2i rect, COLOR c0, COLOR c1) 637 if (isWritableView!V && is(COLOR : ViewColor!V)) 638 { 639 alias ChannelType = COLOR.ChannelType; 640 641 box2i inter = box2i(0, 0, v.w, v.h).intersection(rect); 642 643 int x0 = rect.min.x; 644 int y0 = rect.min.y; 645 int x1 = rect.max.x; 646 int y1 = rect.max.y; 647 648 immutable float invY1mY0 = 1.0f / (y1 - y0); 649 650 foreach (py; inter.min.y .. inter.max.y) 651 { 652 float fAlpha = (py - y0) * invY1mY0; 653 static if (curvature != 1.0f) 654 fAlpha = fAlpha ^^ curvature; 655 ChannelType alpha = cast(ChannelType)( 0.5f + ChannelType.max * fAlpha ); // Not being generic here 656 COLOR c = blendColor(c1, c0, alpha); // warning .blend is confusing, c1 comes first 657 hline(v, inter.min.x, inter.max.x, py, c); 658 } 659 } 660 661 662 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) 663 if (isWritableView!V && is(COLOR : ViewColor!V)) 664 { 665 alias ChannelType = COLOR.ChannelType; 666 assert(r1 <= r2); 667 int x1 = cast(int)(x-r2-1); if (x1<0) x1=0; 668 int y1 = cast(int)(y-r2-1); if (y1<0) y1=0; 669 int x2 = cast(int)(x+r2+1); if (x2>v.w) x2 = v.w; 670 int y2 = cast(int)(y+r2+1); if (y2>v.h) y2 = v.h; 671 672 auto r1s = r1*r1; 673 auto r2s = r2*r2; 674 675 float fx = x; 676 float fy = y; 677 678 immutable float fr1s = r1s; 679 immutable float fr2s = r2s; 680 681 immutable float fr21 = fr2s - fr1s; 682 immutable float invfr21 = 1 / fr21; 683 684 for (int cy=y1;cy<y2;cy++) 685 { 686 auto row = v.scanline(cy); 687 for (int cx=x1;cx<x2;cx++) 688 { 689 float dx = (fx - cx); 690 float dy = (fy - cy); 691 float frs = dx*dx + dy*dy; 692 693 if (frs<fr1s) 694 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * globalAlpha)); 695 else 696 { 697 if (frs<fr2s) 698 { 699 float alpha = (frs-fr1s) * invfr21; 700 static if (curvature != 1.0f) 701 alpha = alpha ^^ curvature; 702 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha)); 703 } 704 } 705 } 706 } 707 } 708 709 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) 710 if (isWritableView!V && is(COLOR : ViewColor!V)) 711 { 712 alias ChannelType = COLOR.ChannelType; 713 assert(r1 <= r2); 714 int x1 = cast(int)(x-r2*scaleX-1); if (x1<0) x1=0; 715 int y1 = cast(int)(y-r2*scaleY-1); if (y1<0) y1=0; 716 int x2 = cast(int)(x+r2*scaleX+1); if (x2>v.w) x2 = v.w; 717 int y2 = cast(int)(y+r2*scaleY+1); if (y2>v.h) y2 = v.h; 718 719 float invScaleX = 1 / scaleX; 720 float invScaleY = 1 / scaleY; 721 722 auto r1s = r1*r1; 723 auto r2s = r2*r2; 724 725 float fx = x; 726 float fy = y; 727 728 immutable float fr1s = r1s; 729 immutable float fr2s = r2s; 730 731 immutable float fr21 = fr2s - fr1s; 732 immutable float invfr21 = 1 / fr21; 733 734 for (int cy=y1;cy<y2;cy++) 735 { 736 auto row = v.scanline(cy); 737 for (int cx=x1;cx<x2;cx++) 738 { 739 float dx = (fx - cx) * invScaleX; 740 float dy = (fy - cy) * invScaleY; 741 float frs = dx*dx + dy*dy; 742 743 if (frs<fr1s) 744 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * globalAlpha)); 745 else 746 { 747 if (frs<fr2s) 748 { 749 float alpha = (frs-fr1s) * invfr21; 750 static if (curvature != 1.0f) 751 alpha = alpha ^^ curvature; 752 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha)); 753 } 754 } 755 } 756 } 757 } 758 759 /// Draw a circle gradually fading in between r1 and r2 and fading out between r2 and r3 760 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) 761 if (isWritableView!V && is(COLOR : ViewColor!V)) 762 { 763 alias ChannelType = COLOR.ChannelType; 764 assert(r1 <= r2); 765 assert(r2 <= r3); 766 int x1 = cast(int)(x-r3-1); if (x1<0) x1=0; 767 int y1 = cast(int)(y-r3-1); if (y1<0) y1=0; 768 int x2 = cast(int)(x+r3+1); if (x2>v.w) x2 = v.w; 769 int y2 = cast(int)(y+r3+1); if (y2>v.h) y2 = v.h; 770 771 auto r1s = r1*r1; 772 auto r2s = r2*r2; 773 auto r3s = r3*r3; 774 775 float fx = x; 776 float fy = y; 777 778 immutable float fr1s = r1s; 779 immutable float fr2s = r2s; 780 immutable float fr3s = r3s; 781 782 immutable float fr21 = fr2s - fr1s; 783 immutable float fr32 = fr3s - fr2s; 784 immutable float invfr21 = 1 / fr21; 785 immutable float invfr32 = 1 / fr32; 786 787 for (int cy=y1;cy<y2;cy++) 788 { 789 auto row = v.scanline(cy); 790 for (int cx=x1;cx<x2;cx++) 791 { 792 float frs = (fx - cx)*(fx - cx) + (fy - cy)*(fy - cy); 793 794 if (frs >= fr1s) 795 { 796 if (frs < fr3s) 797 { 798 float alpha = void; 799 if (frs >= fr2s) 800 alpha = (frs - fr2s) * invfr32; 801 else 802 alpha = 1 - (frs - fr1s) * invfr21; 803 804 static if (curvature != 1.0f) 805 alpha = alpha ^^ curvature; 806 row[cx] = blendColor(color, row[cx], cast(ChannelType)(0.5f + ChannelType.max * (1-alpha) * globalAlpha)); 807 } 808 } 809 } 810 } 811 } 812 813 814 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) 815 if (isWritableView!V && is(COLOR : ViewColor!V)) 816 { 817 if (globalAlpha == 0) 818 return; 819 820 alias ChannelType = COLOR.ChannelType; 821 822 sort2(x1, x2); 823 sort2(y1, y2); 824 825 int ix1 = cast(int)(floorf(x1)); 826 int iy1 = cast(int)(floorf(y1)); 827 int ix2 = cast(int)(floorf(x2)); 828 int iy2 = cast(int)(floorf(y2)); 829 float fx1 = x1 - ix1; 830 float fy1 = y1 - iy1; 831 float fx2 = x2 - ix2; 832 float fy2 = y2 - iy2; 833 834 static ChannelType toAlpha(float fraction) pure nothrow @nogc 835 { 836 return cast(ChannelType)(cast(int)(0.5f + ChannelType.max * fraction)); 837 } 838 839 v.aaPutPixelFloat!CHECKED(ix1, iy1, color, toAlpha(globalAlpha * (1-fx1) * (1-fy1) )); 840 v.hline!CHECKED(ix1+1, ix2, iy1, color, toAlpha(globalAlpha * (1 - fy1) )); 841 v.aaPutPixelFloat!CHECKED(ix2, iy1, color, toAlpha(globalAlpha * fx2 * (1-fy1) )); 842 843 v.vline!CHECKED(ix1, iy1+1, iy2, color, toAlpha(globalAlpha * (1 - fx1))); 844 v.vline!CHECKED(ix2, iy1+1, iy2, color, toAlpha(globalAlpha * fx2)); 845 846 v.aaPutPixelFloat!CHECKED(ix1, iy2, color, toAlpha(globalAlpha * (1-fx1) * fy2 )); 847 v.hline!CHECKED(ix1+1, ix2, iy2, color, toAlpha(globalAlpha * fy2)); 848 v.aaPutPixelFloat!CHECKED(ix2, iy2, color, toAlpha(globalAlpha * fx2 * fy2 )); 849 850 v.fillRectFloat!CHECKED(ix1+1, iy1+1, ix2, iy2, color, globalAlpha); 851 } 852 853 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) // [) 854 if (isWritableView!V && is(COLOR : ViewColor!V)) 855 { 856 if (globalAlpha == 0) 857 return; 858 859 sort2(x1, x2); 860 sort2(y1, y2); 861 static if (CHECKED) 862 { 863 if (x1 >= v.w || y1 >= v.h || x2 <= 0 || y2 <= 0 || x1==x2 || y1==y2) return; 864 if (x1 < 0) x1 = 0; 865 if (y1 < 0) y1 = 0; 866 if (x2 >= v.w) x2 = v.w; 867 if (y2 >= v.h) y2 = v.h; 868 } 869 870 if (globalAlpha == 1) 871 { 872 foreach (y; y1..y2) 873 v.scanline(y)[x1..x2] = b; 874 } 875 else 876 { 877 alias ChannelType = COLOR.ChannelType; 878 static ChannelType toAlpha(float fraction) pure nothrow @nogc 879 { 880 return cast(ChannelType)(cast(int)(0.5f + ChannelType.max * fraction)); 881 } 882 883 ChannelType alpha = toAlpha(globalAlpha); 884 885 foreach (y; y1..y2) 886 { 887 COLOR[] scan = v.scanline(y); 888 foreach (x; x1..x2) 889 { 890 scan[x] = blendColor(b, scan[x], alpha); 891 } 892 } 893 } 894 } 895 896 void aaPutPixelFloat(bool CHECKED=true, V, COLOR, A)(auto ref V v, int x, int y, COLOR color, A alpha) 897 if (is(COLOR.ChannelType == A)) 898 { 899 static if (CHECKED) 900 if (x<0 || x>=v.w || y<0 || y>=v.h) 901 return; 902 903 COLOR* p = v.pixelPtr(x, y); 904 *p = blendColor(color, *p, alpha); 905 } 906 907 908 /// Blits a view onto another. 909 /// The views must have the same size. 910 /// PERF: optimize that 911 void blendWithAlpha(SRC, DST)(auto ref SRC srcView, auto ref DST dstView, auto ref ImageRef!L8 alphaView) 912 { 913 static assert(isDirectView!SRC); 914 static assert(isDirectView!DST); 915 static assert(isWritableView!DST); 916 917 static ubyte blendByte(ubyte a, ubyte b, ubyte f) nothrow @nogc 918 { 919 int sum = ( f * a + b * (cast(ubyte)(~cast(int)f)) ) + 127; 920 return cast(ubyte)(sum / 255 );// ((sum+1)*257) >> 16 ); // integer divide by 255 921 } 922 923 static ushort blendShort(ushort a, ushort b, ubyte f) nothrow @nogc 924 { 925 ushort ff = (f << 8) | f; 926 int sum = ( ff * a + b * (cast(ushort)(~cast(int)ff)) ) + 32768; 927 return cast(ushort)( sum >> 16 ); // MAYDO: this doesn't map to the full range 928 } 929 930 alias COLOR = ViewColor!DST; 931 assert(srcView.w == dstView.w && srcView.h == dstView.h, "View size mismatch"); 932 933 foreach (y; 0..srcView.h) 934 { 935 COLOR* srcScan = srcView.scanline(y).ptr; 936 COLOR* dstScan = dstView.scanline(y).ptr; 937 L8* alphaScan = alphaView.scanline(y).ptr; 938 939 foreach (x; 0..srcView.w) 940 { 941 ubyte alpha = alphaScan[x].l; 942 if (alpha == 0) 943 continue; 944 static if (is(COLOR == RGBA)) 945 { 946 dstScan[x].r = blendByte(srcScan[x].r, dstScan[x].r, alpha); 947 dstScan[x].g = blendByte(srcScan[x].g, dstScan[x].g, alpha); 948 dstScan[x].b = blendByte(srcScan[x].b, dstScan[x].b, alpha); 949 dstScan[x].a = blendByte(srcScan[x].a, dstScan[x].a, alpha); 950 } 951 else static if (is(COLOR == L16)) 952 dstScan[x].l = blendShort(srcScan[x].l, dstScan[x].l, alpha); 953 else 954 static assert(false); 955 } 956 } 957 } 958