New Features in Maple 2018 - ImageTools Draw - Maplesoft

What's New in Maple 2018

ImageTools Draw




Maple 2018 includes a new set of drawing tools so you can annotate images, generate diagrams, and more, all programmatically. These tools are in addition to the interactive drawing tools already available with the Drawing Canvas.  

ImageTools:-Draw is a subpackage of ImageTools that provides primitives for drawing into an ImageTools:-Image. All primitives work in terms of continuous mathematical coordinates, not discrete pixels, and are rendered using anti-aliasing.


Coordinate System

  • ImageTools:-Draw uses a different coordinate system than that of the underlying ImageTools:-Image. An Image is an HxW, HxWx3, or HxWx4 Array of double precision (64-bit) hardware floating point values, each representing a pixel. The top-left pixel of the image has the coordinates [1,1] and the bottom-right pixel is at [H,W]. Notice that this is both upside-down compared to Cartesian coordinates (the lowest y value is at the top), and also that the order of coordinates is reversed (the coordinates are of the form [y,x]). This was done to correspond with the conventions of indexing arrays and matrices, where indexing starts at the top, and the row is specified first.
  • The coordinate system used by ImageTools:-Draw is that of the upper-right quadrant of the Cartesian coordinate system. Furthermore, the coordinates refer to pixel boundaries, not the pixels themselves. For an image stored in an HxW Array, ImageTools:-Draw coordinates are of the form [x,y], where 0 ⋜ x ⋜ W and 0 ⋜ y ⋜ H. The bottom-left corner has coordinates [0,0] and the top-right corner [W,H].
  • The coordinates specified in ImageTools:-Draw commands are not restricted to integer values. A point, such as the endpoint of a line, can be specified to be anywhere within the range of valid coordinates. Since all primitives drawn by ImageTools:-Draw are rendered using anti-aliasing, sub-pixel-sized changes in the positions of points are actually visible. The images below were generated at 20 pixels width and 15 pixels height, magnified by a factor of 16, and then augmented with red crosshairs to show the mathematical endpoints of the lines.

Line from (2.0,2.0) to (18.0,13.0), thickness=1:Image

Line from (2.5,2.5) to (17.5,12.5), thickness=1:Image


The correspondence between point coordinates [x,y] and Array indices [r,c] is given by:

c = x + 0.5

r = (H + 1) - (y + 0.5)


Primitives

After first creating an image using ImageTools:-Create, or loading an image with ImageTools:-Read, drawing operations can be performed on it using a number of primitives (functions that draw one kind of object). The first argument to each primitive is the image itself, as returned by Create or Read. 

Each primitive is a member of the ImageTools:-Draw package, and can be referenced by prefixing the primitive name with the package name (e.g., ImageTools:-Draw:-Poly), or using the bare primitive name in a context in which ImageTools:-Draw is in scope: after a call to with(ImageTools:-Draw), within a procedure having a uses ImageTools:-Draw clause, or within a use ImageTools:-Draw in ... end statement. 

In addition to the drawing primitives any other command from the ImageTools package can be applied to the image at any time, so one can combine drawing operations with other image manipulation techniques.  


The Poly Primitive

The Poly primitive draws a multi-segment line (polyline) or closed shape (polygon) given a list, Array, or Matrix of points.  

>  with(ImageTools):
>  with(ImageTools:-Draw):

Thin polyline. 

>  img := Create(240,320,channels=3,background=white):
>  Poly(img,[[32,32],[100,180],[220,60],[288,208]],color=0.25);
>  Embed(img);

Image 

Filled polygon. 

>  img := Create(240,320,channels=3,background=white):
>  Poly(img,[[32,32],[100,180],[288,208],[220,60],[126,86],[32,32]],
   color=0.25,thickness=1,round=true,pattern="solid",
   fill_color=[0.9,1,0.8],fill_pattern="solid");
>  Embed(img);

Image 

Very thin and thick polylines. 

