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