1 /** 2 * Copyright: Mike Wey 2011 3 * License: zlib (See accompanying LICENSE file) 4 * Authors: Mike Wey 5 */ 6 7 module dmagick.DrawingContext; 8 9 import std.algorithm; 10 import std.array; 11 import std.conv; 12 import std.file; 13 import std.string; 14 15 import dmagick.Color; 16 import dmagick.Exception; 17 import dmagick.Geometry; 18 import dmagick.Image; 19 import dmagick.Options; 20 import dmagick.Utils; 21 22 import dmagick.c.composite; 23 import dmagick.c.draw; 24 import dmagick.c.geometry; 25 import dmagick.c.type; 26 27 /// See_Also: $(CXREF draw, _AlignType) 28 public alias dmagick.c.draw.AlignType AlignType; 29 /// See_Also: $(CXREF draw, _ClipPathUnits) 30 public alias dmagick.c.draw.ClipPathUnits ClipPathUnits; 31 /// See_Also: $(CXREF draw, _DecorationType) 32 public alias dmagick.c.draw.DecorationType DecorationType; 33 /// See_Also: $(CXREF draw, _FillRule) 34 public alias dmagick.c.draw.FillRule FillRule; 35 /// See_Also: $(CXREF draw, _LineCap) 36 public alias dmagick.c.draw.LineCap LineCap; 37 /// See_Also: $(CXREF draw, _LineJoin) 38 public alias dmagick.c.draw.LineJoin LineJoin; 39 /// See_Also: $(CXREF draw, _PaintMethod) 40 public alias dmagick.c.draw.PaintMethod PaintMethod; 41 /// See_Also: $(CXREF type, _StretchType) 42 public alias dmagick.c.type.StretchType StretchType; 43 /// See_Also: $(CXREF type, _StyleType) 44 public alias dmagick.c.type.StyleType StyleType; 45 46 alias ptrdiff_t ssize_t; 47 48 /** 49 * Drawable provides a convenient interface for preparing vector, 50 * image, or text arguments. 51 */ 52 class DrawingContext 53 { 54 string operations; 55 56 /** 57 * Apply the drawing context to the image. 58 */ 59 void draw(Image image) 60 { 61 copyString(image.options.drawInfo.primitive, operations); 62 scope(exit) copyString(image.options.drawInfo.primitive, null); 63 64 DrawImage(image.imageRef, image.options.drawInfo); 65 66 DMagickException.throwException(&(image.imageRef.exception)); 67 } 68 69 /** 70 * Transforms the coordinate system by a 3x3 transformation matrix. 71 */ 72 void affine(AffineMatrix matrix) 73 { 74 operations ~= format(" affine %s,%s,%s,%s,%s,%s", 75 matrix.sx, matrix.rx, matrix.ry, matrix.sy, matrix.tx, matrix.ty); 76 } 77 78 /** 79 * Specify if the text and stroke should be antialiased. 80 */ 81 void antialias(bool antialias) 82 { 83 strokeAntialias = antialias; 84 textAntialias = antialias; 85 } 86 87 /** 88 * Draws an arc within a rectangle. 89 */ 90 void arc(size_t startX, size_t startY, size_t endX, size_t endY, double startDegrees, double endDegrees) 91 { 92 operations ~= format(" arc %s,%s %s,%s %s,%s", 93 startX, startY, endX, endY, startDegrees, endDegrees); 94 } 95 96 /** 97 * Draw a cubic Bezier curve. 98 * 99 * The arguments are pairs of points. At least 4 pairs must be specified. 100 * Each point xn, yn on the curve is associated with a control point 101 * cxn, cyn. The first point, x1, y1, is the starting point. The last 102 * point, xn, yn, is the ending point. Other point/control point pairs 103 * specify intermediate points on a polybezier curve. 104 */ 105 void bezier(size_t x1, size_t y1, size_t cx1, size_t cy1, 106 size_t cx2, size_t cy2, size_t x2, size_t y2, 107 size_t[] points ...) 108 in 109 { 110 assert ( points.length % 2 == 0, 111 "bezier needs an even number of arguments, "~ 112 "each x coordinate needs a coresponding y coordinate." ); 113 } 114 body 115 { 116 operations ~= format(" bezier %s,%s %s,%s %s,%s %s,%s", 117 x1, y1, cx1, cy1, cx2, cy2, x2, y2); 118 119 for( int i = 0; i < points.length; i+=2 ) 120 operations ~= format(" %s,%s", points[i], points[i+1]); 121 } 122 123 /** 124 * Set the image border color. The default is "#dfdfdf". 125 */ 126 void borderColor(const(Color) color) 127 { 128 operations ~= format(" border-color %s", color); 129 } 130 131 /** 132 * Defines a clip-path. Within the delegate, call other drawing 133 * primitive methods (rectangle, polygon, text, etc.) to define the 134 * clip-path. The union of all the primitives (excluding the effects 135 * of rendering methods such as stroke_width, etc.) is the clip-path. 136 * 137 * Params: 138 * path = The delegate that defines the clip-path using 139 * using the provided DrawingContext. 140 */ 141 void clipPath(void delegate(DrawingContext path) defineClipPath) 142 { 143 static size_t count; 144 145 DrawingContext path = new DrawingContext(); 146 defineClipPath(path); 147 148 operations ~= format(" push defs push clip-path path%s push graphic-context", count); 149 operations ~= path.operations; 150 operations ~= " pop graphic-context pop clip-path pop defs"; 151 operations ~= format(" clip-path url(#path%s)", count); 152 153 count++; 154 } 155 156 /** 157 * Specify how to determine if a point on the image is inside 158 * clipping region. 159 * 160 * See_Also: $(LINK2 http://www.w3.org/TR/SVG/painting.html#FillRuleProperty, 161 * the 'fill-rule' property) in the Scalable Vector Graphics (SVG) 162 * 1.1 Specification. 163 */ 164 void clipRule(FillRule rule) 165 { 166 if ( rule == FillRule.UndefinedRule ) 167 throw new DrawException("Undefined Fill Rule"); 168 169 operations ~= format(" clip-rule %s", to!(string)(rule)[0 .. $-4]); 170 } 171 172 /** 173 * Defines the coordinate space within the clipping region. 174 * 175 * See_Also: $(LINK2 http://www.w3.org/TR/SVG/masking.html#EstablishingANewClippingPath, 176 * Establishing a New Clipping Path) in the 177 * Scalable Vector Graphics (SVG) 1.1 Specification. 178 */ 179 void clipUnits(ClipPathUnits units) 180 { 181 if ( units == ClipPathUnits.UndefinedPathUnits ) 182 throw new DrawException("Undefined Path Unit"); 183 184 operations ~= format( " clip-units %s", toLower(to!(string)(units)) ); 185 } 186 187 unittest 188 { 189 auto dc = new DrawingContext(); 190 dc.clipUnits(ClipPathUnits.UserSpace); 191 192 assert(dc.operations == " clip-units userspace"); 193 } 194 195 /** 196 * Draw a circle. 197 * 198 * Params: 199 * xOrigin = The x coordinate for the center of the circle. 200 * yOrigin = The y coordinate for the center of the circle. 201 * xPerimeter = The x coordinate for a point on the perimeter of 202 * the circle. 203 * yPerimeter = The x coordinate for a point on the perimeter of 204 * the circle. 205 */ 206 void circle(size_t xOrigin, size_t yOrigin, size_t xPerimeter, size_t yPerimeter) 207 { 208 operations ~= format(" circle %s,%s %s,%s", 209 xOrigin, yOrigin, xPerimeter, yPerimeter); 210 } 211 212 ///ditto 213 void circle(size_t xOrigin, size_t yOrigin, size_t radius) 214 { 215 circle(xOrigin, yOrigin, xOrigin, yOrigin + radius); 216 } 217 218 /** 219 * Set color in image according to the specified PaintMethod constant. 220 * If you use the PaintMethod.FillToBorderMethod, assign the border 221 * color with the DrawingContext.borderColor property. 222 */ 223 void color(size_t x, size_t y, PaintMethod method) 224 { 225 if ( method == PaintMethod.UndefinedMethod ) 226 throw new DrawException("Undefined Paint Method"); 227 228 operations ~= format(" color %s,%s %s", x, y, to!(string)(method)[0 .. $-6]); 229 } 230 231 /** 232 * Composite filename/image with the receiver image. 233 * 234 * Params: 235 * xOffset = The x-offset of the composited image, 236 * measured from the upper-left corner 237 * of the image. 238 * yOffset = The y-offset of the composited image, 239 * measured from the upper-left corner 240 * of the image. 241 * width = Scale the composite image to this size. 242 * If value is 0, the composite image is not scaled. 243 * height = Scale the composite image to this size. 244 * If value is 0, the composite image is not scaled. 245 * filename = Filename of the mage to use in the 246 * composite operation. 247 * image = Image to use in the composite operation. 248 * compositeOp = The composite operation to use. 249 */ 250 void composite( 251 ssize_t xOffset, 252 ssize_t yOffset, 253 size_t width, 254 size_t height, 255 string filename, 256 CompositeOperator compositeOp = CompositeOperator.OverCompositeOp) 257 { 258 if ( compositeOp == CompositeOperator.UndefinedCompositeOp) 259 throw new DrawException("Undefined Composite Operator"); 260 261 operations ~= format(" image %s %s,%s %s,%s '%s'", 262 to!(string)(compositeOp)[0 .. $-11], xOffset, yOffset, width, height, filename); 263 } 264 265 ///ditto 266 void composite( 267 ssize_t xOffset, 268 ssize_t yOffset, 269 size_t width, 270 size_t height, 271 Image image, 272 CompositeOperator compositeOp = CompositeOperator.OverCompositeOp) 273 { 274 if ( image.filename !is null && image.filename.exists && !image.changed ) 275 { 276 composite(xOffset, yOffset, width, height, image.filename, compositeOp); 277 return; 278 } 279 280 string filename = saveTempFile(image); 281 282 composite(xOffset, yOffset, width, height, filename, compositeOp); 283 } 284 285 /** 286 * Specify text decoration. 287 */ 288 void decorate(DecorationType decoration) 289 { 290 operations ~= " decorate "; 291 292 final switch ( decoration ) 293 { 294 case DecorationType.NoDecoration: 295 operations ~= "none"; break; 296 case DecorationType.UnderlineDecoration: 297 operations ~= "underline"; break; 298 case DecorationType.OverlineDecoration: 299 operations ~= "overline"; break; 300 case DecorationType.LineThroughDecoration: 301 operations ~= "line-through"; break; 302 303 case DecorationType.UndefinedDecoration: 304 throw new DrawException("Undefined Decoration"); 305 } 306 } 307 308 /** 309 * Draw an ellipse. 310 * 311 * Params: 312 * xOrigin = The x coordinate of the ellipse. 313 * yOrigin = The y coordinate of the ellipse. 314 * width = The horizontal radii. 315 * height = The vertical radii. 316 * startDegrees = Where to start the ellipse. 317 * 0 degrees is at 3 o'clock. 318 * endDegrees = Whare to end the ellipse. 319 */ 320 void ellipse(size_t xOrigin, size_t yOrigin, size_t width, size_t height, double startDegrees, double endDegrees) 321 { 322 operations ~= format(" ellipse %s,%s %s,%s %s,%s", 323 xOrigin, yOrigin, width, height, startDegrees, endDegrees); 324 } 325 326 /** 327 * Specify the font encoding. 328 * Note: This specifies the character repertory (i.e., charset), 329 * and not the text encoding method (e.g., UTF-8, UTF-16, etc.). 330 */ 331 void encoding(FontEncoding encoding) 332 { 333 switch ( encoding ) 334 { 335 case FontEncoding.Latin1: 336 operations ~= " encoding Latin-1"; 337 break; 338 case FontEncoding.Latin2: 339 operations ~= " encoding Latin-2"; 340 break; 341 default: 342 operations ~= format(" encoding %s", to!(string)(encoding)); 343 break; 344 } 345 } 346 347 unittest 348 { 349 auto dc = new DrawingContext(); 350 dc.encoding(FontEncoding.Latin1); 351 352 assert(dc.operations == " encoding Latin-1"); 353 } 354 355 /** 356 * Color to use when filling drawn objects. 357 * The default is "black". 358 */ 359 void fill(const(Color) fillColor) 360 { 361 operations ~= format(" fill %s", fillColor); 362 } 363 364 ///ditto 365 alias fill fillColor; 366 367 /** 368 * Pattern to use when filling drawn objects. 369 * 370 * Within the delegate, call other drawing primitive methods (rectangle, 371 * polygon, text, etc.) to define the pattern. 372 */ 373 void fill(size_t x, size_t y, size_t width, size_t height, void delegate(DrawingContext path) pattern) 374 { 375 operations ~= format(" fill url(#%s)", definePattern(x, y, width, height, pattern)); 376 } 377 378 ///ditto 379 alias fill fillPattern; 380 381 /** 382 * The gradient to use when filling drawn objects. 383 */ 384 void fill(Gradient gradient) 385 { 386 operations ~= gradient.defineGradient(); 387 388 operations ~= format(" fill url(#%s)", gradient.id()); 389 } 390 391 /** 392 * Specify the fill opacity. 393 * 394 * Params: 395 * opacity = A number between 0 and 1. 396 */ 397 void fillOpacity(double opacity) 398 in 399 { 400 assert(opacity >= 0); 401 assert(opacity <= 1); 402 } 403 body 404 { 405 operations ~= format(" fill-opacity %s", opacity); 406 } 407 408 /** 409 * Specify how to determine if a point on the image is inside a shape. 410 * 411 * See_Also: $(LINK2 http://www.w3.org/TR/SVG/painting.html#FillRuleProperty, 412 * the 'fill-rule' property) in the Scalable Vector Graphics (SVG) 413 * 1.1 Specification. 414 */ 415 void fillRule(FillRule rule) 416 { 417 if ( rule == FillRule.UndefinedRule ) 418 throw new DrawException("Undefined Fill Rule"); 419 420 operations ~= format(" fill-rule %s", to!(string)(rule)[0 .. $-4]); 421 } 422 423 /** 424 * The _font name or filename. 425 * You can tag a _font to specify whether it is a Postscript, 426 * Truetype, or OPTION1 _font. For example, Arial.ttf is a 427 * Truetype _font, ps:helvetica is Postscript, and x:fixed is OPTION1. 428 * 429 * The _font name can be a complete filename such as 430 * "/mnt/windows/windows/fonts/Arial.ttf". The _font name can 431 * also be a fully qualified X font name such as 432 * "-urw-times-medium-i-normal--0-0-0-0-p-0-iso8859-13". 433 */ 434 void font(string font) 435 { 436 operations ~= format(" font '%s'", font); 437 } 438 439 /** 440 * Specify the font family, such as "arial" or "helvetica". 441 */ 442 void fontFamily(string family) 443 { 444 operations ~= format(" font-family '%s'", family); 445 } 446 447 /** 448 * Text rendering font point size 449 */ 450 void fontSize(double pointSize) 451 { 452 operations ~= format(" font-size %s", pointSize); 453 } 454 455 /** 456 * Specify the spacing between text characters. 457 */ 458 void fontStretch(StretchType type) 459 { 460 if ( type == StretchType.UndefinedStretch ) 461 throw new DrawException("Undefined Stretch type"); 462 463 operations ~= format(" font-stretch %s", to!(string)(type)[0 .. $-7]); 464 } 465 466 /** 467 * Specify the font style, i.e. italic, oblique, or normal. 468 */ 469 void fontStyle(StyleType type) 470 { 471 if ( type == StyleType.UndefinedStyle ) 472 throw new DrawException("Undefined Style type"); 473 474 operations ~= format(" font-style %s", to!(string)(type)[0 .. $-5]); 475 } 476 477 /** 478 * Specify the font weight. 479 * 480 * Eighter use the FontWeight enum or specify a number 481 * between 100 and 900. 482 */ 483 void fontWeight(size_t weight) 484 { 485 operations ~= format(" font-weight %s", weight); 486 } 487 488 ///ditto 489 void fontWeight(FontWeight weight) 490 { 491 if ( weight == FontWeight.Any ) 492 operations ~= " font-weight all"; 493 else 494 operations ~= format(" font-weight %s", weight); 495 } 496 497 /** 498 * Specify how the text is positioned. The default is NorthWestGravity. 499 */ 500 void gravity(GravityType type) 501 { 502 if ( type == GravityType.UndefinedGravity ) 503 throw new DrawException("Undefined Gravity type"); 504 505 operations ~= format(" gravity %s", to!(string)(type)[0 .. $-7]); 506 } 507 508 /** 509 * Modify the spacing between lines when text has multiple lines. 510 * 511 * If positive, inserts additional space between lines. If negative, 512 * removes space between lines. The amount of space inserted 513 * or removed depends on the font. 514 */ 515 void interlineSpacing(double spacing) 516 { 517 operations ~= format(" interline-spacing %s", spacing); 518 } 519 520 /** 521 * Modify the spacing between words in text. 522 * 523 * If positive, inserts additional space between words. If negative, 524 * removes space between words. The amount of space inserted 525 * or removed depends on the font. 526 */ 527 void interwordSpacing(double spacing) 528 { 529 operations ~= format(" interword-spacing %s", spacing); 530 } 531 532 /** 533 * Modify the spacing between letters in text. 534 * 535 * If positive, inserts additional space between letters. If negative, 536 * removes space between letters. The amount of space inserted or 537 * removed depends on the font but is usually measured in pixels. That 538 * is, the following call adds about 5 pixels between each letter. 539 */ 540 void kerning(double kerning) 541 { 542 operations ~= format(" kerning %s", kerning); 543 } 544 545 /** 546 * Draw a line from start to end. 547 */ 548 void line(size_t xStart, size_t yStart, size_t xEnd, size_t yEnd) 549 { 550 operations ~= format(" line %s,%s %s,%s", 551 xStart, yStart, xEnd, yEnd); 552 } 553 554 /** 555 * Make the image transparent according to the specified 556 * PaintMethod constant. 557 * 558 * If you use the PaintMethod.FillToBorderMethod, assign the border 559 * color with the DrawingContext.borderColor property. 560 */ 561 void matte(size_t x, size_t y, PaintMethod method) 562 { 563 if ( method == PaintMethod.UndefinedMethod ) 564 throw new DrawException("Undefined Paint Method"); 565 566 operations ~= format(" matte %s,%s %s", x, y, to!(string)(method)[0 .. $-6]); 567 } 568 569 /** 570 * Specify the fill and stroke opacities. 571 * 572 * Params: 573 * opacity = A number between 0 and 1. 574 */ 575 void opacity(double opacity) 576 in 577 { 578 assert(opacity >= 0); 579 assert(opacity <= 1); 580 } 581 body 582 { 583 operations ~= format(" opacity %s", opacity); 584 } 585 586 /** 587 * Draw using SVG-compatible path drawing commands. 588 * 589 * See_Also: "$(LINK2 http://www.w3.org/TR/SVG/paths.html, 590 * Paths)" in the Scalable Vector Graphics (SVG) 1.1 Specification. 591 */ 592 void path(string svgPath) 593 { 594 operations ~= " path "~svgPath; 595 } 596 597 /** 598 * Set the pixel at x,y to the fill color. 599 */ 600 void point(size_t x, size_t y) 601 { 602 operations ~= format(" point %s,%s", x,y); 603 } 604 605 /** 606 * Draw a polygon. 607 * 608 * The arguments are a sequence of 2 or more points. If the last 609 * point is not the same as the first, the polygon is closed by 610 * drawing a line from the last point to the first. 611 */ 612 void polygon(size_t[] points ...) 613 in 614 { 615 assert ( points.length % 2 == 0, 616 "polygon needs an even number of arguments, "~ 617 "each x coordinate needs a coresponding y coordinate." ); 618 } 619 body 620 { 621 operations ~= " polygon"; 622 623 for( int i = 0; i < points.length; i+=2 ) 624 operations ~= format(" %s,%s", points[i], points[i+1]); 625 } 626 627 /** 628 * Draw a polyline. Unlike a polygon, 629 * a polyline is not automatically closed. 630 */ 631 void polyline(size_t[] points ...) 632 in 633 { 634 assert ( points.length % 2 == 0, 635 "polyline needs an even number of arguments, "~ 636 "each x coordinate needs a coresponding y coordinate." ); 637 } 638 body 639 { 640 operations ~= " polyline"; 641 642 for( int i = 0; i < points.length; i+=2 ) 643 operations ~= format(" %s,%s", points[i], points[i+1]); 644 } 645 646 /** 647 * Restore the graphics context to the state it was in when 648 * push was called last. 649 */ 650 void pop() 651 { 652 operations ~= " pop graphic-context"; 653 } 654 655 /** 656 * Save the current state of the graphics context, including the 657 * attribute settings and the current set of primitives. Use the 658 * pop primitive to restore the state. 659 */ 660 void push() 661 { 662 operations ~= " push graphic-context"; 663 } 664 665 /** 666 * Draw a rectangle. 667 */ 668 void rectangle(size_t xStart, size_t yStart, size_t xEnd, size_t yEnd) 669 { 670 operations ~= format(" rectangle %s,%s %s,%s", 671 xStart, yStart, xEnd, yEnd); 672 } 673 674 /** 675 * Specify a rotation transformation to the coordinate space. 676 */ 677 void rotate(double angle) 678 { 679 operations ~= format(" rotate %s", angle); 680 } 681 682 /** 683 * Draw a rectangle with rounded corners. 684 * 685 * Params: 686 * xStart = The x coordinate for the upper left hand corner 687 * of the rectangle. 688 * yStart = The y coordinate for the upper left hand corner 689 * of the rectangle. 690 * xEnd = The x coordinate for the lower left hand corner 691 * of the rectangle. 692 * yEnd = The y coordinate for the lower left hand corner 693 * of the rectangle. 694 * cornerWidth = The width of the corner. 695 * cornerHeight = The height of the corner. 696 */ 697 void roundRectangle( 698 size_t xStart, size_t yStart, 699 size_t xEnd, size_t yEnd, 700 size_t cornerWidth, size_t cornerHeight) 701 { 702 operations ~= format(" roundRectangle %s,%s %s,%s %s,%s", 703 xStart, yStart, xEnd, yEnd, cornerWidth, cornerHeight); 704 } 705 706 /** 707 * Define a scale transformation to the coordinate space. 708 */ 709 void scale(double xScale, double yScale) 710 { 711 operations ~= format(" scale %s,%s", xScale, yScale); 712 } 713 714 /** 715 * Define a skew transformation along the x-axis. 716 * 717 * Params: 718 * angle = The amount of skew, in degrees. 719 */ 720 void skewX(double angle) 721 { 722 operations ~= format(" skewX %s", angle); 723 } 724 725 /** 726 * Define a skew transformation along the y-axis. 727 * 728 * Params: 729 * angle = The amount of skew, in degrees. 730 */ 731 void skewY(double angle) 732 { 733 operations ~= format(" skewY %s", angle); 734 } 735 736 /** 737 * Color to use when drawing object outlines. 738 */ 739 void stroke(const(Color) strokeColor) 740 { 741 operations ~= format(" stroke %s", strokeColor); 742 } 743 744 ///ditto 745 alias stroke strokeColor; 746 747 /** 748 * Pattern to use when filling drawn objects. 749 * 750 * Within the delegate, call other drawing primitive methods (rectangle, 751 * polygon, text, etc.) to define the pattern. 752 */ 753 void stroke(size_t x, size_t y, size_t width, size_t height, void delegate(DrawingContext path) pattern) 754 { 755 operations ~= format(" stroke url(#%s)", definePattern(x, y, width, height, pattern)); 756 } 757 758 ///ditto 759 alias stroke strokePattern; 760 761 /** 762 * The gradient to use when filling drawn objects. 763 */ 764 void stroke(Gradient gradient) 765 { 766 operations ~= gradient.defineGradient(); 767 768 operations ~= format(" stroke url(#%s)", gradient.id()); 769 } 770 771 /** 772 * Specify if the stroke should be antialiased. 773 */ 774 void strokeAntialias(bool antialias) 775 { 776 operations ~= format(" stroke-antialias %s", (antialias ? 1 : 0)); 777 } 778 779 /** 780 * Describe a pattern of dashes to be used when stroking paths. 781 * The arguments are a list of pixel widths of alternating 782 * dashes and gaps. 783 * 784 * The first argument is the width of the first dash. The second is 785 * the width of the gap following the first dash. The third argument 786 * is another dash width, followed by another gap width, etc. 787 */ 788 void strokeDashArray(const(double)[] dashArray ...) 789 { 790 if ( dashArray.length == 0 ) 791 { 792 operations ~= " stroke-dasharray none"; 793 } 794 else 795 { 796 operations ~= format(" stroke-dasharray %s", 797 array(joiner(map!"to!(string)(a)"(dashArray), ",")) ); 798 } 799 } 800 801 unittest 802 { 803 auto dc = new DrawingContext(); 804 dc.strokeDashArray(10, 10, 10); 805 806 assert(dc.operations == " stroke-dasharray 10,10,10"); 807 } 808 809 /** 810 * Specify the initial distance into the dash pattern. 811 */ 812 void strokeDashOffset(double offset) 813 { 814 operations ~= format(" stroke-dashoffset %s", offset); 815 } 816 817 /** 818 * Specify how the line ends should be drawn. 819 */ 820 void strokeLineCap(LineCap cap) 821 { 822 if ( cap == LineCap.UndefinedCap ) 823 throw new DrawException("Undefined Line cap."); 824 825 operations ~= format(" stroke-linecap %s", to!(string)(cap)[0 .. $-3]); 826 } 827 828 /** 829 * Specify how corners are drawn. 830 */ 831 void strokeLineJoin(LineJoin join) 832 { 833 if ( join == LineJoin.UndefinedJoin ) 834 throw new DrawException("Undefined Line join."); 835 836 operations ~= format(" stroke-linejoin %s", to!(string)(join)[0 .. $-4]); 837 } 838 839 /** 840 * Specify a constraint on the length of the "miter" 841 * formed by two lines meeting at an angle. If the angle 842 * if very sharp, the miter could be very long relative 843 * to the line thickness. The miter _limit is a _limit on 844 * the ratio of the miter length to the line width. 845 * The default is 4. 846 */ 847 void strokeMiterLimit(size_t limit) 848 { 849 operations ~= format(" stroke-miterlimit %s", limit); 850 } 851 852 /** 853 * Specify the stroke opacity. 854 * 855 * Params: 856 * opacity = A number between 0 and 1. 857 */ 858 void strokeOpacity(double opacity) 859 in 860 { 861 assert(opacity >= 0); 862 assert(opacity <= 1); 863 } 864 body 865 { 866 operations ~= format(" stroke-opacity %s", opacity); 867 } 868 869 /** 870 * Specify the stroke width in pixels. The default is 1. 871 */ 872 void strokeWidth(double width) 873 { 874 operations ~= format(" stroke-width %s", width); 875 } 876 877 /** 878 * Draw text at the location specified by (x,y). Use gravity to 879 * position text relative to (x, y). Specify the font appearance 880 * with the font, fontFamily, fontStretch, fontStyle, and fontWeight 881 * properties. Specify the text attributes with the textAlign, 882 * textAnchor, textAntialias, and textUndercolor properties. 883 * 884 * To include a '%' in the text, use '%%'. 885 * 886 * See_Also: Image.annotate for the image properties you can 887 * include in the string. 888 */ 889 void text(size_t x, size_t y, string text) 890 { 891 operations ~= format(" text %s,%s %s", x, y, escapeText(text)); 892 } 893 894 /** 895 * Align text relative to the starting point. 896 */ 897 void textAlign(AlignType type) 898 { 899 if ( type == AlignType.UndefinedAlign ) 900 throw new DrawException("Undefined Align type."); 901 902 operations ~= format(" text-align %s", to!(string)(type)[0 .. $-5]); 903 } 904 905 /** 906 * Specify if the text should be antialiased. 907 */ 908 void textAntialias(bool antialias) 909 { 910 operations ~= format(" text-antialias %s", (antialias ? 1 : 0)); 911 } 912 913 /** 914 * If set, causes the text to be drawn over a box of the specified color. 915 */ 916 void textUnderColor(Color color) 917 { 918 operations ~= format(" text-undercolor %s", color); 919 } 920 921 ///ditto 922 alias textUnderColor boxColor; 923 924 /** 925 * Specify a translation operation on the coordinate space. 926 */ 927 void translate(size_t x, size_t y) 928 { 929 operations ~= format(" translate %s,%s", x, y); 930 } 931 932 /** 933 * Generate to operations to define the pattern. 934 */ 935 private string definePattern(size_t x, size_t y, size_t width, size_t height, void delegate(DrawingContext path) pattern) 936 { 937 static size_t count; 938 count++; 939 940 DrawingContext patt = new DrawingContext(); 941 pattern(patt); 942 943 operations ~= format(" push defs push pattern patt%s %s,%s %s,%s push graphic-context", count, x, y, width, height); 944 operations ~= patt.operations; 945 operations ~= " pop graphic-context pop pattern pop defs"; 946 947 return format("patt%s", count); 948 } 949 950 /** 951 * Escape the text so it can be added to the operations string. 952 */ 953 private static string escapeText(string text) 954 { 955 string escaped; 956 957 //reserve text.lengt + 10% to avoid realocating when appending. 958 escaped.reserve(cast(size_t)(text.length * 0.1)); 959 escaped ~= '\"'; 960 961 foreach ( c; text ) 962 { 963 if ( c == '\"' || c == '\\' ) 964 escaped ~= '\\'; 965 966 escaped ~= c; 967 } 968 969 escaped ~= '\"'; 970 971 return escaped; 972 } 973 974 unittest 975 { 976 assert(escapeText(q{Hello world}) == q{"Hello world"}); 977 assert(escapeText(q{"Hello world"}) == q{"\"Hello world\""}); 978 assert(escapeText(q{"\"Hello world\""}) == q{"\"\\\"Hello world\\\"\""}); 979 } 980 981 /** 982 * Save the image in the temp directory and return the filename. 983 */ 984 private static string saveTempFile(Image image) 985 { 986 import std.datetime; 987 import std.path; 988 import std.process; 989 import core.runtime; 990 991 string tempPath; 992 string filename; 993 994 version(Windows) 995 { 996 tempPath = environment.get("TMP"); 997 if ( tempPath is null ) 998 tempPath = environment.get("TEMP"); 999 if ( tempPath is null ) 1000 tempPath = buildPath(environment.get("USERPROFILE"), "AppData/Local/Temp"); 1001 if ( tempPath is null || !tempPath.exists ) 1002 tempPath = buildPath(environment.get("WinDir"), "Temp"); 1003 } 1004 else 1005 { 1006 import core.sys.posix.stdio; 1007 1008 tempPath = environment.get("TMPDIR"); 1009 if ( tempPath is null ) 1010 tempPath = P_tmpdir; 1011 } 1012 1013 do 1014 { 1015 import std.conv:to; 1016 filename = buildPath(tempPath, "DMagick."~to!(string)(Clock.currTime().stdTime)); 1017 1018 if ( image.magick !is null && toLower(image.magick) != "canvas" ) 1019 filename ~= "."~image.magick; 1020 else 1021 filename ~= ".png"; 1022 } 1023 while ( filename.exists ); 1024 1025 image.write(filename); 1026 1027 return filename; 1028 } 1029 1030 unittest 1031 { 1032 auto image = new Image(Geometry(200, 200), new Color("blue")); 1033 string filename = saveTempFile(image); 1034 1035 assert(filename.exists); 1036 1037 remove(filename); 1038 } 1039 } 1040 1041 /** 1042 * This defines a Gradient used when drawing. 1043 * 1044 * One thing to remember it that the gradient is always drawn from the 1045 * top left corner of the image. And is repeated if it's smaller then the 1046 * image height or width. This mean that the gradient you see in the object 1047 * you are filling does not determine the stating point of the gradient 1048 * but is filled the part of the gradient that would be there when starting 1049 * at the top left corner of the image. 1050 */ 1051 struct Gradient 1052 { 1053 private static size_t count; 1054 private size_t currentCount; 1055 1056 //Is the id to use this gradient already set. 1057 private bool isDefined = false; 1058 1059 size_t size; 1060 GradientDirection direction; 1061 1062 Color startColor; 1063 Color endColor; 1064 1065 /** 1066 * Define a linear gradient. 1067 * 1068 * Params: 1069 * startColor = The starting Color. 1070 * endColor = The end Color. 1071 * size = The height or with of the gradient. 1072 * Direction = Determines is the gradient fades from top to bottom 1073 * or from left to right. 1074 */ 1075 this(Color startColor, Color endColor, size_t size, GradientDirection direction = GradientDirection.Vertical) 1076 { 1077 currentCount = count++; 1078 1079 this.size = size; 1080 this.direction = direction; 1081 this.startColor = startColor; 1082 this.endColor = endColor; 1083 } 1084 1085 /** 1086 * Generate the string used to define this gradient. 1087 */ 1088 private string defineGradient() 1089 { 1090 if ( isDefined ) 1091 return ""; 1092 1093 string operations = " push defs push defs push gradient"; 1094 1095 if ( direction == GradientDirection.Vertical ) 1096 { 1097 operations ~= format(" grad%s linear %s,%s %s,%s", 1098 currentCount, 0, 0, 0, size); 1099 } 1100 else 1101 { 1102 operations ~= format(" grad%s linear %s,%s %s,%s", 1103 currentCount, 0, 0, size, 0); 1104 } 1105 1106 operations ~= format(" stop-color %s %s", startColor, 0); 1107 operations ~= format(" stop-color %s %s", endColor, 1); 1108 1109 operations ~= " pop gradient pop defs"; 1110 1111 return operations; 1112 } 1113 1114 /** 1115 * If the gradient is defined, this id is neded to use it. 1116 */ 1117 private string id() 1118 { 1119 return format("grad%s", currentCount); 1120 } 1121 1122 } 1123 1124 /** 1125 * This enumeration lists specific character repertories (i.e., charsets), 1126 * and not text encoding methods (e.g., UTF-8, UTF-16, etc.). 1127 */ 1128 enum FontEncoding 1129 { 1130 AdobeCustom, /// 1131 AdobeExpert, ///ditto 1132 AdobeStandard, ///ditto 1133 AppleRoman, ///ditto 1134 BIG5, ///ditto 1135 GB2312, ///ditto 1136 Johab, ///ditto 1137 Latin1, ///ditto 1138 Latin2, ///ditto 1139 None, ///ditto 1140 SJIScode, ///ditto 1141 Symbol, ///ditto 1142 Unicode, ///ditto 1143 Wansung, ///ditto 1144 } 1145 1146 /** 1147 * The font weight can be specified as one of 100, 200, 300, 400, 500, 1148 * 600, 700, 800, or 900, or one of the following constants. 1149 */ 1150 enum FontWeight 1151 { 1152 Any, /// No weight specified. 1153 Normal, /// Normal weight, equivalent to 400. 1154 Bold, /// Bold. equivalent to 700. 1155 Bolder, /// Increases weight by 100. 1156 Lighter, /// Decreases weight by 100. 1157 } 1158 1159 /** 1160 * GradientDirection determines if the gradient fades from top to bottom 1161 * or from left to right. 1162 */ 1163 enum GradientDirection 1164 { 1165 Horizontal, /// Top to bottom. 1166 Vertical /// Left to right. 1167 } 1168 1169 /+ 1170 + ImageMagick's gradient implementation is a lot more limmiting then 1171 + it whoud seem. This was the first Gradient implementation based on: 1172 + http://www.imagemagick.org/script/magick-vector-graphics.php and 1173 + http://www.linux-nantes.org/~fmonnier/OCaml/MVG/ 1174 + But a look at the source of DrawImage reveals that only simple 1175 + linear gradients are supported. 1176 1177 /** 1178 * This defines a Gradient used when drawing. 1179 */ 1180 struct Gradient 1181 { 1182 private static size_t count; 1183 private size_t currentCount; 1184 1185 //Is the id to use this gradient already set. 1186 private bool isDefined = false; 1187 1188 GradientType type; 1189 //GradientUnits units; 1190 double x1, y1, x2, y2, radius; 1191 StopColor[] stopColors; 1192 1193 /** 1194 * Define a linear gradient. 1195 * 1196 * x1, y1, x2 and y2 define a gradient vector for the linear gradient. 1197 * This gradient vector provides starting and ending points onto which 1198 * the gradient stops are mapped. The values of x1, y1, x2 and y2 can 1199 * be either numbers or percentages. 1200 */ 1201 static Gradient linear(double x1, double y1, double x2, double y2) 1202 { 1203 Gradient gradient; 1204 1205 gradient.type = GradientType.LinearGradient; 1206 1207 gradient.currentCount = count++; 1208 gradient.x1 = x1; 1209 gradient.y1 = y1; 1210 gradient.x2 = x2; 1211 gradient.y2 = y2; 1212 1213 return gradient; 1214 } 1215 1216 /** 1217 * Define a radial gradient. 1218 * 1219 * cx, cy and r define the largest (i.e., outermost) circle for the 1220 * radial gradient. The gradient will be drawn such that the 100% 1221 * gradient stop is mapped to the perimeter of this largest 1222 * (i.e., outermost) circle. 1223 * 1224 * Params: 1225 * xCenter = x coordinate for the center of the circle. 1226 * yCenter = y coordinate for the center of the circle. 1227 * xFocal = x coordinate the focal point for the radial gradient. 1228 * The gradient will be drawn such that the 0% gradient 1229 * stop is mapped to (xFocal, yFocal). 1230 * yFocal = y coordinate the focal point 1231 * radius = The radius of the gradient. A value of zero will cause 1232 * the area to be painted as a single color using the 1233 * color and opacity of the last gradient stop. 1234 */ 1235 static Gradient radial(double xCenter, double yCenter, double xFocal, double yFocal, double radius) 1236 { 1237 Gradient gradient; 1238 1239 gradient.type = GradientType.RadialGradient; 1240 1241 gradient.currentCount = count++; 1242 gradient.x1 = xCenter; 1243 gradient.y1 = yCenter; 1244 gradient.x2 = xFocal; 1245 gradient.y2 = yFocal; 1246 gradient.radius = radius; 1247 1248 return gradient; 1249 } 1250 1251 /** 1252 * Define a radial gradient. 1253 * 1254 * The same as above but with the focal point at 1255 * the center of the circle. 1256 */ 1257 static Gradient radial(double xCenter, double yCenter, double radius) 1258 { 1259 return radial(xCenter, yCenter, xCenter, yCenter, radius); 1260 } 1261 1262 /** 1263 * Defines the coordinate system to use. 1264 */ 1265 Gradient gradientUnits(GradientUnits units) 1266 { 1267 this.units = units; 1268 1269 return this; 1270 } 1271 1272 /** 1273 * Define the color to use, and there offsets in the gradient. 1274 * 1275 * Params: 1276 * color = The color to use at this stop. 1277 * offset = For linear gradients, the offset attribute represents 1278 * a location along the gradient vector. For radial 1279 * gradients, it represents a percentage distance 1280 * from (fx,fy) to the edge of the outermost/largest circle. 1281 * offset should bwe between 0 and 1. 1282 */ 1283 Gradient stopColor(Color color, double offset) 1284 { 1285 stopColors ~= StopColor(color, offset); 1286 1287 return this; 1288 } 1289 1290 /** 1291 * Generate the string used to define this gradient. 1292 */ 1293 private string defineGradient() 1294 { 1295 if ( isDefined ) 1296 return ""; 1297 1298 string operations = " push defs"; 1299 1300 if ( type == GradientType.LinearGradient ) 1301 { 1302 operations ~= format(" push gradient grad%s linear %s,%s %s,%s", 1303 currentCount, x1, y1, x2, y2); 1304 } 1305 else 1306 { 1307 operations ~= format(" push gradient grad%s radial %s,%s %s,%s %s", 1308 currentCount, x1, y1, x2, y2, radius); 1309 } 1310 1311 if ( units != GradientUnits.Undefined ) 1312 operations ~= format(" gradient-units %s", units); 1313 1314 foreach ( stop; stopColors ) 1315 { 1316 operations ~= format(" stop-color %s %s", stop.color, stop.offset); 1317 } 1318 1319 operations ~= " pop gradient pop defs"; 1320 1321 return operations; 1322 } 1323 1324 /** 1325 * If the gradient is defined, this id is neded to use it. 1326 */ 1327 private string id() 1328 { 1329 return format("grad%s", currentCount); 1330 } 1331 1332 private struct StopColor 1333 { 1334 Color color; 1335 double offset; 1336 } 1337 } 1338 1339 /** 1340 * Defines the coordinate system to use for Gradients. 1341 */ 1342 enum GradientUnits : string 1343 { 1344 /** 1345 * No coordinate systewm defined. 1346 */ 1347 Undefined = "", 1348 1349 /** 1350 * The values supplied to Gradient represent values in the coordinate 1351 * system that results from taking the current user coordinate system 1352 * in place at the time when the gradient element is referenced. 1353 */ 1354 UserSpace = "userSpace", 1355 1356 /** 1357 * The user coordinate system for the values supplied to Gradient is 1358 * established using the bounding box of the element to which the 1359 * gradient is applied. 1360 */ 1361 UserSpaceOnUse = "userSpaceOnUse", 1362 1363 /** 1364 * The normal of the linear gradient is perpendicular to the gradient 1365 * vector in object bounding box space. When the object's bounding box 1366 * is not square, the gradient normal which is initially perpendicular 1367 * to the gradient vector within object bounding box space may render 1368 * non-perpendicular relative to the gradient vector in user space. 1369 * If the gradient vector is parallel to one of the axes of the bounding 1370 * box, the gradient normal will remain perpendicular. 1371 * This transformation is due to application of the non-uniform scaling 1372 * transformation from bounding box space to user space. 1373 */ 1374 ObjectBoundingBox = "objectBoundingBox", 1375 } 1376 +/