>  img := Create(240,320,channels=3,background=white):
>  Poly(img,[[32,32],[68,180],[188,60],[256,208]],
   color=0.25,thickness=0.5,round=true);
>  Poly(img,[[64,32],[100,180],[220,60],[288,208]],
   color=0.25,thickness=4,round=true);
>  Embed(img);

Image 

Round vs. square segment ends, and color transparency. 

>  img := Create(240,320,channels=3,background=white):
>  Poly(img,[[32,32],[68,180],[188,60],[256,208]],
   color=[1,0,0,0.9],thickness=8,round=false);
>  Poly(img,[[64,32],[100,180],[220,60],[288,208]],
   color=[0,0.66,0,0.9],thickness=8,round=true);
>  Embed(img);

Image 

Polyline pattern examples. 

>  img := Create(100,200,channels=3,background=white):
>  Poly(img,[[5,10],[195,12.5],[195,25]],color=0,thickness=2,pattern="dash");
>  Poly(img,[[195,25],[195,25],[20,25]],color=0,thickness=2,pattern="dot");
>  Poly(img,[[20,25],[50,40],[195,40],[195,55]],color=0,thickness=2,pattern="dashdot");
>  Poly(img,[[195,55],[5,55],[5,10]],color=0,thickness=2,pattern="dashdotdot");
>  Embed(img);

Image 

Fill pattern examples. 

>  img := Create(240,180,channels=3,background=white):
>  Poly(img,[[5,5],[55,5],[55,55],[5,55],[5,5]],
       color=0,fill_color=[0.9,1,0.8],fill_pattern="horizontal");
>  Poly(img,[[65,5],[115,5],[115,55],[65,55],[65,5]],
       color=0,fill_color=[0.9,1,0.8],fill_pattern="vertical");
>  Poly(img,[[125,5],[175,5],[175,55],[125,55],[125,5]],
       color=0,fill_color=[0.9,1,0.8],fill_pattern="cross");
>  Poly(img,[[5,65],[55,65],[55,115],[5,115],[5,65]],
       color=0,fill_color=[0.9,1,0.8],fill_pattern="forward");
>  Poly(img,[[65,65],[115,65],[115,115],[65,115],[65,65]],
       color=0,fill_color=[0.9,1,0.8],fill_pattern="backward");
>  Poly(img,[[125,65],[175,65],[175,115],[125,115],[125,65]],
       color=0,fill_color=[0.9,1,0.8],fill_pattern="diagonalCross");
>  Poly(img,[[5,125],[55,125],[55,175],[5,175],[5,125]],
       color=0,fill_color=[0.9,1,0.8],fill_pattern="horizontalCylinder");
>  Poly(img,[[65,125],[115,125],[115,175],[65,175],[65,125]],
       color=0,fill_color=[0.9,1,0.8],fill_pattern="verticalCylinder");
>  Poly(img,[[125,125],[175,125],[175,175],[125,175],[125,125]],
       color=0,fill_color=[0.9,1,0.8],fill_pattern="sphere");
>  Embed(img);

Image 


The Line Primitive 

The Line primitive draws a straight line between two specified points. The same result can be achieved using the Poly primitive with a list of two points, except that the line primitive also has the ability to draw tapered lines. 

>  with(ImageTools):
>  with(ImageTools:-Draw):

Lines of different thicknesses. 

>  img := Create(240,320,channels=3,background=white):
>  Line(img,32,32,288,144,color=0.25,thickness=0.5);
>  Line(img,32,64,288,176,color=0.25,thickness=1);
>  Line(img,32,96,288,208,color=0.25,thickness=3);
>  Embed(img);

Image 

Tapered lines. 

>  img := Create(240,320,channels=3,background=white):
>  Line(img,32,32,288,144,color=0.25,thickness=[0.5,5]);
>  Line(img,32,96,288,208,color=0.25,thickness=[15,5]);
>  Embed(img);

Image 

