Mercurial > projects > dynamin
diff dynamin/painting/graphics.d @ 0:aa4efef0f0b1
Initial commit of code.
author | Jordan Miner <jminer7@gmail.com> |
---|---|
date | Mon, 15 Jun 2009 22:10:48 -0500 |
parents | |
children | dcaa95190f4b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dynamin/painting/graphics.d Mon Jun 15 22:10:48 2009 -0500 @@ -0,0 +1,589 @@ +// Written in the D programming language +// www.digitalmars.com/d/ + +/* + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Dynamin library. + * + * The Initial Developer of the Original Code is Jordan Miner. + * Portions created by the Initial Developer are Copyright (C) 2006-2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jordan Miner <jminer7@gmail.com> + * + */ + +module dynamin.painting.graphics; + +import dynamin.c.cairo; +import dynamin.core.string; +import dynamin.core.math; +import dynamin.core.file; +import dynamin.painting.coordinates; +import dynamin.painting.color; +import tango.io.Stdout; + +/// +class Font { +private: + string _family; + int _style = 0; + int _size; +public: + this(string family_ = "", int size = 10, bool b = false, bool i = false, bool u = false) { + this.family = family_; + this.size = size; + bold = b; + italic = i; + underline = u; + } + /** + * Gets or sets the family name of this font. Common font family names on + * Windows are "Arial", "Times New Roman", and "Tahoma". + */ + string family() { return _family; } + /// ditto + void family(string str) { _family = str; } + /// + int style() { return _style; } + /// ditto + void style(int s) { _style = s; } + /// Gets or sets whether this font is bold. + bool bold() { return cast(bool)(_style & 1); } + /// ditto + void bold(bool b) { b ? (_style |= 1) : (_style &= ~1); } + /// Gets or sets whether this font is italic. + bool italic() { return cast(bool)(_style & 2); } + /// ditto + void italic(bool b) { b ? (_style |= 2) : (_style &= ~2); } + /// Gets or sets whether this font is underline. + bool underline() { return cast(bool)(_style & 4); } + /// ditto + void underline(bool b) { b ? (_style |= 4) : (_style &= ~4); } + /// Gets or sets whether this font is strikethrough. + bool strikethrough() { return cast(bool)(_style & 8); } + /// ditto + void strikethrough(bool b) { b ? (_style |= 8) : (_style &= ~8); } + /** + * Gets or sets the size of this font in user space units, not in points. + * This size is the ascent plus the descent, not including the leading. + */ + int size() { return _size; } + /// ditto + void size(int s) { _size = s; } +} + +/// +struct FontExtents { + /// + real ascent; + /// + real descent; + /// + real leading() { return height - ascent - descent; } + /// + real height; + /// + real maxAdvance; +} + +//import lodepng = dynamin.lodepng.decode; +/// An RGBA 32-bit-per-pixel image. +class Image { + Color* _data; + uint _width, _height; + Color* data() { return _data; } + uint width() { return _width; } + uint height() { return _height; } + protected this() { + } + Color* opIndex(int x, int y) { + return _data + x+y*_width; + } + static Image load(string file) { + static if(false) { + auto img = new Image; + lodepng.PngInfo info; + img._data = cast(Color*)lodepng.decode32(readFileBytes(file), info); + img._width = info.image.width; + img._height = info.image.height; + + ubyte r; + for(uint i = 0; i < img.width * img.height; ++i) { + // lodepng returns data as ABGR instead of the ARGB that cairo, + // Windows, and I think X use. + r = img.data[i].R; + img.data[i].R = img.data[i].B; + img.data[i].B = r; + // cairo, Windows, and I think X use pre-multiplied alpha + img.data[i].R = img.data[i].R * img.data[i].A / 255; + img.data[i].G = img.data[i].G * img.data[i].A / 255; + img.data[i].B = img.data[i].B * img.data[i].A / 255; + } + + return img; + } else { return null; } + } +} + +/// +enum GraphicsOperator { + Clear, /// + + Source, /// + Over, /// + In, /// + Out, /// + Atop, /// + + Dest, /// + DestOver, /// + DestIn, /// + DestOut, /// + DestAtop, /// + + Xor, /// + Add, /// + Saturate /// +} + +/// +enum GraphicsFillRule { + /// + Winding, + EvenOdd /// +} +/** + * Example: + * ----- + * graphics.source = Color.Gold; + * graphics.rectangle(40, 10, 100, 120); + * graphics.fill(); + * graphics.source = Color.Black; + * + * graphics.lineWidth = 20; + * + * // GraphicsLineCap.Butt is default + * graphics.moveTo(40, 30); + * graphics.lineTo(140, 30); + * graphics.stroke(); + * + * graphics.lineCap = GraphicsLineCap.Round; + * graphics.moveTo(40, 70); + * graphics.lineTo(140, 70); + * graphics.stroke(); + * + * graphics.lineCap = GraphicsLineCap.Square; + * graphics.moveTo(40, 110); + * graphics.lineTo(140, 110); + * graphics.stroke(); + * ----- + * $(IMAGE ../web/example_line_cap.png) + */ +enum GraphicsLineCap { + /** + * Uses no ending. The line ends exactly at the end point. + */ + Butt, + /** + * Uses a rounded ending with the center of the circle at the end point. + * Therefore, the cap extends past the end point for half the line width. + */ + Round, + /** + * Uses a square ending with the center of the square at the end point. + * Therefore, the cap extends past the end point for half the line width. + */ + Square +} + +// cairo_copy_clip_rectangles --> Rectangle[] ClipRectangles() +// cairo_get_dash --> Dashes() +// cairo_get_color_stop_rgba --> ColorStops() +/** + * A Graphics object uses its source to draw on its target. Its target is set + * when it is created, but its source can be changed whenever desired. For + * example, for a painting event, the target of a Graphics is the control + * being painted. In other cases it could be an image. Its source is usually a + * color, but could be a gradient, an image, or some other pattern. + * + * If the documentation here is not sufficient, cairo might have + * better documentation at $(LINK http://www.cairographics.org/manual/). + */ +class Graphics { +private: + cairo_t* cr; +public: + this(cairo_t* cr) { + this.cr = cr; + cairo_reference(cr); + checkStatus(); + } + ~this() { + cairo_destroy(cr); + } + /** + * Returns: a pointer to the cairo context (cairo_t*) that backs this object + */ + cairo_t* handle() { return cr; } + void checkStatus() { + cairo_status_t status = cairo_status(cr); + if(status == CAIRO_STATUS_SUCCESS) + return; + + Stdout("Cairo error: ")(cairo_status_to_string(status)).newline; + assert(0); + } + /// + void moveTo(real x, real y) { + cairo_move_to(cr, x, y); + } + /// ditto + void moveTo(Point pt) { + moveTo(pt.x, pt.y); + } + /// + void lineTo(real x, real y) { + cairo_line_to(cr, x, y); + } + /// ditto + void lineTo(Point pt) { + lineTo(pt.x, pt.y); + } + /// + void curveTo(Point pt1, Point pt2, Point pt3) { + curveTo(pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y); + } + /// ditto + void curveTo(real x1, real y1, real x2, real y2, real x3, real y3) { + cairo_curve_to(cr, x1, y1, x2, y2, x3, y3); + } + /// + void relMoveTo(real x, real y) { + cairo_rel_move_to(cr, x, y); + } + /// ditto + void relMoveTo(Point pt) { + relMoveTo(pt.x, pt.y); + } + /// + void relLineTo(real x, real y) { + cairo_rel_line_to(cr, x, y); + } + /// ditto + void relLineTo(Point pt) { + relLineTo(pt.x, pt.y); + } + /// + void relCurveTo(Point pt1, Point pt2, Point pt3) { + relCurveTo(pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y); + } + /// ditto + void relCurveTo(real x1, real y1, real x2, real y2, real x3, real y3) { + cairo_rel_curve_to(cr, x1, y1, x2, y2, x3, y3); + } + /** + * Adds an arc to the current path. A line is added connecting the + * current point to the beginning of the arc. + * Example: + * ----- + * graphics.moveTo(5, 5); + * graphics.arc(50.5, 80.5, 40, 40, -0.2, PI/2); + * graphics.stroke(); + * ----- + * $(IMAGE ../web/example_arc.png) + */ + void arc(Point ptc, real radius, real angle1, real angle2) { + arc(ptc.x, ptc.y, radius, angle1, angle2); + } + /// ditto + void arc(real xc, real yc, real radius, real angle1, real angle2) { + cairo_arc(cr, xc, yc, radius, angle1, angle2); + } + /// ditto + void arc(real xc, real yc, real xradius, real yradius, real angle1, real angle2) { + cairo_save(cr); + cairo_translate(cr, xc, yc); + cairo_scale(cr, xradius, yradius); + cairo_arc(cr, 0, 0, 1, angle1, angle2); + cairo_restore(cr); + } + /** + * Adds an ellipse as a closed sub-path--a line will not connect it + * to the current point. + * Example: + * ----- + * graphics.ellipse(70.5, 50.5, 60, 25); + * graphics.stroke(); + * ----- + * $(IMAGE ../web/example_ellipse.png) + */ + void ellipse(real xc, real yc, real radius) { + cairo_new_sub_path(cr); + cairo_arc(cr, xc, yc, radius, 0, Pi * 2); + cairo_close_path(cr); + } + /// ditto + void ellipse(real xc, real yc, real xradius, real yradius) { + cairo_new_sub_path(cr); + arc(xc, yc, xradius, yradius, 0, Pi * 2); + cairo_close_path(cr); + } + /** + * Adds a rectangle as a sub-path--a line will not connect it + * to the current point. + * Example: + * ----- + * graphics.rectangle(5.5, 5.5, 70, 20); + * graphics.stroke(); + * ----- + * $(IMAGE ../web/example_rectangle.png) + */ + void rectangle(Rect rect) { + rectangle(rect.x, rect.y, rect.width, rect.height); + } + /// ditto + void rectangle(real x, real y, real width, real height) { + cairo_rectangle(cr, x, y, width, height); + } + /// + void closePath() { + cairo_close_path(cr); + } + /** + * Draws the outline of the current path. + * Example: + * ----- + * graphics.moveTo(12.5, 14.5); + * graphics.lineTo(123.5, 22.5); + * graphics.lineTo(139.5, 108.5); + * graphics.lineTo(49.5, 86.5); + * graphics.closePath(); + * graphics.stroke(); + * ----- + * $(IMAGE ../web/example_stroke.png) + */ + void stroke() { + cairo_stroke(cr); + } + /** + * Draws the inside of the current path. + * Example: + * ----- + * graphics.MoveTo(12.5, 14.5); + * graphics.LineTo(123.5, 22.5); + * graphics.LineTo(139.5, 108.5); + * graphics.LineTo(49.5, 86.5); + * graphics.ClosePath(); + * graphics.Fill(); + * ----- + * $(IMAGE ../web/example_fill.png) + */ + void fill() { + cairo_fill(cr); + } + /** + * Paints the current source everywhere within the current clip region. + * Examples: + * ----- + * graphics.source = Color(255, 128, 0); + * graphics.paint(); + * ----- + * $(IMAGE ../web/example_paint.png) + */ + void paint() { + cairo_paint(cr); + } + /** + * Gets or sets the line _width used for stroking. + * Example: + * ----- + * graphics.ellipse(40.5, 30.5, 30, 20); + * graphics.lineWidth = 1; + * graphics.stroke(); + * graphics.ellipse(40.5, 80.5, 30, 20); + * graphics.lineWidth = 5; + * graphics.stroke(); + * ----- + * $(IMAGE ../web/example_line_width.png) + */ + real lineWidth() { + return cairo_get_line_width(cr); + } + /// ditto + void lineWidth(real width) { + cairo_set_line_width(cr, width); + } + /** + * Gets or sets the line cap used for stroking. + * + * The line cap is only examined when the stroke is performed, not before. + * Therefore, drawing two lines, each with a different line cap, would + * require calling stroke twice. + */ + GraphicsLineCap lineCap() { + return cast(GraphicsLineCap)cairo_get_line_cap(cr); + } + /// ditto + void lineCap(GraphicsLineCap cap) { + cairo_set_line_cap(cr, cap); + } + /** + * Sets the font size to the specified size in user space units, not + * in points. + */ + void fontSize(real size) { + assert(size != 0); + cairo_set_font_size(cr, size); + } + /** + * Set the font used to draw text. + */ + void font(Font f) { + assert(f.size != 0); + cairo_set_font_size(cr, f.size); + cairo_select_font_face(cr, toCharPtr(f.family), f.italic, f.bold); + } + // TODO: if text is all ascii, do fast path with no uniscribe + void drawText(string text, real x, real y) { + auto extents = getFontExtents; + cairo_font_extents_t fextents; + cairo_font_extents(cr, &fextents); + cairo_move_to(cr, x, y + fextents.ascent); + cairo_show_text(cr, toCharPtr(text)); // 99% of time spent in show_text + checkStatus(); + } + /// + Size getTextExtents(string text) { + cairo_text_extents_t textents; + cairo_text_extents(cr, toCharPtr(text), &textents); + cairo_font_extents_t fextents; + cairo_font_extents(cr, &fextents); + return Size(textents.x_advance, fextents.height); + } + /// + FontExtents getFontExtents() { // TODO: make property? + cairo_font_extents_t fextents; + cairo_font_extents(cr, &fextents); + FontExtents extents; + extents.ascent = fextents.ascent; + extents.descent = fextents.descent; + extents.height = fextents.height; + extents.maxAdvance = fextents.max_x_advance; + return extents; + } + /// + Rect getClipExtents() { // TODO: make property? + double x, y, width, height; + cairo_clip_extents(cr, &x, &y, &width, &height); + return Rect(x, y, width, height); + } + /// + void save() { + cairo_save(cr); + } + /// + void restore() { + cairo_restore(cr); + checkStatus(); + } + /// + void clip() { + cairo_clip(cr); + } + /// + void translate(Point pt) { + translate(pt.x, pt.y); + } + /// ditto + void translate(real x, real y) { + cairo_translate(cr, x, y); + } + /// + void scale(real x, real y) { + cairo_scale(cr, x, y); + } + /// + void rotate(real angle) { + cairo_rotate(cr, angle); + } + /// + GraphicsOperator operator() { + return cast(GraphicsOperator)cairo_get_operator(cr); + } + /// + void operator(GraphicsOperator op) { + cairo_set_operator(cr, cast(cairo_operator_t)op); + } + /** + * Sets the dash pattern to be used when lines are drawn. + */ + void setDash(real[] dashes, real offset) { + auto cdashes = new double[dashes.length]; + foreach(int i, real r; dashes) + cdashes[i] = r; + cairo_set_dash(cr, cdashes.ptr, cdashes.length, offset); + } + /** + * Gets or sets the fill rule the current fill rule. + * The default is GraphicsFillRule.Winding. + */ + GraphicsFillRule fillRule() { + return cast(GraphicsFillRule)cairo_get_fill_rule(cr); + } + /// ditto + void fillRule(GraphicsFillRule rule) { + cairo_set_fill_rule(cr, cast(cairo_fill_rule_t)rule); + } + /** + * 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. + */ + void pushGroup() { + cairo_push_group(cr); + } + //popGroup() { // TODO: returning a pattern + // cairo_pop_group(cr); + //} + /** + * Terminates the redirection begun by a call to PushGroup() or + * PushGroupWithContent() and installs the resulting pattern as the + * source pattern. + */ + void popGroupToSource() { + cairo_pop_group_to_source(cr); + checkStatus(); + } + // TODO: figure out the best way to set the source and get the source + void source(Color c) { + cairo_set_source_rgba(cr, c.R/255.0, c.G/255.0, c.B/255.0, c.A/255.0); + } + //void source(Pattern s) {} + //void setSource(Surface s, real x = 0, real y = 0) {} + // TODO: remove this function and have users do: + // g.setSource(img, x, y); + // g.paint(); + // ??? + // paintSource(Image, real, real) ? + /// Draws the specified image unscaled. + void drawImage(Image image, real x, real y) { + auto surface= cairo_image_surface_create_for_data(cast(char*)image.data, + CAIRO_FORMAT_ARGB32, image.width, image.height, image.width*4); + save(); + cairo_set_source_surface(cr, surface, x, y); + cairo_paint(cr); + restore(); + cairo_surface_destroy(surface); + } + // Draws the specified image scaled to the specified width and height. + //void drawImage(Image image, real x, real y, real width, real height); +}