1 /** 2 * Drawing functions. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.utils.graphics.draw; 15 16 import std.algorithm : sort; 17 import std.traits; 18 19 import ae.utils.geometry : TAU; 20 import ae.utils.graphics.view; 21 import ae.utils.math; 22 import ae.utils.meta : structFields, SignedBitsType, UnsignedBitsType; 23 24 version(unittest) import ae.utils.graphics.image; 25 26 // Constraints could be simpler if this was fixed: 27 // https://d.puremagic.com/issues/show_bug.cgi?id=12386 28 29 /// Get the pixel color at the specified coordinates, 30 /// or fall back to the specified default value if 31 /// the coordinates are out of bounds. 32 COLOR safeGet(V, COLOR)(auto ref V v, int x, int y, COLOR def) 33 if (isView!V && is(COLOR : ViewColor!V)) 34 { 35 if (x>=0 && y>=0 && x<v.w && y<v.h) 36 return v[x, y]; 37 else 38 return def; 39 } 40 41 unittest 42 { 43 auto v = onePixel(7); 44 assert(v.safeGet(0, 0, 0) == 7); 45 assert(v.safeGet(0, 1, 0) == 0); 46 } 47 48 /// Set the pixel color at the specified coordinates 49 /// if the coordinates are not out of bounds. 50 void safePut(V, COLOR)(auto ref V v, int x, int y, COLOR value) 51 if (isWritableView!V && is(COLOR : ViewColor!V)) 52 { 53 if (x>=0 && y>=0 && x<v.w && y<v.h) 54 v[x, y] = value; 55 } 56 57 unittest 58 { 59 auto v = Image!int(1, 1); 60 v.safePut(0, 0, 7); 61 v.safePut(0, 1, 9); 62 assert(v[0, 0] == 7); 63 } 64 65 /// Forwards to safePut or opIndex, depending on the 66 /// CHECKED parameter. Allows propagation of a 67 /// CHECKED parameter from other callers. 68 void putPixel(bool CHECKED, V, COLOR)(auto ref V v, int x, int y, COLOR value) 69 if (isWritableView!V && is(COLOR : ViewColor!V)) 70 { 71 static if (CHECKED) 72 v.safePut(x, y, value); 73 else 74 v[x, y] = value; 75 } 76 77 unittest 78 { 79 auto v = Image!int(1, 1); 80 v.putPixel!false(0, 0, 7); 81 v.putPixel!true(0, 1, 9); 82 assert(v[0, 0] == 7); 83 } 84 85 /// Gets a pixel's address from a direct view. 86 ViewColor!V* pixelPtr(V)(auto ref V v, int x, int y) 87 if (isDirectView!V) 88 { 89 return &v.scanline(y)[x]; 90 } 91 92 unittest 93 { 94 auto v = Image!int(1, 1); 95 v[0, 0] = 7; 96 auto p = v.pixelPtr(0, 0); 97 assert(*p == 7); 98 } 99 100 /// Fills a writable view with a solid color. 101 void fill(V, COLOR)(auto ref V v, COLOR c) 102 if (isWritableView!V 103 && is(COLOR : ViewColor!V)) 104 { 105 foreach (y; 0..v.h) 106 { 107 static if (isDirectView!V) 108 v.scanline(y)[] = c; 109 else 110 foreach (x; 0..v.w) 111 v[x, y] = c; 112 } 113 } 114 deprecated alias clear = fill; 115 116 unittest 117 { 118 auto i = onePixel(0).copy(); 119 i.fill(1); 120 assert(i[0, 0] == 1); 121 auto t = i.tile(10, 10); 122 t.fill(2); 123 assert(i[0, 0] == 2); 124 } 125 126 // *************************************************************************** 127 128 enum CheckHLine = 129 q{ 130 static if (CHECKED) 131 { 132 if (x1 >= v.w || x2 <= 0 || y < 0 || y >= v.h || x1 >= x2) return; 133 if (x1 < 0) x1 = 0; 134 if (x2 >= v.w) x2 = v.w; 135 } 136 assert(x1 <= x2); 137 }; 138 139 enum CheckVLine = 140 q{ 141 static if (CHECKED) 142 { 143 if (x < 0 || x >= v.w || y1 >= v.h || y2 <= 0 || y1 >= y2) return; 144 if (y1 < 0) y1 = 0; 145 if (y2 >= v.h) y2 = v.h; 146 } 147 assert(y1 <= y2); 148 }; 149 150 void hline(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int x2, int y, COLOR c) 151 if (isWritableView!V && is(COLOR : ViewColor!V)) 152 { 153 mixin(CheckHLine); 154 static if (isDirectView!V) 155 v.scanline(y)[x1..x2] = c; 156 else 157 foreach (x; x1..x2) 158 v[x, y] = c; 159 } 160 161 void vline(bool CHECKED=true, V, COLOR)(auto ref V v, int x, int y1, int y2, COLOR c) 162 if (isWritableView!V && is(COLOR : ViewColor!V)) 163 { 164 mixin(CheckVLine); 165 foreach (y; y1..y2) // TODO: optimize 166 v[x, y] = c; 167 } 168 169 void line(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR c) 170 if (isWritableView!V && is(COLOR : ViewColor!V)) 171 { 172 mixin FixMath; 173 import std.algorithm.mutation : swap; 174 175 enum DrawLine = q{ 176 // Axis-independent part. Mixin context: 177 // a0 .. a1 - longer side 178 // b0 .. b1 - shorter side 179 // DrawPixel - mixin to draw a pixel at coordinates (a, b) 180 181 if (a0 == a1) 182 return; 183 184 if (a0 > a1) 185 { 186 swap(a0, a1); 187 swap(b0, b1); 188 } 189 190 // Use fixed-point for b position and offset per 1 pixel along "a" axis 191 assert(b0 < (1L<<coordinateBits) && b1 < (1L<<coordinateBits)); 192 SignedBitsType!(coordinateBits*2) bPos = b0 << coordinateBits; 193 SignedBitsType!(coordinateBits*2) bOff = ((b1-b0) << coordinateBits) / (a1-a0); 194 195 foreach (a; a0..a1+1) 196 { 197 int b = (bPos += bOff) >> coordinateBits; 198 mixin(DrawPixel); 199 } 200 }; 201 202 import std.math : abs; 203 204 if (abs(x2-x1) > abs(y2-y1)) 205 { 206 alias x1 a0; 207 alias x2 a1; 208 alias y1 b0; 209 alias y2 b1; 210 enum DrawPixel = q{ v.putPixel!CHECKED(a, b, c); }; 211 mixin(DrawLine); 212 } 213 else 214 { 215 alias y1 a0; 216 alias y2 a1; 217 alias x1 b0; 218 alias x2 b1; 219 enum DrawPixel = q{ v.putPixel!CHECKED(b, a, c); }; 220 mixin(DrawLine); 221 } 222 } 223 224 /// Draws a rectangle with a solid line. 225 /// The coordinates represent bounds (open on the right) for the outside of the rectangle. 226 void rect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR c) 227 if (isWritableView!V && is(COLOR : ViewColor!V)) 228 { 229 sort2(x1, x2); 230 sort2(y1, y2); 231 v.hline!CHECKED(x1, x2, y1 , c); 232 v.hline!CHECKED(x1, x2, y2-1, c); 233 v.vline!CHECKED(x1 , y1, y2, c); 234 v.vline!CHECKED(x2-1, y1, y2, c); 235 } 236 237 void fillRect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR b) // [) 238 if (isWritableView!V && is(COLOR : ViewColor!V)) 239 { 240 sort2(x1, x2); 241 sort2(y1, y2); 242 static if (CHECKED) 243 { 244 if (x1 >= v.w || y1 >= v.h || x2 <= 0 || y2 <= 0 || x1==x2 || y1==y2) return; 245 if (x1 < 0) x1 = 0; 246 if (y1 < 0) y1 = 0; 247 if (x2 >= v.w) x2 = v.w; 248 if (y2 >= v.h) y2 = v.h; 249 } 250 foreach (y; y1..y2) 251 v.hline!false(x1, x2, y, b); 252 } 253 254 void fillRect(bool CHECKED=true, V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, COLOR c, COLOR b) // [) 255 if (isWritableView!V && is(COLOR : ViewColor!V)) 256 { 257 v.rect!CHECKED(x1, y1, x2, y2, c); 258 if (x2-x1>2 && y2-y1>2) 259 v.fillRect!CHECKED(x1+1, y1+1, x2-1, y2-1, b); 260 } 261 262 /// Unchecked! Make sure area is bounded. 263 void uncheckedFloodFill(V, COLOR)(auto ref V v, int x, int y, COLOR c) 264 if (isDirectView!V && is(COLOR : ViewColor!V)) 265 { 266 v.floodFillPtr(&v[x, y], c, v[x, y]); 267 } 268 269 private void floodFillPtr(V, COLOR)(auto ref V v, COLOR* pp, COLOR c, COLOR f) 270 if (isDirectView!V && is(COLOR : ViewColor!V)) 271 { 272 COLOR* p0 = pp; while (*p0==f) p0--; p0++; 273 COLOR* p1 = pp; while (*p1==f) p1++; p1--; 274 auto stride = v.scanline(1).ptr-v.scanline(0).ptr; 275 for (auto p=p0; p<=p1; p++) 276 *p = c; 277 p0 -= stride; p1 -= stride; 278 for (auto p=p0; p<=p1; p++) 279 if (*p == f) 280 v.floodFillPtr(p, c, f); 281 p0 += stride*2; p1 += stride*2; 282 for (auto p=p0; p<=p1; p++) 283 if (*p == f) 284 v.floodFillPtr(p, c, f); 285 } 286 287 void fillCircle(V, COLOR)(auto ref V v, int x, int y, int r, COLOR c) 288 if (isWritableView!V && is(COLOR : ViewColor!V)) 289 { 290 import std.algorithm.comparison : min; 291 292 int x0 = x>r?x-r:0; 293 int y0 = y>r?y-r:0; 294 int x1 = min(x+r, v.w-1); 295 int y1 = min(y+r, v.h-1); 296 int rs = sqr(r); 297 // TODO: optimize 298 foreach (py; y0..y1+1) 299 foreach (px; x0..x1+1) 300 if (sqr(x-px) + sqr(y-py) < rs) 301 v[px, py] = c; 302 } 303 304 void fillSector(V, COLOR)(auto ref V v, int x, int y, int r0, int r1, real a0, real a1, COLOR c) 305 if (isWritableView!V && is(COLOR : ViewColor!V)) 306 { 307 import std.algorithm.comparison : min; 308 import std.math : atan2; 309 310 int x0 = x>r1?x-r1:0; 311 int y0 = y>r1?y-r1:0; 312 int x1 = min(x+r1, v.w-1); 313 int y1 = min(y+r1, v.h-1); 314 int r0s = sqr(r0); 315 int r1s = sqr(r1); 316 if (a0 > a1) 317 a1 += TAU; 318 foreach (py; y0..y1+1) 319 foreach (px; x0..x1+1) 320 { 321 int dx = px-x; 322 int dy = py-y; 323 int rs = sqr(dx) + sqr(dy); 324 if (r0s <= rs && rs < r1s) 325 { 326 real a = atan2(cast(real)dy, cast(real)dx); 327 if ((a0 <= a && a <= a1) || 328 (a0 <= a+TAU && a+TAU <= a1)) 329 v[px, py] = c; 330 } 331 } 332 } 333 334 struct Coord { int x, y; string toString() { import std.string; return format("%s", [this.tupleof]); } } 335 336 void fillPoly(V, COLOR)(auto ref V v, Coord[] coords, COLOR f) 337 if (isWritableView!V && is(COLOR : ViewColor!V)) 338 { 339 import std.algorithm.comparison : min, max; 340 341 int minY, maxY; 342 minY = maxY = coords[0].y; 343 foreach (c; coords[1..$]) 344 minY = min(minY, c.y), 345 maxY = max(maxY, c.y); 346 347 foreach (y; minY..maxY+1) 348 { 349 int[] intersections; 350 for (uint i=0; i<coords.length; i++) 351 { 352 auto c0=coords[i], c1=coords[i==$-1?0:i+1]; 353 if (y==c0.y) 354 { 355 assert(y == coords[i%$].y); 356 int pi = i-1; int py; 357 while ((py=coords[(pi+$)%$].y)==y) 358 pi--; 359 int ni = i+1; int ny; 360 while ((ny=coords[ni%$].y)==y) 361 ni++; 362 if (ni > coords.length) 363 continue; 364 if ((py>y) == (y>ny)) 365 intersections ~= coords[i%$].x; 366 i = ni-1; 367 } 368 else 369 if (c0.y<y && y<c1.y) 370 intersections ~= itpl(c0.x, c1.x, y, c0.y, c1.y); 371 else 372 if (c1.y<y && y<c0.y) 373 intersections ~= itpl(c1.x, c0.x, y, c1.y, c0.y); 374 } 375 376 assert(intersections.length % 2==0); 377 intersections.sort(); 378 for (uint i=0; i<intersections.length; i+=2) 379 v.hline!true(intersections[i], intersections[i+1], y, f); 380 } 381 } 382 383 // No caps 384 void thickLine(V, COLOR)(auto ref V v, int x1, int y1, int x2, int y2, int r, COLOR c) 385 if (isWritableView!V && is(COLOR : ViewColor!V)) 386 { 387 int dx = x2-x1; 388 int dy = y2-y1; 389 int d = cast(int)sqrt(cast(float)(sqr(dx)+sqr(dy))); 390 if (d==0) return; 391 392 int nx = dx*r/d; 393 int ny = dy*r/d; 394 395 fillPoly([ 396 Coord(x1-ny, y1+nx), 397 Coord(x1+ny, y1-nx), 398 Coord(x2+ny, y2-nx), 399 Coord(x2-ny, y2+nx), 400 ], c); 401 } 402 403 // No caps 404 void thickLinePoly(V, COLOR)(auto ref V v, Coord[] coords, int r, COLOR c) 405 if (isWritableView!V && is(COLOR : ViewColor!V)) 406 { 407 foreach (i; 0..coords.length) 408 thickLine(coords[i].tupleof, coords[(i+1)%$].tupleof, r, c); 409 } 410 411 // ************************************************************************************************************************************ 412 413 mixin template FixMath(ubyte coordinateBitsParam = 16) 414 { 415 import ae.utils.meta : SignedBitsType, UnsignedBitsType; 416 417 enum coordinateBits = coordinateBitsParam; 418 419 static assert(COLOR.homogenous, "Asymmetric color types not supported, fix me!"); 420 /// Fixed-point type, big enough to hold a coordinate, with fractionary precision corresponding to channel precision. 421 alias fix = SignedBitsType!(COLOR.channelBits + coordinateBits); 422 /// Type to hold temporary values for multiplication and division 423 alias fix2 = SignedBitsType!(COLOR.channelBits*2 + coordinateBits); 424 425 static assert(COLOR.channelBits < 32, "Shift operators are broken for shifts over 32 bits, fix me!"); 426 fix tofix(T:int )(T x) { return cast(fix) (x<<COLOR.channelBits); } 427 fix tofix(T:float)(T x) { return cast(fix) (x*(1<<COLOR.channelBits)); } 428 T fixto(T:int)(fix x) { return cast(T)(x>>COLOR.channelBits); } 429 430 fix fixsqr(fix x) { return cast(fix)((cast(fix2)x*x) >> COLOR.channelBits); } 431 fix fixmul(fix x, fix y) { return cast(fix)((cast(fix2)x*y) >> COLOR.channelBits); } 432 fix fixdiv(fix x, fix y) { return cast(fix)((cast(fix2)x << COLOR.channelBits)/y); } 433 434 static assert(COLOR.ChannelType.sizeof*8 == COLOR.channelBits, "COLORs with ChannelType not corresponding to native type not currently supported, fix me!"); 435 /// Type only large enough to hold a fractionary part of a "fix" (i.e. color channel precision). Used for alpha values, etc. 436 alias COLOR.ChannelType frac; 437 /// Type to hold temporary values for multiplication and division 438 alias UnsignedBitsType!(COLOR.channelBits*2) frac2; 439 440 frac tofrac(T:float)(T x) { return cast(frac) (x*(1<<COLOR.channelBits)); } 441 frac fixfpart(fix x) { return cast(frac)x; } 442 frac fracsqr(frac x ) { return cast(frac)((cast(frac2)x*x) >> COLOR.channelBits); } 443 frac fracmul(frac x, frac y) { return cast(frac)((cast(frac2)x*y) >> COLOR.channelBits); } 444 445 frac tofracBounded(T:float)(T x) { return cast(frac) bound(tofix(x), 0, frac.max); } 446 } 447 448 // ************************************************************************************************************************************ 449 450 void whiteNoise(V)(V v) 451 if (isWritableView!V) 452 { 453 import std.random; 454 alias COLOR = ViewColor!V; 455 456 for (int y=0;y<v.h/2;y++) 457 for (int x=0;x<v.w/2;x++) 458 v[x*2, y*2] = COLOR.monochrome(uniform!(COLOR.ChannelType)()); 459 460 // interpolate 461 enum AVERAGE = q{(a+b)/2}; 462 463 for (int y=0;y<v.h/2;y++) 464 for (int x=0;x<v.w/2-1;x++) 465 v[x*2+1, y*2 ] = COLOR.op!AVERAGE(v[x*2 , y*2], v[x*2+2, y*2 ]); 466 for (int y=0;y<v.h/2-1;y++) 467 for (int x=0;x<v.w/2;x++) 468 v[x*2 , y*2+1] = COLOR.op!AVERAGE(v[x*2 , y*2], v[x*2 , y*2+2]); 469 for (int y=0;y<v.h/2-1;y++) 470 for (int x=0;x<v.w/2-1;x++) 471 v[x*2+1, y*2+1] = COLOR.op!AVERAGE(v[x*2+1, y*2], v[x*2+2, y*2+2]); 472 } 473 474 private template softRoundShape(bool RING) 475 { 476 void softRoundShape(T, V, COLOR)(auto ref V v, T x, T y, T r0, T r1, T r2, COLOR color) 477 if (isWritableView!V && isNumeric!T && is(COLOR : ViewColor!V)) 478 { 479 mixin FixMath; 480 481 assert(r0 <= r1); 482 assert(r1 <= r2); 483 assert(r2 < 256); // precision constraint - see SqrType 484 //int ix = cast(int)x; 485 //int iy = cast(int)y; 486 //int ir1 = cast(int)sqr(r1-1); 487 //int ir2 = cast(int)sqr(r2+1); 488 int x1 = cast(int)(x-r2-1); if (x1<0) x1=0; 489 int y1 = cast(int)(y-r2-1); if (y1<0) y1=0; 490 int x2 = cast(int)(x+r2+1); if (x2>v.w) x2 = v.w; 491 int y2 = cast(int)(y+r2+1); if (y2>v.h) y2 = v.h; 492 493 static if (RING) 494 auto r0s = r0*r0; 495 auto r1s = r1*r1; 496 auto r2s = r2*r2; 497 //float rds = r2s - r1s; 498 499 fix fx = tofix(x); 500 fix fy = tofix(y); 501 502 static if (RING) 503 fix fr0s = tofix(r0s); 504 fix fr1s = tofix(r1s); 505 fix fr2s = tofix(r2s); 506 507 static if (RING) 508 fix fr10 = fr1s - fr0s; 509 fix fr21 = fr2s - fr1s; 510 511 for (int cy=y1;cy<y2;cy++) 512 { 513 auto row = v.scanline(cy); 514 for (int cx=x1;cx<x2;cx++) 515 { 516 alias SignedBitsType!(2*(8 + COLOR.channelBits)) SqrType; // fit the square of radius expressed as fixed-point 517 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 518 519 //static frac alphafunc(frac x) { return fracsqr(x); } 520 static frac alphafunc(frac x) { return x; } 521 522 static if (RING) 523 { 524 if (frs<fr0s) 525 {} 526 else 527 if (frs<fr2s) 528 { 529 frac alpha; 530 if (frs<fr1s) 531 alpha = alphafunc(cast(frac)fixdiv(frs-fr0s, fr10)); 532 else 533 alpha = alphafunc(cast(frac)fixdiv(frs-fr1s, fr21)).flipBits; 534 row[cx] = COLOR.op!q{.blend(a, b, c)}(color, row[cx], alpha); 535 } 536 } 537 else 538 { 539 if (frs<fr1s) 540 row[cx] = color; 541 else 542 if (frs<fr2s) 543 { 544 frac alpha = alphafunc(cast(frac)fixdiv(frs-fr1s, fr21)).flipBits; 545 row[cx] = COLOR.op!q{.blend(a, b, c)}(color, row[cx], alpha); 546 } 547 } 548 } 549 } 550 } 551 } 552 553 void softRing(T, V, COLOR)(auto ref V v, T x, T y, T r0, T r1, T r2, COLOR color) 554 if (isWritableView!V && isNumeric!T && is(COLOR : ViewColor!V)) 555 { 556 v.softRoundShape!true(x, y, r0, r1, r2, color); 557 } 558 559 void softCircle(T, V, COLOR)(auto ref V v, T x, T y, T r1, T r2, COLOR color) 560 if (isWritableView!V && isNumeric!T && is(COLOR : ViewColor!V)) 561 { 562 v.softRoundShape!false(x, y, cast(T)0, r1, r2, color); 563 } 564 565 template aaPutPixel(bool CHECKED=true, bool USE_ALPHA=true) 566 { 567 void aaPutPixel(F:float, V, COLOR, frac)(auto ref V v, F x, F y, COLOR color, frac alpha) 568 if (isWritableView!V && is(COLOR : ViewColor!V)) 569 { 570 mixin FixMath; 571 572 void plot(bool CHECKED2)(int x, int y, frac f) 573 { 574 static if (CHECKED2) 575 if (x<0 || x>=v.w || y<0 || y>=v.h) 576 return; 577 578 COLOR* p = v.pixelPtr(x, y); 579 static if (USE_ALPHA) f = fracmul(f, cast(frac)alpha); 580 *p = COLOR.op!q{.blend(a, b, c)}(color, *p, f); 581 } 582 583 fix fx = tofix(x); 584 fix fy = tofix(y); 585 int ix = fixto!int(fx); 586 int iy = fixto!int(fy); 587 static if (CHECKED) 588 if (ix>=0 && iy>=0 && ix+1<v.w && iy+1<v.h) 589 { 590 plot!false(ix , iy , fracmul(fixfpart(fx).flipBits, fixfpart(fy).flipBits)); 591 plot!false(ix , iy+1, fracmul(fixfpart(fx).flipBits, fixfpart(fy) )); 592 plot!false(ix+1, iy , fracmul(fixfpart(fx) , fixfpart(fy).flipBits)); 593 plot!false(ix+1, iy+1, fracmul(fixfpart(fx) , fixfpart(fy) )); 594 return; 595 } 596 plot!CHECKED(ix , iy , fracmul(fixfpart(fx).flipBits, fixfpart(fy).flipBits)); 597 plot!CHECKED(ix , iy+1, fracmul(fixfpart(fx).flipBits, fixfpart(fy) )); 598 plot!CHECKED(ix+1, iy , fracmul(fixfpart(fx) , fixfpart(fy).flipBits)); 599 plot!CHECKED(ix+1, iy+1, fracmul(fixfpart(fx) , fixfpart(fy) )); 600 } 601 } 602 603 void aaPutPixel(bool CHECKED=true, F:float, V, COLOR)(auto ref V v, F x, F y, COLOR color) 604 if (isWritableView!V && is(COLOR : ViewColor!V)) 605 { 606 //aaPutPixel!(false, F)(x, y, color, 0); // doesn't work, wtf 607 alias aaPutPixel!(CHECKED, false) f; 608 f(v, x, y, color, 0); 609 } 610 611 void hline(bool CHECKED=true, V, COLOR, frac)(auto ref V v, int x1, int x2, int y, COLOR color, frac alpha) 612 if (isWritableView!V && is(COLOR : ViewColor!V)) 613 { 614 mixin(CheckHLine); 615 616 if (alpha==0) 617 return; 618 else 619 if (alpha==frac.max) 620 v.scanline(y)[x1..x2] = color; 621 else 622 foreach (ref p; v.scanline(y)[x1..x2]) 623 p = COLOR.op!q{.blend(a, b, c)}(color, p, alpha); 624 } 625 626 void vline(bool CHECKED=true, V, COLOR, frac)(auto ref V v, int x, int y1, int y2, COLOR color, frac alpha) 627 if (isWritableView!V && is(COLOR : ViewColor!V)) 628 { 629 mixin(CheckVLine); 630 631 if (alpha==0) 632 return; 633 else 634 if (alpha==frac.max) 635 foreach (y; y1..y2) 636 v[x, y] = color; 637 else 638 foreach (y; y1..y2) 639 { 640 auto p = v.pixelPtr(x, y); 641 *p = COLOR.op!q{.blend(a, b, c)}(color, *p, alpha); 642 } 643 } 644 645 void aaFillRect(bool CHECKED=true, F:float, V, COLOR)(auto ref V v, F x1, F y1, F x2, F y2, COLOR color) 646 if (isWritableView!V && is(COLOR : ViewColor!V)) 647 { 648 mixin FixMath; 649 650 sort2(x1, x2); 651 sort2(y1, y2); 652 fix x1f = tofix(x1); int x1i = fixto!int(x1f); 653 fix y1f = tofix(y1); int y1i = fixto!int(y1f); 654 fix x2f = tofix(x2); int x2i = fixto!int(x2f); 655 fix y2f = tofix(y2); int y2i = fixto!int(y2f); 656 657 v.vline!CHECKED(x1i, y1i+1, y2i, color, fixfpart(x1f).flipBits); 658 v.vline!CHECKED(x2i, y1i+1, y2i, color, fixfpart(x2f) ); 659 v.hline!CHECKED(x1i+1, x2i, y1i, color, fixfpart(y1f).flipBits); 660 v.hline!CHECKED(x1i+1, x2i, y2i, color, fixfpart(y2f) ); 661 v.aaPutPixel!CHECKED(x1i, y1i, color, fracmul(fixfpart(x1f).flipBits, fixfpart(y1f).flipBits)); 662 v.aaPutPixel!CHECKED(x1i, y2i, color, fracmul(fixfpart(x1f).flipBits, fixfpart(y2f) )); 663 v.aaPutPixel!CHECKED(x2i, y1i, color, fracmul(fixfpart(x2f) , fixfpart(y1f).flipBits)); 664 v.aaPutPixel!CHECKED(x2i, y2i, color, fracmul(fixfpart(x2f) , fixfpart(y2f) )); 665 666 v.fillRect!CHECKED(x1i+1, y1i+1, x2i, y2i, color); 667 } 668 669 void aaLine(bool CHECKED=true, V, COLOR)(auto ref V v, float x1, float y1, float x2, float y2, COLOR color) 670 if (isWritableView!V && is(COLOR : ViewColor!V)) 671 { 672 import std.math : abs; 673 674 // Simplistic straight-forward implementation. TODO: optimize 675 if (abs(x1-x2) > abs(y1-y2)) 676 for (auto x=x1; sign(x1-x2)!=sign(x2-x); x += sign(x2-x1)) 677 v.aaPutPixel!CHECKED(x, itpl(y1, y2, x, x1, x2), color); 678 else 679 for (auto y=y1; sign(y1-y2)!=sign(y2-y); y += sign(y2-y1)) 680 v.aaPutPixel!CHECKED(itpl(x1, x2, y, y1, y2), y, color); 681 } 682 683 void aaLine(bool CHECKED=true, V, COLOR, frac)(auto ref V v, float x1, float y1, float x2, float y2, COLOR color, frac alpha) 684 if (isWritableView!V && is(COLOR : ViewColor!V)) 685 { 686 import std.math : abs; 687 688 // ditto 689 if (abs(x1-x2) > abs(y1-y2)) 690 for (auto x=x1; sign(x1-x2)!=sign(x2-x); x += sign(x2-x1)) 691 v.aaPutPixel!CHECKED(x, itpl(y1, y2, x, x1, x2), color, alpha); 692 else 693 for (auto y=y1; sign(y1-y2)!=sign(y2-y); y += sign(y2-y1)) 694 v.aaPutPixel!CHECKED(itpl(x1, x2, y, y1, y2), y, color, alpha); 695 } 696 697 unittest 698 { 699 // Test instantiation 700 import ae.utils.graphics.color; 701 auto i = Image!RGB(100, 100); 702 auto c = RGB(1, 2, 3); 703 i.whiteNoise(); 704 i.aaLine(10, 10, 20, 20, c); 705 i.aaLine(10f, 10f, 20f, 20f, c, 100); 706 i.rect(10, 10, 20, 20, c); 707 i.fillRect(10, 10, 20, 20, c); 708 i.aaFillRect(10, 10, 20, 20, c); 709 i.vline(10, 10, 20, c); 710 i.vline(10, 10, 20, c); 711 i.line(10, 10, 20, 20, c); 712 i.fillCircle(10, 10, 10, c); 713 i.fillSector(10, 10, 10, 10, 0.0, TAU, c); 714 i.softRing(50, 50, 10, 15, 20, c); 715 i.softCircle(50, 50, 10, 15, c); 716 i.fillPoly([Coord(10, 10), Coord(10, 20), Coord(20, 20)], c); 717 i.uncheckedFloodFill(15, 15, RGB(4, 5, 6)); 718 } 719 720 // ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 721 722 // Outdated code from before the overhaul. 723 // TODO: fix and update 724 725 version(none): 726 727 /// Draws an image. Returns this. 728 typeof(this) draw(bool CHECKED=true, SRCCANVAS)(int x, int y, SRCCANVAS v) 729 if (IsCanvas!SRCCANVAS && is(COLOR == SRCCANVAS.COLOR)) 730 { 731 static if (CHECKED) 732 { 733 if (v.w == 0 || v.h == 0 || 734 x+v.w <= 0 || y+v.h <= 0 || x >= w || y >= h) 735 return this; 736 737 auto r = v.window(0, 0, v.w, v.h); 738 if (x < 0) 739 r = r.window(-x, 0, r.w, r.h), 740 x = 0; 741 if (y < 0) 742 r = r.window(0, -y, r.w, r.h), 743 y = 0; 744 if (x+r.w > w) 745 r = r.window(0, 0, w-x, r.h); 746 if (y+r.h > h) 747 r = r.window(0, 0, r.w, h-y); 748 749 draw!false(x, y, r); 750 } 751 else 752 { 753 assert(v.w > 0 && v.h > 0); 754 assert(x >= 0 && x+v.w <= w && y >= 0 && y+v.h <= h); 755 756 // TODO: alpha blending 757 size_t dstStart = y*stride+x, srcStart = 0; 758 foreach (j; 0..v.h) 759 pixels[dstStart..dstStart+v.w] = v.pixels[srcStart..srcStart+v.w], 760 dstStart += stride, 761 srcStart += v.stride; 762 } 763 764 return this; 765 } 766 767 void subpixelDownscale()() 768 if (structFields!COLOR == ["r","g","b"] || structFields!COLOR == ["b","g","r"]) 769 { 770 Image!COLOR i; 771 i.size(HRX + hr.w*3 + HRX, hr.h); 772 i.draw(0, 0, hr.window(0, 0, HRX, hr.h)); 773 i.window(HRX, 0, HRX+hr.w*3, hr.h).upscaleDraw!(3, 1)(hr); 774 i.draw(HRX + hr.w*3, 0, hr.window(hr.w-HRX, 0, hr.w, hr.h)); 775 alias Color!(COLOR.BaseType, "g") BASE; 776 Image!BASE[3] channels; 777 Image!BASE scratch; 778 scratch.size(hr.w*3, hr.h); 779 780 foreach (int cx, char c; ValueTuple!('r', 'g', 'b')) 781 { 782 auto w = i.window(cx*HRX, 0, cx*HRX+hr.w*3, hr.h); 783 scratch.transformDraw!(`COLOR(c.`~c~`)`)(0, 0, w); 784 channels[cx].size(lr.w, lr.h); 785 channels[cx].downscaleDraw!(3*HRX, HRY)(scratch); 786 } 787 788 foreach (y; 0..lr.h) 789 foreach (x; 0..lr.w) 790 { 791 COLOR c; 792 c.r = channels[0][x, y].g; 793 c.g = channels[1][x, y].g; 794 c.b = channels[2][x, y].g; 795 lr[x, y] = c; 796 } 797 }