Round vs. square line ends. 

>  img := Create(240,320,channels=3,background=white):
>  Line(img,32,32,288,144,color=0.25,thickness=10,round=true);
>  Line(img,32,96,288,208,color=0.25,thickness=10,round=false);
>  Embed(img);

Image 

Supporting Functions for the Examples 

Draw a red cross hair centered at (xscale,yscale). 

>  crossHair := proc( img :: Array, x :: numeric, y :: numeric, scale :: numeric )
  uses ImageTools:-Draw;
  Line(img,(x-1.5)*scale,y*scale,(x+1.5)*scale,y*scale,
       color="red",thickness=1.5);
  Line(img,x*scale,(y-1.5)*scale,x*scale,(y+1.5)*scale,
       color="red",thickness=1.5)
end:

Draw a background grid with specified interval. 

>  gridFill := proc( img :: Array, interval :: numeric )
  local x, y, w, h;
  uses ImageTools:-Draw;
  w, h := Width(img), Height(img);
  for x from interval by interval while x < w do
      Line(img,x,0,x,h,color=[0,1,1,0.25])
  od;
  for y from interval by interval while y < h do
      Line(img,0,y,w,y,color=[0,1,1,0.25])
  od
end:

Zoomed image of anti-aliased line with integer endpoints. 

>  img := Create(15,20,channels=3,background=white):
>  Line(img,2,2,18,13,color=0);
>  img := Scale(img,16,method=nearest):
>  gridFill(img,16);
>  crossHair(img,2,2,16);
>  crossHair(img,18,13,16);
>  Embed(img);

Image 


Zoomed image of anti-aliased line with non-integer endpoints. 

>  img := Create(15,20,channels=3,background=white):
>  Line(img,2.5,2.5,17.5,12.5,color=0);
>  img := Scale(img,16,method=nearest):
>  gridFill(img,16);
>  crossHair(img,2.5,2.5,16);
>  crossHair(img,17.5,12.5,16);
>  Embed(img);

Image 

Line pattern examples. 

>  img := Create(260,320,channels=3,background=white):
>  Line(img,32,32,288,144,pattern="dash");
>  Line(img,32,64,288,176,pattern="dot");
>  Line(img,32,96,288,208,pattern="dashdot");
>  Line(img,32,128,288,240,pattern="dashdotdot");
>  Embed(img);

Image 


The SolidRectangle Primitive

The SolidRectangle primitive draws a rectangle whose boundaries fall (perceptually) on the mathematical rectangle specified by two opposing corners. This differs from using the Poly primitive with matching line color and solid fill pattern in that with the latter, half of the thickness of the enclosing line segments falls outside of the boundaries.  

>  with(ImageTools):
>  with(ImageTools:-Draw):

Solid gray rectangle. 

>  img := Create(240,320,channels=3,background=white):
>  SolidRectangle(img,32,32,288,208,color=0.5);
>  Embed(img);

Image 

Supporting Functions for the Examples 

Draw a red cross hair cantered at (xscale,yscale). 

>  crossHair := proc( img :: Array, x :: numeric, y :: numeric, scale :: numeric )
  uses ImageTools:-Draw;
  Line(img,(x-1.5)*scale,y*scale,(x+1.5)*scale,y*scale,
       color="red",thickness=1.5);
  Line(img,x*scale,(y-1.5)*scale,x*scale,(y+1.5)*scale,
       color="red",thickness=1.5)
end:

Draw a background grid with specified interval. 

>  gridFill := proc( img :: Array, interval :: numeric )
  local x, y, w, h;
  uses ImageTools:-Draw;
  w, h := Width(img), Height(img);
  for x from interval by interval while x < w do
      Line(img,x,0,x,h,color=[0,1,1,0.25])
  od;
  for y from interval by interval while y < h do
      Line(img,0,y,w,y,color=[0,1,1,0.25])
  od
end:

