Image Tools - Maple Help

 ImageTools Draw

Maple 2018 includes a new set of drawing tools so you can annotate images, generate diagrams, and more, all programmatically. 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 H×W, H×W×3, or H×Wx4 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 H×W 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:
 Line from (2.5,2.5) to (17.5,12.5), thickness=1:
 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 use 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);

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);

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);

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);

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);

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);

 > 

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);

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);

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);

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);

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);

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);

 > 

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);

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);

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);

 > 

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);

 > Write("circ1.png",img);
 ${6056}$ (6.1)

Solid gray circle.

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

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);

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);

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);

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);

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);

 > 

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);

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);

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);

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);

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);

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);

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);

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);

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);

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);

 > Embed(imgG);

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);

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);

 > Embed(img_sy1);

 > Embed(img_sy2);

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);

 > 

ImageTools:-Draw provides the TextSize function to aid in the manipulation of text. TextSize returns 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;
 ${w}{,}{h}{≔}{500}{,}{120}$ (7.1)
 > img := Create(h,w,channels=3,background=0.5):
 > msg := "Red Box Fits the Text";
 ${\mathrm{msg}}{≔}{"Red Box Fits the Text"}$ (7.2)
 > boxW, boxH := TextSize(img,msg,font=10,font_size=25,weight=1.5);
 ${\mathrm{boxW}}{,}{\mathrm{boxH}}{≔}{435.659090909091049}{,}{26.5681818181818166}$ (7.3)
 > x1, y1 := (w - boxW) / 2, (h - boxH) / 2;
 ${\mathrm{x1}}{,}{\mathrm{y1}}{≔}{32.1704546}{,}{46.71590909}$ (7.4)
 > x2, y2 := (w + boxW) / 2, (h + boxH) / 2;
 ${\mathrm{x2}}{,}{\mathrm{y2}}{≔}{467.8295454}{,}{73.28409091}$ (7.5)
 > 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.}$ (7.6)

Draw a border 5 pixels outside the box.

 > x1, y1, x2, y2 := x1-5, y1-5, x2+5, y2+5;
 ${\mathrm{x1}}{,}{\mathrm{y1}}{,}{\mathrm{x2}}{,}{\mathrm{y2}}{≔}{27.1704546}{,}{41.71590909}{,}{472.8295454}{,}{78.28409091}$ (7.7)
 > Poly(img,[[x1,y1],[x1,y2],[x2,y2],[x2,y1],[x1,y1]],color="yellow");
 > Embed(img);