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