The images below were generated at 20 pixels width and 15 pixels height, magnified by a factor of 16, and then augmented with red crosshairs to show the mathematical corners of the rectangles. 

Zoomed solid gray rectangle with integer bounds. 

>  img := Create(15,20,channels=3,background=white):
>  SolidRectangle(img,2,2,18,13,color=0.5);
>  img := Scale(img,16,method=nearest):
> gridFill(img,16);
>  crossHair(img,2,2,16);
>  crossHair(img,18,13,16);
>  Embed(img);

Image 

Zoomed solid gray rectangle with some non-integer bounds. 

>  img := Create(15,20,channels=3,background=white):
>  SolidRectangle(img,2.5,2,18,12.7,color=0.5);
>  img := Scale(img,16,method=nearest):
>  gridFill(img,16);
>  crossHair(img,2.5,2,16);
>  crossHair(img,18,12.7,16);
>  Embed(img);

Image 


The Circle and SolidCircle Primitives

The Circle and SolidCircle Primitives draw either an outlined or solid circle around a specified center point and with a specified diameter. Like the SolidRectangle primitive, the circle is perceptually entirely within the bounds specified by the center point and diameter.  

>  with(ImageTools):
>  with(ImageTools:-Draw):

Circle with thick edge. 

>  img := Create(240,320,channels=3,background=white):
>  Circle(img,160,120,240-64,color=0.5,thickness=3);
>  Embed(img);

Image 

>  Write("circ1.png",img);
6056
 

Solid gray circle. 

>  img := Create(240,320,channels=3,background=white):
>  SolidCircle(img,160,120,240-64,color=0.5);
>  Embed(img);

Image 

Supporting Functions for the Examples 

Draw a red cross hair centered at (xscale,yscale). 

>  crossHair := proc( img :: Array, x :: numeric, y :: numeric, scale :: numeric )
  uses ImageTools:-Draw;
  Line(img,(x-1.5)*scale,y*scale,(x+1.5)*scale,y*scale,
       color="red",thickness=1.5);
  Line(img,x*scale,(y-1.5)*scale,x*scale,(y+1.5)*scale,
       color="red",thickness=1.5)
end:

Draw a background grid with specified interval. 

>  gridFill := proc( img :: Array, interval :: numeric )
  local x, y, w, h;
  uses ImageTools:-Draw;
  w, h := Width(img), Height(img);
  for x from interval by interval while x < w do
      Line(img,x,0,x,h,color=[0,1,1,0.25])
  od;
  for y from interval by interval while y < h do
      Line(img,0,y,w,y,color=[0,1,1,0.25])
  od
end:

Unzoomed image to hold all four of the following circles. 

>  img4 := Create(62,80,channels=3,background=white):

The images below will be generated at 20 pixels width and 15 pixels height, magnified by a factor of 16, and then augmented with red crosshairs to show the mathematical corners of the circles.  

Zoomed circle with thin edge. 

>  img := Create(15,20,channels=3,background=white):
>  Circle(img,10,7,10,color=0.5,thickness=1);
>  img4[9..23,11..30] := img: # copy into collage
>  img := Scale(img,16,method=nearest):
>  gridFill(img,16);
>  crossHair(img,10,7,16);
>  crossHair(img,5,2,16);
>  crossHair(img,15,12,16);
>  Embed(img);

Image 

Zoomed circle with thick edge. 

>  img := Create(15,20,channels=3,background=white):
>  Circle(img,10,7,10,color=0.5,thickness=2.5);
>  img4[9..23,51..70] := img: # copy into collage
>  img := Scale(img,16,method=nearest):
>  gridFill(img,16);
>  crossHair(img,10,7,16);
>  crossHair(img,5,2,16);
>  crossHair(img,15,12,16);
>  Embed(img);

Image 

Both of these two circles above have the same center coordinates and same diameter. Since the center coordinates are integers, and the diameter is an integer multiple of two, the left, right, top, and bottom edges coincide exactly with pixel boundaries. In the second circle, notice that the edge pixels are identical to those of the first circle; the additional edge thickness has not made the circle any larger.  

