0
|
1 // Written in the D programming language
|
|
2 // www.digitalmars.com/d/
|
|
3
|
|
4 /*
|
|
5 * The contents of this file are subject to the Mozilla Public License Version
|
|
6 * 1.1 (the "License"); you may not use this file except in compliance with
|
|
7 * the License. You may obtain a copy of the License at
|
|
8 * http://www.mozilla.org/MPL/
|
|
9 *
|
|
10 * Software distributed under the License is distributed on an "AS IS" basis,
|
|
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
12 * for the specific language governing rights and limitations under the
|
|
13 * License.
|
|
14 *
|
|
15 * The Original Code is the Dynamin library.
|
|
16 *
|
|
17 * The Initial Developer of the Original Code is Jordan Miner.
|
|
18 * Portions created by the Initial Developer are Copyright (C) 2006-2009
|
|
19 * the Initial Developer. All Rights Reserved.
|
|
20 *
|
|
21 * Contributor(s):
|
|
22 * Jordan Miner <jminer7@gmail.com>
|
|
23 *
|
|
24 */
|
|
25
|
|
26 module dynamin.painting.graphics;
|
|
27
|
|
28 import dynamin.c.cairo;
|
|
29 import dynamin.core.string;
|
|
30 import dynamin.core.math;
|
|
31 import dynamin.core.file;
|
|
32 import dynamin.painting.coordinates;
|
|
33 import dynamin.painting.color;
|
|
34 import tango.io.Stdout;
|
|
35
|
|
36 ///
|
|
37 class Font {
|
|
38 private:
|
|
39 string _family;
|
|
40 int _style = 0;
|
|
41 int _size;
|
|
42 public:
|
|
43 this(string family_ = "", int size = 10, bool b = false, bool i = false, bool u = false) {
|
|
44 this.family = family_;
|
|
45 this.size = size;
|
|
46 bold = b;
|
|
47 italic = i;
|
|
48 underline = u;
|
|
49 }
|
|
50 /**
|
|
51 * Gets or sets the family name of this font. Common font family names on
|
|
52 * Windows are "Arial", "Times New Roman", and "Tahoma".
|
|
53 */
|
|
54 string family() { return _family; }
|
|
55 /// ditto
|
|
56 void family(string str) { _family = str; }
|
|
57 ///
|
|
58 int style() { return _style; }
|
|
59 /// ditto
|
|
60 void style(int s) { _style = s; }
|
|
61 /// Gets or sets whether this font is bold.
|
|
62 bool bold() { return cast(bool)(_style & 1); }
|
|
63 /// ditto
|
|
64 void bold(bool b) { b ? (_style |= 1) : (_style &= ~1); }
|
|
65 /// Gets or sets whether this font is italic.
|
|
66 bool italic() { return cast(bool)(_style & 2); }
|
|
67 /// ditto
|
|
68 void italic(bool b) { b ? (_style |= 2) : (_style &= ~2); }
|
|
69 /// Gets or sets whether this font is underline.
|
|
70 bool underline() { return cast(bool)(_style & 4); }
|
|
71 /// ditto
|
|
72 void underline(bool b) { b ? (_style |= 4) : (_style &= ~4); }
|
|
73 /// Gets or sets whether this font is strikethrough.
|
|
74 bool strikethrough() { return cast(bool)(_style & 8); }
|
|
75 /// ditto
|
|
76 void strikethrough(bool b) { b ? (_style |= 8) : (_style &= ~8); }
|
|
77 /**
|
|
78 * Gets or sets the size of this font in user space units, not in points.
|
|
79 * This size is the ascent plus the descent, not including the leading.
|
|
80 */
|
|
81 int size() { return _size; }
|
|
82 /// ditto
|
|
83 void size(int s) { _size = s; }
|
|
84 }
|
|
85
|
|
86 ///
|
|
87 struct FontExtents {
|
|
88 ///
|
|
89 real ascent;
|
|
90 ///
|
|
91 real descent;
|
|
92 ///
|
|
93 real leading() { return height - ascent - descent; }
|
|
94 ///
|
|
95 real height;
|
|
96 ///
|
|
97 real maxAdvance;
|
|
98 }
|
|
99
|
|
100 //import lodepng = dynamin.lodepng.decode;
|
|
101 /// An RGBA 32-bit-per-pixel image.
|
|
102 class Image {
|
|
103 Color* _data;
|
|
104 uint _width, _height;
|
|
105 Color* data() { return _data; }
|
|
106 uint width() { return _width; }
|
|
107 uint height() { return _height; }
|
|
108 protected this() {
|
|
109 }
|
|
110 Color* opIndex(int x, int y) {
|
|
111 return _data + x+y*_width;
|
|
112 }
|
|
113 static Image load(string file) {
|
|
114 static if(false) {
|
|
115 auto img = new Image;
|
|
116 lodepng.PngInfo info;
|
|
117 img._data = cast(Color*)lodepng.decode32(readFileBytes(file), info);
|
|
118 img._width = info.image.width;
|
|
119 img._height = info.image.height;
|
|
120
|
|
121 ubyte r;
|
|
122 for(uint i = 0; i < img.width * img.height; ++i) {
|
|
123 // lodepng returns data as ABGR instead of the ARGB that cairo,
|
|
124 // Windows, and I think X use.
|
|
125 r = img.data[i].R;
|
|
126 img.data[i].R = img.data[i].B;
|
|
127 img.data[i].B = r;
|
|
128 // cairo, Windows, and I think X use pre-multiplied alpha
|
|
129 img.data[i].R = img.data[i].R * img.data[i].A / 255;
|
|
130 img.data[i].G = img.data[i].G * img.data[i].A / 255;
|
|
131 img.data[i].B = img.data[i].B * img.data[i].A / 255;
|
|
132 }
|
|
133
|
|
134 return img;
|
|
135 } else { return null; }
|
|
136 }
|
|
137 }
|
|
138
|
|
139 ///
|
|
140 enum GraphicsOperator {
|
|
141 Clear, ///
|
|
142
|
|
143 Source, ///
|
|
144 Over, ///
|
|
145 In, ///
|
|
146 Out, ///
|
|
147 Atop, ///
|
|
148
|
|
149 Dest, ///
|
|
150 DestOver, ///
|
|
151 DestIn, ///
|
|
152 DestOut, ///
|
|
153 DestAtop, ///
|
|
154
|
|
155 Xor, ///
|
|
156 Add, ///
|
|
157 Saturate ///
|
|
158 }
|
|
159
|
|
160 ///
|
|
161 enum GraphicsFillRule {
|
|
162 ///
|
|
163 Winding,
|
|
164 EvenOdd ///
|
|
165 }
|
|
166 /**
|
|
167 * Example:
|
|
168 * -----
|
|
169 * graphics.source = Color.Gold;
|
|
170 * graphics.rectangle(40, 10, 100, 120);
|
|
171 * graphics.fill();
|
|
172 * graphics.source = Color.Black;
|
|
173 *
|
|
174 * graphics.lineWidth = 20;
|
|
175 *
|
|
176 * // GraphicsLineCap.Butt is default
|
|
177 * graphics.moveTo(40, 30);
|
|
178 * graphics.lineTo(140, 30);
|
|
179 * graphics.stroke();
|
|
180 *
|
|
181 * graphics.lineCap = GraphicsLineCap.Round;
|
|
182 * graphics.moveTo(40, 70);
|
|
183 * graphics.lineTo(140, 70);
|
|
184 * graphics.stroke();
|
|
185 *
|
|
186 * graphics.lineCap = GraphicsLineCap.Square;
|
|
187 * graphics.moveTo(40, 110);
|
|
188 * graphics.lineTo(140, 110);
|
|
189 * graphics.stroke();
|
|
190 * -----
|
|
191 * $(IMAGE ../web/example_line_cap.png)
|
|
192 */
|
|
193 enum GraphicsLineCap {
|
|
194 /**
|
|
195 * Uses no ending. The line ends exactly at the end point.
|
|
196 */
|
|
197 Butt,
|
|
198 /**
|
|
199 * Uses a rounded ending with the center of the circle at the end point.
|
|
200 * Therefore, the cap extends past the end point for half the line width.
|
|
201 */
|
|
202 Round,
|
|
203 /**
|
|
204 * Uses a square ending with the center of the square at the end point.
|
|
205 * Therefore, the cap extends past the end point for half the line width.
|
|
206 */
|
|
207 Square
|
|
208 }
|
|
209
|
|
210 // cairo_copy_clip_rectangles --> Rectangle[] ClipRectangles()
|
|
211 // cairo_get_dash --> Dashes()
|
|
212 // cairo_get_color_stop_rgba --> ColorStops()
|
|
213 /**
|
|
214 * A Graphics object uses its source to draw on its target. Its target is set
|
|
215 * when it is created, but its source can be changed whenever desired. For
|
|
216 * example, for a painting event, the target of a Graphics is the control
|
|
217 * being painted. In other cases it could be an image. Its source is usually a
|
|
218 * color, but could be a gradient, an image, or some other pattern.
|
|
219 *
|
|
220 * If the documentation here is not sufficient, cairo might have
|
|
221 * better documentation at $(LINK http://www.cairographics.org/manual/).
|
|
222 */
|
|
223 class Graphics {
|
|
224 private:
|
|
225 cairo_t* cr;
|
|
226 public:
|
|
227 this(cairo_t* cr) {
|
|
228 this.cr = cr;
|
|
229 cairo_reference(cr);
|
|
230 checkStatus();
|
|
231 }
|
|
232 ~this() {
|
|
233 cairo_destroy(cr);
|
|
234 }
|
|
235 /**
|
|
236 * Returns: a pointer to the cairo context (cairo_t*) that backs this object
|
|
237 */
|
|
238 cairo_t* handle() { return cr; }
|
|
239 void checkStatus() {
|
|
240 cairo_status_t status = cairo_status(cr);
|
|
241 if(status == CAIRO_STATUS_SUCCESS)
|
|
242 return;
|
|
243
|
|
244 Stdout("Cairo error: ")(cairo_status_to_string(status)).newline;
|
|
245 assert(0);
|
|
246 }
|
|
247 ///
|
|
248 void moveTo(real x, real y) {
|
|
249 cairo_move_to(cr, x, y);
|
|
250 }
|
|
251 /// ditto
|
|
252 void moveTo(Point pt) {
|
|
253 moveTo(pt.x, pt.y);
|
|
254 }
|
|
255 ///
|
|
256 void lineTo(real x, real y) {
|
|
257 cairo_line_to(cr, x, y);
|
|
258 }
|
|
259 /// ditto
|
|
260 void lineTo(Point pt) {
|
|
261 lineTo(pt.x, pt.y);
|
|
262 }
|
|
263 ///
|
|
264 void curveTo(Point pt1, Point pt2, Point pt3) {
|
|
265 curveTo(pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y);
|
|
266 }
|
|
267 /// ditto
|
|
268 void curveTo(real x1, real y1, real x2, real y2, real x3, real y3) {
|
|
269 cairo_curve_to(cr, x1, y1, x2, y2, x3, y3);
|
|
270 }
|
|
271 ///
|
|
272 void relMoveTo(real x, real y) {
|
|
273 cairo_rel_move_to(cr, x, y);
|
|
274 }
|
|
275 /// ditto
|
|
276 void relMoveTo(Point pt) {
|
|
277 relMoveTo(pt.x, pt.y);
|
|
278 }
|
|
279 ///
|
|
280 void relLineTo(real x, real y) {
|
|
281 cairo_rel_line_to(cr, x, y);
|
|
282 }
|
|
283 /// ditto
|
|
284 void relLineTo(Point pt) {
|
|
285 relLineTo(pt.x, pt.y);
|
|
286 }
|
|
287 ///
|
|
288 void relCurveTo(Point pt1, Point pt2, Point pt3) {
|
|
289 relCurveTo(pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y);
|
|
290 }
|
|
291 /// ditto
|
|
292 void relCurveTo(real x1, real y1, real x2, real y2, real x3, real y3) {
|
|
293 cairo_rel_curve_to(cr, x1, y1, x2, y2, x3, y3);
|
|
294 }
|
|
295 /**
|
|
296 * Adds an arc to the current path. A line is added connecting the
|
|
297 * current point to the beginning of the arc.
|
|
298 * Example:
|
|
299 * -----
|
|
300 * graphics.moveTo(5, 5);
|
|
301 * graphics.arc(50.5, 80.5, 40, 40, -0.2, PI/2);
|
|
302 * graphics.stroke();
|
|
303 * -----
|
|
304 * $(IMAGE ../web/example_arc.png)
|
|
305 */
|
|
306 void arc(Point ptc, real radius, real angle1, real angle2) {
|
|
307 arc(ptc.x, ptc.y, radius, angle1, angle2);
|
|
308 }
|
|
309 /// ditto
|
|
310 void arc(real xc, real yc, real radius, real angle1, real angle2) {
|
|
311 cairo_arc(cr, xc, yc, radius, angle1, angle2);
|
|
312 }
|
|
313 /// ditto
|
|
314 void arc(real xc, real yc, real xradius, real yradius, real angle1, real angle2) {
|
|
315 cairo_save(cr);
|
|
316 cairo_translate(cr, xc, yc);
|
|
317 cairo_scale(cr, xradius, yradius);
|
|
318 cairo_arc(cr, 0, 0, 1, angle1, angle2);
|
|
319 cairo_restore(cr);
|
|
320 }
|
|
321 /**
|
|
322 * Adds an ellipse as a closed sub-path--a line will not connect it
|
|
323 * to the current point.
|
|
324 * Example:
|
|
325 * -----
|
|
326 * graphics.ellipse(70.5, 50.5, 60, 25);
|
|
327 * graphics.stroke();
|
|
328 * -----
|
|
329 * $(IMAGE ../web/example_ellipse.png)
|
|
330 */
|
|
331 void ellipse(real xc, real yc, real radius) {
|
|
332 cairo_new_sub_path(cr);
|
|
333 cairo_arc(cr, xc, yc, radius, 0, Pi * 2);
|
|
334 cairo_close_path(cr);
|
|
335 }
|
|
336 /// ditto
|
|
337 void ellipse(real xc, real yc, real xradius, real yradius) {
|
|
338 cairo_new_sub_path(cr);
|
|
339 arc(xc, yc, xradius, yradius, 0, Pi * 2);
|
|
340 cairo_close_path(cr);
|
|
341 }
|
|
342 /**
|
|
343 * Adds a rectangle as a sub-path--a line will not connect it
|
|
344 * to the current point.
|
|
345 * Example:
|
|
346 * -----
|
|
347 * graphics.rectangle(5.5, 5.5, 70, 20);
|
|
348 * graphics.stroke();
|
|
349 * -----
|
|
350 * $(IMAGE ../web/example_rectangle.png)
|
|
351 */
|
|
352 void rectangle(Rect rect) {
|
|
353 rectangle(rect.x, rect.y, rect.width, rect.height);
|
|
354 }
|
|
355 /// ditto
|
|
356 void rectangle(real x, real y, real width, real height) {
|
|
357 cairo_rectangle(cr, x, y, width, height);
|
|
358 }
|
|
359 ///
|
|
360 void closePath() {
|
|
361 cairo_close_path(cr);
|
|
362 }
|
|
363 /**
|
|
364 * Draws the outline of the current path.
|
|
365 * Example:
|
|
366 * -----
|
|
367 * graphics.moveTo(12.5, 14.5);
|
|
368 * graphics.lineTo(123.5, 22.5);
|
|
369 * graphics.lineTo(139.5, 108.5);
|
|
370 * graphics.lineTo(49.5, 86.5);
|
|
371 * graphics.closePath();
|
|
372 * graphics.stroke();
|
|
373 * -----
|
|
374 * $(IMAGE ../web/example_stroke.png)
|
|
375 */
|
|
376 void stroke() {
|
|
377 cairo_stroke(cr);
|
|
378 }
|
|
379 /**
|
|
380 * Draws the inside of the current path.
|
|
381 * Example:
|
|
382 * -----
|
|
383 * graphics.MoveTo(12.5, 14.5);
|
|
384 * graphics.LineTo(123.5, 22.5);
|
|
385 * graphics.LineTo(139.5, 108.5);
|
|
386 * graphics.LineTo(49.5, 86.5);
|
|
387 * graphics.ClosePath();
|
|
388 * graphics.Fill();
|
|
389 * -----
|
|
390 * $(IMAGE ../web/example_fill.png)
|
|
391 */
|
|
392 void fill() {
|
|
393 cairo_fill(cr);
|
|
394 }
|
|
395 /**
|
|
396 * Paints the current source everywhere within the current clip region.
|
|
397 * Examples:
|
|
398 * -----
|
|
399 * graphics.source = Color(255, 128, 0);
|
|
400 * graphics.paint();
|
|
401 * -----
|
|
402 * $(IMAGE ../web/example_paint.png)
|
|
403 */
|
|
404 void paint() {
|
|
405 cairo_paint(cr);
|
|
406 }
|
|
407 /**
|
|
408 * Gets or sets the line _width used for stroking.
|
|
409 * Example:
|
|
410 * -----
|
|
411 * graphics.ellipse(40.5, 30.5, 30, 20);
|
|
412 * graphics.lineWidth = 1;
|
|
413 * graphics.stroke();
|
|
414 * graphics.ellipse(40.5, 80.5, 30, 20);
|
|
415 * graphics.lineWidth = 5;
|
|
416 * graphics.stroke();
|
|
417 * -----
|
|
418 * $(IMAGE ../web/example_line_width.png)
|
|
419 */
|
|
420 real lineWidth() {
|
|
421 return cairo_get_line_width(cr);
|
|
422 }
|
|
423 /// ditto
|
|
424 void lineWidth(real width) {
|
|
425 cairo_set_line_width(cr, width);
|
|
426 }
|
|
427 /**
|
|
428 * Gets or sets the line cap used for stroking.
|
|
429 *
|
|
430 * The line cap is only examined when the stroke is performed, not before.
|
|
431 * Therefore, drawing two lines, each with a different line cap, would
|
|
432 * require calling stroke twice.
|
|
433 */
|
|
434 GraphicsLineCap lineCap() {
|
|
435 return cast(GraphicsLineCap)cairo_get_line_cap(cr);
|
|
436 }
|
|
437 /// ditto
|
|
438 void lineCap(GraphicsLineCap cap) {
|
|
439 cairo_set_line_cap(cr, cap);
|
|
440 }
|
|
441 /**
|
|
442 * Sets the font size to the specified size in user space units, not
|
|
443 * in points.
|
|
444 */
|
|
445 void fontSize(real size) {
|
|
446 assert(size != 0);
|
|
447 cairo_set_font_size(cr, size);
|
|
448 }
|
|
449 /**
|
|
450 * Set the font used to draw text.
|
|
451 */
|
|
452 void font(Font f) {
|
|
453 assert(f.size != 0);
|
|
454 cairo_set_font_size(cr, f.size);
|
|
455 cairo_select_font_face(cr, toCharPtr(f.family), f.italic, f.bold);
|
|
456 }
|
|
457 // TODO: if text is all ascii, do fast path with no uniscribe
|
|
458 void drawText(string text, real x, real y) {
|
|
459 auto extents = getFontExtents;
|
|
460 cairo_font_extents_t fextents;
|
|
461 cairo_font_extents(cr, &fextents);
|
|
462 cairo_move_to(cr, x, y + fextents.ascent);
|
|
463 cairo_show_text(cr, toCharPtr(text)); // 99% of time spent in show_text
|
|
464 checkStatus();
|
|
465 }
|
|
466 ///
|
|
467 Size getTextExtents(string text) {
|
|
468 cairo_text_extents_t textents;
|
|
469 cairo_text_extents(cr, toCharPtr(text), &textents);
|
|
470 cairo_font_extents_t fextents;
|
|
471 cairo_font_extents(cr, &fextents);
|
|
472 return Size(textents.x_advance, fextents.height);
|
|
473 }
|
|
474 ///
|
|
475 FontExtents getFontExtents() { // TODO: make property?
|
|
476 cairo_font_extents_t fextents;
|
|
477 cairo_font_extents(cr, &fextents);
|
|
478 FontExtents extents;
|
|
479 extents.ascent = fextents.ascent;
|
|
480 extents.descent = fextents.descent;
|
|
481 extents.height = fextents.height;
|
|
482 extents.maxAdvance = fextents.max_x_advance;
|
|
483 return extents;
|
|
484 }
|
|
485 ///
|
|
486 Rect getClipExtents() { // TODO: make property?
|
|
487 double x, y, width, height;
|
|
488 cairo_clip_extents(cr, &x, &y, &width, &height);
|
|
489 return Rect(x, y, width, height);
|
|
490 }
|
|
491 ///
|
|
492 void save() {
|
|
493 cairo_save(cr);
|
|
494 }
|
|
495 ///
|
|
496 void restore() {
|
|
497 cairo_restore(cr);
|
|
498 checkStatus();
|
|
499 }
|
|
500 ///
|
|
501 void clip() {
|
|
502 cairo_clip(cr);
|
|
503 }
|
|
504 ///
|
|
505 void translate(Point pt) {
|
|
506 translate(pt.x, pt.y);
|
|
507 }
|
|
508 /// ditto
|
|
509 void translate(real x, real y) {
|
|
510 cairo_translate(cr, x, y);
|
|
511 }
|
|
512 ///
|
|
513 void scale(real x, real y) {
|
|
514 cairo_scale(cr, x, y);
|
|
515 }
|
|
516 ///
|
|
517 void rotate(real angle) {
|
|
518 cairo_rotate(cr, angle);
|
|
519 }
|
|
520 ///
|
|
521 GraphicsOperator operator() {
|
|
522 return cast(GraphicsOperator)cairo_get_operator(cr);
|
|
523 }
|
|
524 ///
|
|
525 void operator(GraphicsOperator op) {
|
|
526 cairo_set_operator(cr, cast(cairo_operator_t)op);
|
|
527 }
|
|
528 /**
|
|
529 * Sets the dash pattern to be used when lines are drawn.
|
|
530 */
|
|
531 void setDash(real[] dashes, real offset) {
|
|
532 auto cdashes = new double[dashes.length];
|
|
533 foreach(int i, real r; dashes)
|
|
534 cdashes[i] = r;
|
|
535 cairo_set_dash(cr, cdashes.ptr, cdashes.length, offset);
|
|
536 }
|
|
537 /**
|
|
538 * Gets or sets the fill rule the current fill rule.
|
|
539 * The default is GraphicsFillRule.Winding.
|
|
540 */
|
|
541 GraphicsFillRule fillRule() {
|
|
542 return cast(GraphicsFillRule)cairo_get_fill_rule(cr);
|
|
543 }
|
|
544 /// ditto
|
|
545 void fillRule(GraphicsFillRule rule) {
|
|
546 cairo_set_fill_rule(cr, cast(cairo_fill_rule_t)rule);
|
|
547 }
|
|
548 /**
|
|
549 * The temporary surface created will be the same size as the current clip. To speed up using this function, call Clip() to the area you will be drawing in.
|
|
550 */
|
|
551 void pushGroup() {
|
|
552 cairo_push_group(cr);
|
|
553 }
|
|
554 //popGroup() { // TODO: returning a pattern
|
|
555 // cairo_pop_group(cr);
|
|
556 //}
|
|
557 /**
|
|
558 * Terminates the redirection begun by a call to PushGroup() or
|
|
559 * PushGroupWithContent() and installs the resulting pattern as the
|
|
560 * source pattern.
|
|
561 */
|
|
562 void popGroupToSource() {
|
|
563 cairo_pop_group_to_source(cr);
|
|
564 checkStatus();
|
|
565 }
|
|
566 // TODO: figure out the best way to set the source and get the source
|
|
567 void source(Color c) {
|
|
568 cairo_set_source_rgba(cr, c.R/255.0, c.G/255.0, c.B/255.0, c.A/255.0);
|
|
569 }
|
|
570 //void source(Pattern s) {}
|
|
571 //void setSource(Surface s, real x = 0, real y = 0) {}
|
|
572 // TODO: remove this function and have users do:
|
|
573 // g.setSource(img, x, y);
|
|
574 // g.paint();
|
|
575 // ???
|
|
576 // paintSource(Image, real, real) ?
|
|
577 /// Draws the specified image unscaled.
|
|
578 void drawImage(Image image, real x, real y) {
|
|
579 auto surface= cairo_image_surface_create_for_data(cast(char*)image.data,
|
|
580 CAIRO_FORMAT_ARGB32, image.width, image.height, image.width*4);
|
|
581 save();
|
|
582 cairo_set_source_surface(cr, surface, x, y);
|
|
583 cairo_paint(cr);
|
|
584 restore();
|
|
585 cairo_surface_destroy(surface);
|
|
586 }
|
|
587 // Draws the specified image scaled to the specified width and height.
|
|
588 //void drawImage(Image image, real x, real y, real width, real height);
|
|
589 }
|