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 filename = buildPath(tempPath, "DMagick."~std.conv.to!(string)(Clock.currTime().stdTime)); 1016 1017 if ( image.magick !is null && toLower(image.magick) != "canvas" ) 1018 filename ~= "."~image.magick; 1019 else 1020 filename ~= ".png"; 1021 } 1022 while ( filename.exists ); 1023 1024 image.write(filename); 1025 1026 return filename; 1027 } 1028 1029 unittest 1030 { 1031 auto image = new Image(Geometry(200, 200), new Color("blue")); 1032 string filename = saveTempFile(image); 1033 1034 assert(filename.exists); 1035 1036 remove(filename); 1037 } 1038 } 1039 1040 /** 1041 * This defines a Gradient used when drawing. 1042 * 1043 * One thing to remember it that the gradient is always drawn from the 1044 * top left corner of the image. And is repeated if it's smaller then the 1045 * image height or width. This mean that the gradient you see in the object 1046 * you are filling does not determine the stating point of the gradient 1047 * but is filled the part of the gradient that would be there when starting 1048 * at the top left corner of the image. 1049 */ 1050 struct Gradient 1051 { 1052 private static size_t count; 1053 private size_t currentCount; 1054 1055 //Is the id to use this gradient already set. 1056 private bool isDefined = false; 1057 1058 size_t size; 1059 GradientDirection direction; 1060 1061 Color startColor; 1062 Color endColor; 1063 1064 /** 1065 * Define a linear gradient. 1066 * 1067 * Params: 1068 * startColor = The starting Color. 1069 * endColor = The end Color. 1070 * size = The height or with of the gradient. 1071 * Direction = Determines is the gradient fades from top to bottom 1072 * or from left to right. 1073 */ 1074 this(Color startColor, Color endColor, size_t size, GradientDirection direction = GradientDirection.Vertical) 1075 { 1076 currentCount = count++; 1077 1078 this.size = size; 1079 this.direction = direction; 1080 this.startColor = startColor; 1081 this.endColor = endColor; 1082 } 1083 1084 /** 1085 * Generate the string used to define this gradient. 1086 */ 1087 private string defineGradient() 1088 { 1089 if ( isDefined ) 1090 return ""; 1091 1092 string operations = " push defs push defs push gradient"; 1093 1094 if ( direction == GradientDirection.Vertical ) 1095 { 1096 operations ~= format(" grad%s linear %s,%s %s,%s", 1097 currentCount, 0, 0, 0, size); 1098 } 1099 else 1100 { 1101 operations ~= format(" grad%s linear %s,%s %s,%s", 1102 currentCount, 0, 0, size, 0); 1103 } 1104 1105 operations ~= format(" stop-color %s %s", startColor, 0); 1106 operations ~= format(" stop-color %s %s", endColor, 1); 1107 1108 operations ~= " pop gradient pop defs"; 1109 1110 return operations; 1111 } 1112 1113 /** 1114 * If the gradient is defined, this id is neded to use it. 1115 */ 1116 private string id() 1117 { 1118 return format("grad%s", currentCount); 1119 } 1120 1121 } 1122 1123 /** 1124 * This enumeration lists specific character repertories (i.e., charsets), 1125 * and not text encoding methods (e.g., UTF-8, UTF-16, etc.). 1126 */ 1127 enum FontEncoding 1128 { 1129 AdobeCustom, /// 1130 AdobeExpert, ///ditto 1131 AdobeStandard, ///ditto 1132 AppleRoman, ///ditto 1133 BIG5, ///ditto 1134 GB2312, ///ditto 1135 Johab, ///ditto 1136 Latin1, ///ditto 1137 Latin2, ///ditto 1138 None, ///ditto 1139 SJIScode, ///ditto 1140 Symbol, ///ditto 1141 Unicode, ///ditto 1142 Wansung, ///ditto 1143 } 1144 1145 /** 1146 * The font weight can be specified as one of 100, 200, 300, 400, 500, 1147 * 600, 700, 800, or 900, or one of the following constants. 1148 */ 1149 enum FontWeight 1150 { 1151 Any, /// No weight specified. 1152 Normal, /// Normal weight, equivalent to 400. 1153 Bold, /// Bold. equivalent to 700. 1154 Bolder, /// Increases weight by 100. 1155 Lighter, /// Decreases weight by 100. 1156 } 1157 1158 /** 1159 * GradientDirection determines if the gradient fades from top to bottom 1160 * or from left to right. 1161 */ 1162 enum GradientDirection 1163 { 1164 Horizontal, /// Top to bottom. 1165 Vertical /// Left to right. 1166 } 1167 1168 /+ 1169 + ImageMagick's gradient implementation is a lot more limmiting then 1170 + it whoud seem. This was the first Gradient implementation based on: 1171 + http://www.imagemagick.org/script/magick-vector-graphics.php and 1172 + http://www.linux-nantes.org/~fmonnier/OCaml/MVG/ 1173 + But a look at the source of DrawImage reveals that only simple 1174 + linear gradients are supported. 1175 1176 /** 1177 * This defines a Gradient used when drawing. 1178 */ 1179 struct Gradient 1180 { 1181 private static size_t count; 1182 private size_t currentCount; 1183 1184 //Is the id to use this gradient already set. 1185 private bool isDefined = false; 1186 1187 GradientType type; 1188 //GradientUnits units; 1189 double x1, y1, x2, y2, radius; 1190 StopColor[] stopColors; 1191 1192 /** 1193 * Define a linear gradient. 1194 * 1195 * x1, y1, x2 and y2 define a gradient vector for the linear gradient. 1196 * This gradient vector provides starting and ending points onto which 1197 * the gradient stops are mapped. The values of x1, y1, x2 and y2 can 1198 * be either numbers or percentages. 1199 */ 1200 static Gradient linear(double x1, double y1, double x2, double y2) 1201 { 1202 Gradient gradient; 1203 1204 gradient.type = GradientType.LinearGradient; 1205 1206 gradient.currentCount = count++; 1207 gradient.x1 = x1; 1208 gradient.y1 = y1; 1209 gradient.x2 = x2; 1210 gradient.y2 = y2; 1211 1212 return gradient; 1213 } 1214 1215 /** 1216 * Define a radial gradient. 1217 * 1218 * cx, cy and r define the largest (i.e., outermost) circle for the 1219 * radial gradient. The gradient will be drawn such that the 100% 1220 * gradient stop is mapped to the perimeter of this largest 1221 * (i.e., outermost) circle. 1222 * 1223 * Params: 1224 * xCenter = x coordinate for the center of the circle. 1225 * yCenter = y coordinate for the center of the circle. 1226 * xFocal = x coordinate the focal point for the radial gradient. 1227 * The gradient will be drawn such that the 0% gradient 1228 * stop is mapped to (xFocal, yFocal). 1229 * yFocal = y coordinate the focal point 1230 * radius = The radius of the gradient. A value of zero will cause 1231 * the area to be painted as a single color using the 1232 * color and opacity of the last gradient stop. 1233 */ 1234 static Gradient radial(double xCenter, double yCenter, double xFocal, double yFocal, double radius) 1235 { 1236 Gradient gradient; 1237 1238 gradient.type = GradientType.RadialGradient; 1239 1240 gradient.currentCount = count++; 1241 gradient.x1 = xCenter; 1242 gradient.y1 = yCenter; 1243 gradient.x2 = xFocal; 1244 gradient.y2 = yFocal; 1245 gradient.radius = radius; 1246 1247 return gradient; 1248 } 1249 1250 /** 1251 * Define a radial gradient. 1252 * 1253 * The same as above but with the focal point at 1254 * the center of the circle. 1255 */ 1256 static Gradient radial(double xCenter, double yCenter, double radius) 1257 { 1258 return radial(xCenter, yCenter, xCenter, yCenter, radius); 1259 } 1260 1261 /** 1262 * Defines the coordinate system to use. 1263 */ 1264 Gradient gradientUnits(GradientUnits units) 1265 { 1266 this.units = units; 1267 1268 return this; 1269 } 1270 1271 /** 1272 * Define the color to use, and there offsets in the gradient. 1273 * 1274 * Params: 1275 * color = The color to use at this stop. 1276 * offset = For linear gradients, the offset attribute represents 1277 * a location along the gradient vector. For radial 1278 * gradients, it represents a percentage distance 1279 * from (fx,fy) to the edge of the outermost/largest circle. 1280 * offset should bwe between 0 and 1. 1281 */ 1282 Gradient stopColor(Color color, double offset) 1283 { 1284 stopColors ~= StopColor(color, offset); 1285 1286 return this; 1287 } 1288 1289 /** 1290 * Generate the string used to define this gradient. 1291 */ 1292 private string defineGradient() 1293 { 1294 if ( isDefined ) 1295 return ""; 1296 1297 string operations = " push defs"; 1298 1299 if ( type == GradientType.LinearGradient ) 1300 { 1301 operations ~= format(" push gradient grad%s linear %s,%s %s,%s", 1302 currentCount, x1, y1, x2, y2); 1303 } 1304 else 1305 { 1306 operations ~= format(" push gradient grad%s radial %s,%s %s,%s %s", 1307 currentCount, x1, y1, x2, y2, radius); 1308 } 1309 1310 if ( units != GradientUnits.Undefined ) 1311 operations ~= format(" gradient-units %s", units); 1312 1313 foreach ( stop; stopColors ) 1314 { 1315 operations ~= format(" stop-color %s %s", stop.color, stop.offset); 1316 } 1317 1318 operations ~= " pop gradient pop defs"; 1319 1320 return operations; 1321 } 1322 1323 /** 1324 * If the gradient is defined, this id is neded to use it. 1325 */ 1326 private string id() 1327 { 1328 return format("grad%s", currentCount); 1329 } 1330 1331 private struct StopColor 1332 { 1333 Color color; 1334 double offset; 1335 } 1336 } 1337 1338 /** 1339 * Defines the coordinate system to use for Gradients. 1340 */ 1341 enum GradientUnits : string 1342 { 1343 /** 1344 * No coordinate systewm defined. 1345 */ 1346 Undefined = "", 1347 1348 /** 1349 * The values supplied to Gradient represent values in the coordinate 1350 * system that results from taking the current user coordinate system 1351 * in place at the time when the gradient element is referenced. 1352 */ 1353 UserSpace = "userSpace", 1354 1355 /** 1356 * The user coordinate system for the values supplied to Gradient is 1357 * established using the bounding box of the element to which the 1358 * gradient is applied. 1359 */ 1360 UserSpaceOnUse = "userSpaceOnUse", 1361 1362 /** 1363 * The normal of the linear gradient is perpendicular to the gradient 1364 * vector in object bounding box space. When the object's bounding box 1365 * is not square, the gradient normal which is initially perpendicular 1366 * to the gradient vector within object bounding box space may render 1367 * non-perpendicular relative to the gradient vector in user space. 1368 * If the gradient vector is parallel to one of the axes of the bounding 1369 * box, the gradient normal will remain perpendicular. 1370 * This transformation is due to application of the non-uniform scaling 1371 * transformation from bounding box space to user space. 1372 */ 1373 ObjectBoundingBox = "objectBoundingBox", 1374 } 1375 +/