Zoomed solid circle with integer center and bounds.

>  img := Create(15,20,channels=3,background=white):
>  SolidCircle(img,10,7,10,color=0.5);
>  img4[40..54,11..30] := img: # copy into collage
>  img := Scale(img,16,method=nearest):
>  gridFill(img,16);
>  crossHair(img,10,7,16);
>  crossHair(img,5,2,16);
>  crossHair(img,15,12,16);
>  Embed(img);

Image 

Zoomed solid circle with non-integer center and bounds. 

>  img := Create(15,20,channels=3,background=white):
>  SolidCircle(img,10.3,7.4,11,color=0.5);
>  img4[40..54,51..70] := img: # copy into collage
>  img := Scale(img,16,method=nearest):
>  gridFill(img,16);
>  crossHair(img,10.3,7.4,16);
>  crossHair(img,4.8,1.9,16);
>  crossHair(img,15.8,12.9,16);
>  Embed(img);

Image 

The first solid circle above has the same coordinates and diameter as the first open circle, and hence has the same alignment and identical edge pixels. The second solid circle above has non-integer center coordinates and a non-even diameter, resulting in some edges that don't fall on pixel boundaries.  

Write collage containing the previous four circles at original size. The following image shows each of the four circles above, rendered at their original size. 

>  Embed(img4);

Image 


The Text Primitive and TextSize Function

Images can be annotated with text using the Text primitive. The text produced is based on the Hershey vector fonts, first published by Dr. Allen V. Hershey in 1967. These fonts are platform and resolution independent, and are rendered by ImageTools:-Draw using anti-aliasing. They can be rotated, and scaled independently in width and height. In addition to the usual ASCII characters, the Hershey fonts contain Greek and Cyrillic glyphs, a subset of Japanese characters, and a large collection of symbols.  

>  with(ImageTools):
>  with(ImageTools:-Draw):

Collage of text samples. 

>  img := Create(240,320,channels=3,background=white):
>  Text(img,5,235,"Text Samples",position="SE",font=16,font_size=20,weight=1.5,color="maroon"):
>  Text(img,10,145,"Roman Plain",position="E",font=14,font_size=20,weight=1.2,color=[0,0.5,0],rotation=15,degrees):
>  Text(img,5,110,"Italic Triplex",position="E",font=10,font_size=20,weight=1.5,color=[0,0.5,0.5]):
>  Text(img,10,80,"Script Complex",position="E",font=17,font_size=20,weight=1.5,color="blue",rotation=-Pi/12):
>  Text(img,310,120,"Vertical Text",position="N",font=1,font_size=25,color=[0.5,0,0.5],rotation=90,degrees):
>  Text(img,270,135,"Kirillica",position="N",font=0,font_size=20,color=[0.25,0.25,0.25],rotation=Pi/2):
>  Text(img,5,5,"WIDE",position="NE",font=11,font_size=[45,20],weight=1.2,color=[0.5,0.5,0]):
>  Text(img,195,5,"NARROW",position="NE",font=11,font_size=[10,20],weight=1.5,color=[0.75,0.5,0]):
>  Embed(img);

Image 

Sample of each font 10. 

>  img := Create(35,320,channels=3,background=white):
>  Text(img,5,17,"ABCD abcd 0123 $&?*",position="E",
    font=10,font_size=17,weight=1.2,color=0):
>  Embed(img);

Image 

The font_size argument. 

>  img := Create(120,400,channels=3,background=white):
>  Text(img,10,100,"font_size=20",position="E",font=11,
   font_size=20,weight=1.5,color=0):
>  Text(img,10,60,"font_size=[14,20]",position="E",font=11,
   font_size=[14,20],weight=1.5,color=0):
>  Text(img,10,20,"font_size=[28,20]",position="E",font=11,
   font_size=[28,20],weight=1.5,color=0):
