// gloobs.cc // Gloobs graphics library // // by Smylers // // 2000 Jan 5: inital version // 2000 Jan 26: improved comments; Canvas::pause() overloaded; memory leak // fixed (probably) // 2000 Feb 7: fixed bug which had Circles never scaling nor translating // 2000 Feb 7: ditto, but it works this time! // TODO: include new.h and do memory properly! // The naming convention used is that everything has initial caps, except for // methods of Gloobs::Canvas, which use lowercase to preserve compatibility // with the equivalent g2 functions. // // When variables are numbered, a base of 0 is used, EG Point0, Point1, etc. // // In class constructors, when the point of a parameter is to have its value // copied to some variable in that class the the name of the parameter shall be // the name of the variable with 0 (zero) appended. // // Unfortunately the above two rules sometimes co-incide, and this looks a // little ugly. For example, a class with private variables X0, Y0 and X1, Y1 // ends up with constructor parameters called X00, Y00, X10, and Y10. Sorry // about that. #include #include #include #include #include #include // everything defined in this file is within the Gloobs:: namespace: using namespace Gloobs; // Canvas is the basic class used to manipulate a g2 canvas. The syntax of the // constructor has been rationalized over that of the various g2_open_...() // functions, to make it easier to switch between different output types. In // particular the same class is used for them all, with the first parameter // specifying the canvas type. Virtual canvases are not implemented, cos I // couldn't see a need for them with this syntax. // // The final parameter to the constructor is always the name of the canvas. // For an X11 canvas, this is the window title. For the other types, this is // the base of the filename, with an appropriate extension added. // (Automatically adding the extension like this makes it easier to switch // between canvas types, without having to have windows labelled things like // Foo.png or png files simply called Foo with out an extension, but it can // occasionally be inconvenient. An improvement would be to check the passed // filename, and only add an extension if there's one missing. This method // also makes it too easy to inadvertently create files with spaces in their // names; maybe these could be stripped or converted to underscores?) // // Unfortunately, two separate constructors are needed because of the way (g2) // PostScript canvases are specified, using g2 enumerated types, hence the // slightly awkward error checking below: Canvas::Canvas(CanvasType Type0, int Width, int Height, char* Name) { // remember the type, so it can be referenced in Canvas::pause(): Type = Type0; // open the appropriate kind of g2 canvas, and note the handle for reference // when drawing primitives: switch (Type) { case X11: // use the extended X11 spec, which includes the window and icon titles: Handle = g2_open_X11X(Width, Height, 0, 0, Name, Name, 0, 0, 0); break; case PNG: { char* Filename = new char[strlen(Name) + 5]; strcpy(Filename, Name); strcat(Filename, ".png"); Handle = g2_open_GIF(Filename, Width, Height); delete []Filename; } break; case PS: cerr << "Error in Gloobs::Canvas object construction:\n" << " Canvases of the PS type need to have a size and orientation, not " << "height and\n width." << endl; exit(2); break; } } Canvas::Canvas(CanvasType Type0, g2_PS_paper Size, g2_PS_orientation Orientation, char* Name) // see comments above { Type = Type0; switch (Type) { case X11: cerr << "Error in Gloobs::Canvas object construction:\n" << " Canvases of the X11 type need to have a height and width, not a " << "size and\n orientation." << endl; exit(2); break; case PNG: cerr << "Error in Gloobs::Canvas object construction:\n" << " Canvases of the PNG type need to have a height and width, not a " << "size and\n orientation." << endl; exit(1); break; case PS: // this code is very similar to that for case PNG above; maybe it should // be abstracted into a function? { char* Filename = new char[strlen(Name) + 4]; strcpy(Filename, Name); strcat(Filename, ".ps"); Handle = g2_open_PS(Filename, Size, Orientation); delete []Filename; } break; } } Canvas::~Canvas() // closes the actual g2 canvas when a Canvas ceases to exist { g2_close(Handle); } void Canvas::pause(bool Prompt) // pause the program to allow an X11 canvas to be viewed, until is // pressed; doesn't pause for other canvas types (but still flushes the output // buffer) so this line can always be included when switching programs between // different types of canvas // Prompt -- whether the prompt should be displayed { g2_flush(Handle); if (Type == X11) { if (Prompt == true) { cout << "Gloobs Canvas is paused; press to continue." << endl; } cin.get(); } } void Canvas::pause() // if called without an argument, then default to having a prompt: { pause(true); } void Canvas::clear_palette() // clears the palette -- fairly pointless in a png; could be used for some // primitive animation in an X11 window(?); inserts a page break in a PS file { g2_clear_palette(Handle); } // g2 inks (palette entries) are specified as R, G, and B components; // overloading allows three ways of doing this: int Canvas::ink(double Red, double Green, double Blue) // standard g2-type ink specification, with each component a double in the // range 0 to 1 { return g2_ink(Handle, Red, Green, Blue); } int Canvas::ink(int Red, int Green, int Blue) // new variant, where the components are integers in the range 0 to 255 (or // 0x00 to 0xFF) { return g2_ink(Handle, (double) Red / 0xFF, (double) Green / 0xFF, (double) Blue / 0xFF); } int Canvas::ink(const char* HexString) // new variant, where the components are chars (representing hex values) in the // range "00" to "FF" and concatenated as "RRGGBB" (like in HTML, but with out // the leading "#") { unsigned long Value = strtoul(HexString, NULL, 16); return ink((int) Value / 0x10000, (int) Value / 0x100 % 0x100, (int) Value % 0x100); } // These two functions select inks. // // (It's unclear why g2_set_background() has "set" in its name but g2_pen() // doesn't, but I've kept the same names so at least the inconsistency is // consistent. This probably isn't a useful thing for people using Gloobs who // don't know g2; maybe we should have further canvas class methods called // set_pen() and background() which do exactly the same things, so make it // easier for people?) void Canvas::pen(int Colr) // set a canvas's pen (foreground) colour to the specified ink { g2_pen(Handle, Colr); } void Canvas::set_background(int Colr) // set a cavas's background colour to the specified ink { g2_set_background(Handle, Colr); // png canvases seem to need clearing explicitly for this to come into // affect; this isn't needed with X11 canvases, and definitely isn't wanted // with PS canvases (where a call to g2_clear() inserts a page break): if (Type == PNG) { g2_clear(Handle); } } // These next few things set other parameters which affect subsequent // primitive-drawing commands. They are merely an interface to the g2 // routines; see the latter for more details: void Canvas::set_dash(int NumDashes, double* DashLngths) { g2_set_dash(Handle, NumDashes, DashLngths); } void Canvas::set_dash() // new overloaded variant which resets things to the default (a solid line with // no dashes) { g2_set_dash(Handle, 0, NULL); } void Canvas::set_line_width(double Width) { g2_set_line_width(Handle, Width); } void Canvas::set_font_size(double Size) { g2_set_font_size(Handle, Size); } // Assorted primitives for drawing on canvases follow. They have the same // names as the equivalent g2 routines, and (mostly) just pass the parameters // through. For details on what any primitive does, see the g2 documentation: void Canvas::move(double X, double Y) { g2_move(Handle, X, Y); } void Canvas::move_r(double DX, double DY) { g2_move_r(Handle, DX, DY); } void Canvas::plot(double X, double Y) { g2_plot(Handle, X, Y); } void Canvas::plot_r(double DX, double DY) { g2_plot_r(Handle, DX, DY); } void Canvas::set_QP(double Scale, enum QPshape Shape) { g2_set_QP(Handle, Scale, Shape); // keep a note of the scale separately, cos it comes in handy in plot_QP(): QPScale = Scale; } void Canvas::plot_QP(double X, double Y) { // for some reason the Scale parameter of g2_set_QP() causes the co-ordinates // of the QP to be scaled by that factor, as well as its size; this is // generally unwanted (and unexpected), so to circumvent it the co-ordinates // passed to this function will be divided by that scale factor before // g2_plot_QP() multiplies them up again(!): g2_plot_QP(Handle, X / QPScale, Y / QPScale); } void Canvas::line(double X0, double Y0, double X1, double Y1) { g2_line(Handle, X0, Y0, X1, Y1); } void Canvas::line_to(double X, double Y) { g2_line_to(Handle, X, Y); } void Canvas::line_r(double DX, double DY) { g2_line_r(Handle, DX, DY); } void Canvas::poly_line(int NumPts, double* Pts) { g2_poly_line(Handle, NumPts, Pts); } void Canvas::triangle(double X0, double Y0, double X1, double Y1, double X2, double Y2) { g2_triangle(Handle, X0, Y0, X1, Y1, X2, Y2); } void Canvas::filled_triangle(double X0, double Y0, double X1, double Y1, double X2, double Y2) { g2_filled_triangle(Handle, X0, Y0, X1, Y1, X2, Y2); } void Canvas::rectangle(double X0, double Y0, double X1, double Y1) { g2_rectangle(Handle, X0, Y0, X1, Y1); } void Canvas::filled_rectangle(double X0, double Y0, double X1, double Y1) { g2_filled_rectangle(Handle, X0, Y0, X1, Y1); } void Canvas::polygon(int NumPts, double* Pts) { g2_polygon(Handle, NumPts, Pts); } void Canvas::filled_polygon(int NumPts, double* Pts) { g2_filled_polygon(Handle, NumPts, Pts); } void Canvas::circle(double X, double Y, double Radius) { g2_circle(Handle, X, Y, Radius); } void Canvas::filled_circle(double X, double Y, double Radius) { g2_filled_circle(Handle, X, Y, Radius); } void Canvas::ellipse(double X, double Y, double XRadius, double YRadius) { g2_ellipse(Handle, X, Y, XRadius, YRadius); } void Canvas::filled_ellipse(double X, double Y, double XRadius, double YRadius) { g2_filled_ellipse(Handle, X, Y, XRadius, YRadius); } void Canvas::arc(double X, double Y, double XRadius, double YRadius, double StartAngle, double EndAngle) // Note that StartAngle and EndAngle are in degrees, despite the doc for // g2_arc() claiming that its angles should be radians. { g2_arc(Handle, X, Y, XRadius, YRadius, StartAngle, EndAngle); } void Canvas::filled_arc(double X, double Y, double XRadius, double YRadius, double StartAngle, double EndAngle) // Note that StartAngle and EndAngle are in degrees, despite the doc for // g2_filled_arc() claiming that its angles should be radians. { g2_filled_arc(Handle, X, Y, XRadius, YRadius, StartAngle, EndAngle); } void Canvas::string(double X, double Y, char* Text) { g2_string(Handle, X, Y, Text); } void Canvas::image(double X, double Y, int XSize, int YSize, int* Colrs) { g2_image(Handle, X, Y, XSize, YSize, Colrs); } // Below is the draw_object() method for drawing Gloobs::Object collections on // a canvas. There is (obviously) nothing like this in g2. There are four // overloaded variants. void Canvas::draw_object(Object& Object0, float XOffset, float YOffset, float Scale) // draw the specified Gloobs object on this canvas, scaled by a factor of Scale // and offset by (XOffset, YOffset) { // iterate through each of the primitives in the object, and get them to draw // themselves on this canvas: for (Iter PrimNum = Object0.begin(); PrimNum != Object0.end(); PrimNum++) { (*PrimNum)->draw(*this, XOffset, YOffset, Scale); } return; } void Canvas::draw_object(Object& Object0, float XOffset, float YOffset) // draw the specified object on this canvas; since there is no scale parameter, // assume a scale of 1 { draw_object(Object0, XOffset, YOffset, 1); } void Canvas::draw_object(Object& Object0, float Scale) // draw the specified object on this canvas; since there are no offset // parameters, do not offset the object { draw_object(Object0, 0, 0, Scale); } void Canvas::draw_object(Object& Object0) // draw the specified object on this canvas; do not offset it, and use a scale // of 1: { draw_object(Object0, 0, 0, 1); } // The classes representing various primitives are defined next. Remember that // these are all inherited from the Gloobs::Primitive class. They all have // constructors which take arguments (so that it isn't possible, for example, // to have in existent a rectangle whose co-ordinates are yet to be specified). // They all inherit a public variable called Pen (even the Move commands!), so // that they can be recoloured even when their actual types aren't handy. They // also all have a draw() method which takes a Canvas, x- and y-offsets, and // scale. These are needed to enable the Canvas::draw_object() methods to // work, and all parameters must be present. (So even the relative primitives // need to take the offsets, even though they never do anything with them.) // Generally the draw() methods call the appropriate primitive-drawing method // of the Canvas class. // // Generally the classes are given initial-caps versions of the names of the g2 // functions used to draw them, but there are a couple of exceptions to keep // them vaguely sensible. // // Note that filled versions of shapes aren't separate primitives; whether a // shape should be filled is specified by an argument to its constructor. In // particular, the Thickness0 argument (passed to the Thickness private // variable) specifies the line thickness of outline shapes. However, if it's // set to the constant Filled, then the shape will be filled instead. // // There's no (sane) way of specifying dashed lines in primitives. Since g2 // doesn't yet implement them for png canvases this is no big loss, but when // (if) g2 gets that functionality (or we find a large market of people wanting // only to use X11 or PS canvases), it could be slightly tricky. Anyone for // more overloading? Move::Move(double X0, double Y0) { X = X0; Y = Y0; } void Move::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { Canvas0.move(X * Scale + XOffset, Y * Scale + YOffset); } Move_R::Move_R(double DX0, double DY0) { DX = DX0; DY = DY0; } void Move_R::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { Canvas0.move_r(DX * Scale, DY * Scale); } // the Pixel class represents the primitive drawn with the g2_plot() function, // cos calling it the Plot class would be just too confusing: Pixel::Pixel(int Pen0, double X0, double Y0) { Pen = Pen0; X = X0; Y = Y0; } void Pixel::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { Canvas0.pen(Pen); Canvas0.plot(X * Scale + XOffset, Y * Scale + YOffset); } Pixel_R::Pixel_R(int Pen0, double DX0, double DY0) { Pen = Pen0; DX = DX0; DY = DY0; } void Pixel_R::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { Canvas0.pen(Pen); Canvas0.plot_r(DX * Scale, DY * Scale); } QP::QP(int Pen0, double Size0, QPshape Shape0, double X0, double Y0) { Pen = Pen0; Size = Size0; Shape = Shape0; X = X0; Y = Y0; } void QP::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { Canvas0.pen(Pen); Canvas0.set_QP(Size, Shape); Canvas0.plot_QP(X * Scale + XOffset, Y * Scale + YOffset); } Line::Line(int Pen0, double Thickness0, double X00, double Y00, double X10, double Y10) { Pen = Pen0; Thickness = Thickness0; X0 = X00; Y0 = Y00; X1 = X10; Y1 = Y10; } void Line::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { Canvas0.pen(Pen); Canvas0.set_line_width(Thickness * Scale); Canvas0.line(X0 * Scale + XOffset, Y0 * Scale + YOffset, X1 * Scale + XOffset, Y1 * Scale + YOffset); } Line_To::Line_To(int Pen0, double Thickness0, double X0, double Y0) { Pen = Pen0; Thickness = Thickness0; X = X0; Y = Y0; } void Line_To::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { Canvas0.pen(Pen); Canvas0.set_line_width(Thickness * Scale); Canvas0.line_to(X * Scale + XOffset, Y * Scale + YOffset); } Line_R::Line_R(int Pen0, double Thickness0, double DX0, double DY0) { Pen = Pen0; Thickness = Thickness0; DX = DX0; DY = DY0; } void Line_R::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { Canvas0.pen(Pen); Canvas0.set_line_width(Thickness * Scale); Canvas0.line_r(DX * Scale, DY * Scale); } Poly_Line::Poly_Line(int Pen0, double Thickness0, int NumPoints0, const double* Points0) { Pen = Pen0; Thickness = Thickness0; NumPoints = NumPoints0; // keep a copy of the contents of the array of points, not just a pointer to // them: Points = new double[NumPoints * 2]; if (Points == NULL) { cerr << "Gloobs: Out of memory when trying to construct a Poly_Line" << endl; exit(1); } for (int CoordNum = 0; CoordNum < NumPoints * 2; CoordNum++) Points[CoordNum] = Points0[CoordNum]; } void Poly_Line::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { // transforming the co-ords is awkward, cos it can't be done inline in the // call to Canvas::poly_line(), and the original values can't be corrupted in // case a copy is needed later, so need to create a modified copy: double* ScaledPoints; // modifying is a hastle, so first see if can get away without having to // bother: if (Scale == 1 && XOffset == 0 && YOffset == 0) ScaledPoints = Points; else { // grab some memory, and copy across each co-ordinate, scaling as we go: ScaledPoints = new double[NumPoints * 2]; if (ScaledPoints == NULL) { cerr << "Gloobs: Out of memory when trying to draw a Poly_Line" << endl; exit(1); } for (int CoordNum = 0; CoordNum < NumPoints * 2; CoordNum += 2) { ScaledPoints[CoordNum] = Points[CoordNum] * Scale + XOffset; ScaledPoints[CoordNum + 1] = Points[CoordNum + 1] * Scale + YOffset; } } // then can draw the thing: Canvas0.pen(Pen); Canvas0.set_line_width(Thickness * Scale); Canvas0.poly_line(NumPoints, ScaledPoints); } Poly_Line::~Poly_Line() // frees up memory { delete []Points; } Triangle::Triangle(int Pen0, double Thickness0, double X00, double Y00, double X10, double Y10, double X20, double Y20) { Pen = Pen0; Thickness = Thickness0; X0 = X00; Y0 = Y00; X1 = X10; Y1 = Y10; X2 = X20; Y2 = Y20; } void Triangle::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { Canvas0.pen(Pen); if (Thickness == Filled) { Canvas0.set_line_width(1); Canvas0.filled_triangle( X0 * Scale + XOffset, Y0 * Scale + YOffset, X1 * Scale + XOffset, Y1 * Scale + YOffset, X2 * Scale + XOffset, Y2 * Scale + YOffset); } else { Canvas0.set_line_width(Thickness * Scale); Canvas0.triangle(X0 * Scale + XOffset, Y0 * Scale + YOffset, X1 * Scale + XOffset, Y1 * Scale + YOffset, X2 * Scale + XOffset, Y2 * Scale + YOffset); } } Rectangle::Rectangle(int Pen0, double Thickness0, double X00, double Y00, double X10, double Y10) { Pen = Pen0; Thickness = Thickness0; X0 = X00; Y0 = Y00; X1 = X10; Y1 = Y10; } void Rectangle::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { Canvas0.pen(Pen); if (Thickness == Filled) { Canvas0.set_line_width(1); Canvas0.filled_rectangle(X0 * Scale + XOffset, Y0 * Scale + YOffset, X1 * Scale + XOffset, Y1 * Scale + YOffset); } else { Canvas0.set_line_width(Thickness * Scale); Canvas0.rectangle(X0 * Scale + XOffset, Y0 * Scale + YOffset, X1 * Scale + XOffset, Y1 * Scale + YOffset); } } Polygon::Polygon(int Pen0, double Thickness0, int NumPoints0, const double* Points0) { Pen = Pen0; Thickness = Thickness0; NumPoints = NumPoints0; // Polygons have the same properties as Poly_Lines; in fact this code is so // similar that arguably it should be abstracted to another function: Points = new double[NumPoints * 2]; if (Points == NULL) { cerr << "Gloobs: Out of memory when trying to construct a Polygon" << endl; exit(1); } for (int CoordNum = 0; CoordNum < NumPoints * 2; CoordNum++) Points[CoordNum] = Points0[CoordNum]; } void Polygon::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { // ditto: double* ScaledPoints; if (Scale == 1 && XOffset == 0 && YOffset == 0) ScaledPoints = Points; else { ScaledPoints = new double[NumPoints * 2]; if (ScaledPoints == NULL) { cerr << "Gloobs: Out of memory when trying to draw a Polygon" << endl; exit(1); } for (int CoordNum = 0; CoordNum < NumPoints * 2; CoordNum += 2) { ScaledPoints[CoordNum] = Points[CoordNum] * Scale + XOffset; ScaledPoints[CoordNum + 1] = Points[CoordNum + 1] * Scale + YOffset; } } Canvas0.pen(Pen); if (Thickness == Filled) { Canvas0.set_line_width(1); Canvas0.filled_polygon(NumPoints, ScaledPoints); } else { Canvas0.set_line_width(Thickness * Scale); Canvas0.polygon(NumPoints, ScaledPoints); } } Polygon::~Polygon() // frees up memory { delete []Points; } Circle::Circle(int Pen0, double Thickness0, double X0, double Y0, double Radius0) { Pen = Pen0; Thickness = Thickness0; X = X0; Y = Y0; Radius = Radius0; } void Circle::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { Canvas0.pen(Pen); if (Thickness == Filled) { Canvas0.set_line_width(1); Canvas0.filled_circle(X * Scale + XOffset, Y * Scale + YOffset, Radius * Scale); } else { Canvas0.set_line_width(Thickness * Scale); Canvas0.circle(X * Scale + XOffset, Y * Scale + YOffset, Radius * Scale); } } Ellipse::Ellipse(int Pen0, double Thickness0, double X0, double Y0, double XRadius0, double YRadius0) { Pen = Pen0; Thickness = Thickness0; X = X0; Y = Y0; XRadius = XRadius0; YRadius = YRadius0; } void Ellipse::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { Canvas0.pen(Pen); if (Thickness == Filled) { Canvas0.set_line_width(1); Canvas0.filled_ellipse(X * Scale + XOffset, Y * Scale + YOffset, XRadius * Scale, YRadius * Scale); } else { Canvas0.set_line_width(Thickness * Scale); Canvas0.ellipse(X * Scale + XOffset, Y * Scale + YOffset, XRadius * Scale, YRadius * Scale); } } Arc::Arc(int Pen0, double Thickness0, double X0, double Y0, double XRadius0, double YRadius0, double StartAngle0, double EndAngle0) { Pen = Pen0; Thickness = Thickness0; X = X0; Y = Y0; XRadius = XRadius0; YRadius = YRadius0; StartAngle = StartAngle0; EndAngle = EndAngle0; } void Arc::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { Canvas0.pen(Pen); if (Thickness == Filled) { Canvas0.set_line_width(1); Canvas0.filled_arc(X * Scale + XOffset, Y * Scale + YOffset, XRadius * Scale, YRadius * Scale, StartAngle, EndAngle); } else { Canvas0.set_line_width(Thickness * Scale); Canvas0.arc(X * Scale + XOffset, Y * Scale + YOffset, XRadius * Scale, YRadius * Scale, StartAngle, EndAngle); } } String::String(int Pen0, double Size0, double X0, double Y0, const char* Text0) { Pen = Pen0; Size = Size0; X = X0; Y = Y0; // text needs copying properly, too: Text = new char[strlen(Text0) + 1]; if (Text == NULL) { cerr << "Gloobs: Out of memory when trying to construct a String" << endl; exit(1); } strcpy(Text, Text0); } void String::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) { Canvas0.pen(Pen); // fortunately scaling text is easy enough: Canvas0.set_font_size(Size * Scale); Canvas0.string(X * Scale + XOffset, Y * Scale + YOffset, Text); } String::~String() // frees up memory { delete []Text; } // the Bitmap class represents the `primitive' drawn with the g2_image() // function, cos calling it the Image class could get a little confusing: Bitmap::Bitmap(double X0, double Y0, int Width0, int Height0, int* Colrs0) { X = X0; Y = Y0; Width = Width0; Height = Height0; // not a particular efficient use of memory, really: Colrs = new int[Width * Height]; if (Colrs == NULL) { cerr << "Gloobs: Out of memory when trying to construct a Bitmap" << endl; exit(1); } // lets copy across each pixel, one at a time: for (int PixelNum = 0; PixelNum < Width * Height; PixelNum++) Colrs[PixelNum] = Colrs0[PixelNum]; } void Bitmap::draw(PretendCanvas& Canvas0, float XOffset, float YOffset, float Scale) // note that Scale does _not_ scale the image; it will remain the same size, // though it will scale the original origin, though that won't have any affect // if it was (0,0) { Canvas0.pen(Pen); Canvas0.image(X * Scale + XOffset, Y * Scale + YOffset, Width, Height, Colrs); } Bitmap::~Bitmap() // frees up memory { delete []Colrs; } // end of gloobs.cc