>  Embed(img);

Image 

The rotation argument. 

>  img := Create(400,400,channels=3,background=[0.1,0.1,0.1]):
>  for r from 0 to 359 by 30 do
  Text(img,200,200,sprintf("--rotation=%3d\e718",r),position="E",
       font=11,font_size=15,weight=1.10,rotation=r,degrees=true,
       color=("HSV",[r/360,1,1]))
od:
>  Embed(img);

Image 


Effect of the weight argument with different font sizes. 

>  img := Create(380,620,channels=3,background=white):
>  Text(img,10,360,"font_size=12",position="E",font=11,
   font_size=12,weight=1.5,color="blue"):
>  Text(img,10,336,"font_size=20",position="E",font=11,
   font_size=20,weight=1.5,color="blue"):
>  Text(img,12,290,"weight=1.0",position="E",font=11,
   font_size=12,weight=1.0,color=0):
>  Text(img,12,210,"weight=1.5",position="E",font=11,
   font_size=12,weight=1.5,color=0):
>  Text(img,12,130,"weight=2.0",position="E",font=11,
   font_size=12,weight=2.0,color=0):
>  Text(img,12,50,"weight=3.3",position="E",font=11,
   font_size=12,weight=3.3,color=0):
>  Text(img,10,260,"weight=1.0",position="E",font=11,
   font_size=20,weight=1.0,color=0):
>  Text(img,10,180,"weight=1.5",position="E",font=11,
    font_size=20,weight=1.5,color=0):
>  Text(img,10,100,"weight=2.0",position="E",font=11,
   font_size=20,weight=2.0,color=0):
>  Text(img,10,20,"weight=3.3",position="E",font=11,
   font_size=20,weight=3.3,color=0):
>  Text(img,255,350,"font_size=40",position="E",font=11,
   font_size=[30,40],weight=1.5,color="blue"):
>  Text(img,255,280,"weight=1.0",position="E",font=11,
   font_size=40,weight=1.0,color=0):
>  Text(img,255,200,"weight=1.5",position="E",font=11,
    font_size=40,weight=1.10,color=0):
>  Text(img,255,120,"weight=2.0",position="E",font=11,
   font_size=40,weight=2.0,color=0):
>  Text(img,255,40,"weight=3.3",position="E",font=11,
   font_size=40,weight=3.3,color=0):
>  Embed(img);

Image 

Using the stretch argument. 

>  wL, hL, xOff, yOff := 240, 60, 50, 30:
>  fntSz := wL / 13:
>  wR, hR := 330, floor(fntSz*0.9):
>  img := Create(4*hL+5*yOff,wL+wR+3*xOff,channels=3,background=0.5):
>  for cw in [0,1] do
  for ch in [0,1] do
      r := (1-cw) * 2 + (1-ch);

      # Left column of examples.
      x := xOff;
      y := r * (yOff + hL) + yOff;
      SolidRectangle(img,x,y,x+wL,y+hL,color=[0.75,0,0]);
      Text(img,x+wL/2,y+hL/2,
           sprintf("stretch=[%03d,%03d]",wL*cw,hL*ch),
           position="*",font=16,font_size=fntSz,color=1,
           stretch=[wL*cw,hL*ch]);

      # Right column of examples.
      x := xOff * 2 + wL;
      y := y + (hL - hR) / 2;
      SolidRectangle(img,x,y,x+wR,y+hR,color=[0.75,0,0]);
      Text(img,x+wR/2,y+hR/2,
           sprintf("stretch=[%03d,%03d]",wR*cw,hR*ch),
           position="*",font=16,font_size=fntSz,color=1,
           stretch=[wR*cw,hR*ch])
  od
od:
>  Embed(img);

Image 


Using the constrain argument. 

>  wL, hL, xOff, yOff := 240, 60, 50, 30:
>  fntSz := wL / 13:
>  wR, hR := 330, floor(fntSz*0.9):
>  img := Create(4*hL+5*yOff,wL+wR+3*xOff,channels=3,background=0.5):
>  r := 3:
>  for cw in [0,1] do
   for ch in [0,1] do
       r := (1-cw) * 2 + (1-ch);

       x := xOff;
       y := r * (yOff + hL) + yOff;
       SolidRectangle(img,x,y,x+wL,y+hL,color=[0.75,0,0]);
       Text(img,x+wL/2,y+hL/2,
            sprintf("constrain=[%03d,%03d]",wL*cw,hL*ch),
            position="*",font=16,font_size=fntSz,color=1,
            constrain=[wL*cw,hL*ch]);

       x := xOff*2 + wL;
       y := y + (hL - hR) / 2;
       SolidRectangle(img,x,y,x+wR,y+hR,color=[0.75,0,0]);
       Text(img,x+wR/2,y+hR/2,
            sprintf("constrain=[%03d,%03d]",wR*cw,hR*ch),
            position="*",font=16,font_size=fntSz,color=1,
            constrain=[wR*cw,hR*ch])
   od
od:
>  Embed(img);

Image 


Manual kerning using b and t. 

>  img := Create(105,420,channels=3,background=white):
>  Text(img,10,75,"ACTIVATION Typewriter",
   position="E",font=13,font_size=25,weight=1.2,color=0):
>  Text(img,10,25,
   "A\b\bC\bT\tIV\b\b\b\bA\b\bT\tIO\bN T\b\b\b\by\b\bp\be\b\bw\b\br\t\titer",
   position="E",font=13,font_size=25,weight=1.2,color=0):
>  Embed(img);

Image 

Changing font mid-text using f. 

>  img := Create(55,600,channels=3,background=white):
>  Text(img,10,25,"Embedded \f8italic\f11 and \f17script\f11 text",
    position="E",font=11,font_size=25,weight=1.2,color=0):
>  Embed(img);

Image 

Generate the charts mapping Latin to Greek and Cyrillic characters. 

>  w, h := 720, round(w * 8/11):
>  rs, cs := round(h / 13), round(w / 9):
>  fntSz := h / 33.33:
>  for L in [[0,"cy-map"], [4,"gr-map"]] do
   img := Create(h,w,3,background=white):
   for r from 0 to 11 do
       for c from 0 to 7 do
           ch := convert([r*8+c+32],bytes);
           Text(img,(c+1)*cs,h-(r+1)*rs,
                sprintf("%s=\f%02d%s",ch,L[1],ch),
                position="*",font=13,font_size=fntSz,color=0)
       od
   od;
   if L[2] = "cy-map" then
      imgC := img;
   else
      imgG := img;
   end if;
od:
>  Embed(imgC);

Image 

>  Embed(imgG);

Image 

A sample of some of the available non-ASCII glyphs. 

>  img := Create(200,450,channels=3,background=white):
>  Text(img,10,175,"Math: \e1411 \e2268 \e2269 \e2265 \e2266 \e2270 ...",
   position="E",font=11,font_size=25,weight=1.2,color=0):
>  Text(img,10,125,"Zodiac: \e2301 \e2302 \e2303 \e2304 \e2305 ...",
   position="E",font=11,font_size=25,weight=1.2,color=0):
>  Text(img,10,75,"Music: \e2324 \e2325 \e2330 \e2381 \e2378 ...",
   position="E",font=11,font_size=25,weight=1.2,color=0):
>  Text(img,10,25,"Weather: \e764 \e765 \e766 \e767 \e768 ...",
   position="E",font=11,font_size=25,weight=1.2,color=0):
>  Embed(img);

Image 

Generate the charts of (1) Japanese characters and (2) all other glyphs. 

>  glyphW := 67:
>  for chart in [[round(21.3*glyphW),"jp",4000,4757],
               [round(21.3*glyphW),"sy1",1,2200],
               [round(21.3*glyphW),"sy2",2201,3926]]
do
   w, file, lo, hi := op(chart);
   h := round(w * 1.7); # Enough for each chart; will be truncated below.
   x, y := glyphW/4, 10:
   img := Create(h,w,3,background=white):
   dymax := 0;
   for n from lo to hi do
       s := sprintf("\e%d",n);
       (dx,dy) := TextSize(img,s,font=14,font_size=12,weight=1.2);
       dx, dy := dx-1, dy-1;
       if (dx,dy) <> (0.,0.) then
           # Glyph number.
           Text(img,x+5,h-y,sprintf("%d",n),position="SE",
                font=14,font_size=10,color="red",weight=1.2);
           # Glyph.
           Text(img,x+5,h-y-17,s,position="SE",
                font=14,font_size=12,color=0,weight=1.2);
           # Find next column. Skip extra columns if glyph is too wide.
           while dx > -5 do
               x := x + glyphW;
               dx := dx - glyphW
           od;
           # Keep track of the tallest glyph in this row.
           if dy > dymax then dymax := dy fi;
           # Start a new row if we've reached the right edge.
           if x >= w - 3 * glyphW / 4 or n = hi then
               x := glyphW / 4;
               y := y + 25 + dymax;
               dymax := 0;
               if y >= h then break fi
           fi
       fi
   od;
   # Truncate image to just the part we've filled.
   img := img[1..round(y+10),..,..];
   assign(cat('img_',chart[2]),img);
od:
>  Embed(img_jp);

Image 

>  Embed(img_sy1);

Image 

>  Embed(img_sy2);

Image 

ASCII to Cyrillic and Greek mapping. 

>  img := Create(105,640,channels=3,background=white):
>  Text(img,10,75,"Vostok, Pilot = \f0Vostok, Pilot",
    position="E",font=11,font_size=25,weight=1.2,color=0):
>  Text(img,10,25,"Archimedes = \f4Arximhdhw",
    position="E",font=11,font_size=25,weight=1.2,color=0):
>  Embed(img);

Image 

ImageTools:-Draw provides the TextSizefunction to aid in the manipulation of text. TextSizereturns the width and height of a piece of text given a subset of the arguments accepted by the Text primitive 

>  with(ImageTools):
>  with(ImageTools:-Draw):

Determining the extent of rendered text. 

>  w, h := 500, 120;
Typesetting:-mprintslash([w, h := 500, 120], [500, 120])
>  img := Create(h,w,channels=3,background=0.5):
>  msg := "Red Box Fits the Text";
Typesetting:-mprintslash([msg :=
>  boxW, boxH := TextSize(img,msg,font=10,font_size=25,weight=1.5);
Typesetting:-mprintslash([boxW, boxH := 435.659090909091049, 26.5681818181818166], [435.659090909091049, 26.5681818181818166])
>  x1, y1 := (w - boxW) / 2, (h - boxH) / 2;
Typesetting:-mprintslash([x1, y1 := 32.1704546, 46.71590909], [32.1704546, 46.71590909])
>  x2, y2 := (w + boxW) / 2, (h + boxH) / 2;
Typesetting:-mprintslash([x2, y2 := 467.8295454, 73.28409091], [467.8295454, 73.28409091])
>  SolidRectangle(img,x1,y1,x2,y2,color=[0.75,0,0]);
>  Text(img,w/2,h/2,msg,position="*",font=10,font_size=25,weight=1.5,color=1);
25., 25.

Draw a border 5 pixels outside the box. 

>  x1, y1, x2, y2 := x1-5, y1-5, x2+5, y2+5;
Typesetting:-mprintslash([x1, y1, x2, y2 := 27.1704546, 41.71590909, 472.8295454, 78.28409091], [27.1704546, 41.71590909, 472.8295454, 78.28409091])
>  Poly(img,[[x1,y1],[x1,y2],[x2,y2],[x2,y1],[x1,y1]],color="yellow");
>  Embed(img);

Image