/** * @file graphics.h * @brief Declares the graphics backend: where DRAW, BOX, CIRCLE and PAINT land. * * The core library is free of SDL and builds with no libakgl present, so the * graphics verbs cannot call akgl_draw_* directly. They call through a record of * function pointers instead -- the same shape as akbasic_TextSink, and the same * shape libakgl itself uses for akgl_RenderBackend and akgl_PhysicsBackend. * * akbasic_graphics_init_akgl() lives in the separate akbasic_akgl target and * draws through whatever renderer the host game already initialized. A host that * renders some other way supplies its own record and never links libakgl at all. * * A runtime with no graphics backend is the normal case -- the standalone driver * has none -- so every graphics verb refuses with AKBASIC_ERR_DEVICE rather than * dereferencing a NULL vtable. */ #ifndef _AKBASIC_GRAPHICS_H_ #define _AKBASIC_GRAPHICS_H_ #include #include /** * @brief The coordinate space assumed when the device will not say how big it is. * * A coordinate reaching the backend is a *device pixel*: `DRAW 1, 799, 599` lands * at the bottom-right of an 800x600 window, and every pixel of that window is * addressable from BASIC. The interpreter still does not own the renderer -- it * asks, through akbasic_GraphicsBackend::size(), and the backend that does own * one answers. * * These constants are what is assumed when nobody answers: a backend that leaves * `size` NULL, or reports a nonsense size, gets BASIC 7.0's 320x200 hi-res * screen. That is the right fallback rather than a guess, because it is the space * a C128 listing was written for. */ #define AKBASIC_GRAPHICS_WIDTH 320 #define AKBASIC_GRAPHICS_HEIGHT 200 /** @brief How many COLOR sources there are. BASIC 7.0 numbers them 0 through 6. */ #define AKBASIC_COLOR_SOURCES 7 /** * @brief An RGBA color, in the shape SDL_Color has without requiring SDL. * * BASIC never names a color this way -- COLOR takes a source and a 1-16 palette * index -- so this is what the palette table in src/graphics_tables.c converts * to, and what the backend receives. */ typedef struct { uint8_t r; uint8_t g; uint8_t b; uint8_t a; } akbasic_Color; /** * @brief The graphics verbs' own state, which lives on the runtime. * * DRAW, BOX, CIRCLE and PAINT name a *color source*, not a color: COLOR binds a * source to a palette index and the drawing verbs reference the source. That * indirection is BASIC 7.0's, not an invention here, and it is why `source` is a * table of palette indices rather than a single current color. */ typedef struct { int mode; /* GRAPHIC mode; 0 is text, and refuses to draw */ int source[AKBASIC_COLOR_SOURCES]; /* palette index bound to each COLOR source */ double x; /* pixel cursor: where LOCATE put it, or where */ double y; /* the last DRAW ended */ bool scaling; /* SCALE on */ int linewidth; /* WIDTH: 1 or 2 pixels per drawn line */ double xmax; /* user-coordinate maxima SCALE maps from */ double ymax; int devwidth; /* what the device last said it is, in pixels; */ int devheight; /* the AKBASIC_GRAPHICS_* fallback until then */ } akbasic_GraphicsState; /** * @brief Where the graphics verbs draw. * * Coordinates are `double` rather than an integer pixel address because SCALE * makes them fractional: a program running at a 1023x1023 logical scale on a * 320x200 screen produces non-integer pixel positions, and rounding at each verb * rather than once at the backend accumulates visible drift along a polyline. * * There is deliberately no circle entry point. BASIC 7.0's CIRCLE takes two * radii, a start and end angle, a rotation and a degree increment -- it is a * polygon by definition -- so it is built from `line` calls in the verb handler. * See TODO.md section 5. */ typedef struct akbasic_GraphicsBackend { void *self; /** * How big the drawing surface is, in pixels. * * **Optional, and the only optional entry point in the record.** It was * added after the rest, so a host that filled one of these in by hand * before it existed leaves it NULL and keeps the 320x200 fallback rather * than crashing; and a backend drawing somewhere with no meaningful size * has an honest way to say so. Everything else here is required. * * Called before each verb draws rather than once at attach, because a * window is resizable and a script that runs for an hour should not be * drawing to the size the window was when it started. */ akerr_ErrorContext AKERR_NOIGNORE *(*size)(struct akbasic_GraphicsBackend *self, int *width, int *height); /** Plot one pixel. */ akerr_ErrorContext AKERR_NOIGNORE *(*point)(struct akbasic_GraphicsBackend *self, double x, double y, akbasic_Color color); /** Draw a line between two points. */ akerr_ErrorContext AKERR_NOIGNORE *(*line)(struct akbasic_GraphicsBackend *self, double x1, double y1, double x2, double y2, akbasic_Color color); /** Outline an axis-aligned rectangle. A rotated BOX becomes four `line` calls. */ akerr_ErrorContext AKERR_NOIGNORE *(*rect)(struct akbasic_GraphicsBackend *self, double x1, double y1, double x2, double y2, akbasic_Color color); /** Fill an axis-aligned rectangle. */ akerr_ErrorContext AKERR_NOIGNORE *(*filled_rect)(struct akbasic_GraphicsBackend *self, double x1, double y1, double x2, double y2, akbasic_Color color); /** * Flood-fill the region containing (x, y). * * May report AKERR_OUTOFBOUNDS having filled only part of the region: the * akgl implementation walks spans from a fixed stack and gives up rather * than growing it. PAINT reports that to the program instead of leaving a * half-painted screen unexplained. */ akerr_ErrorContext AKERR_NOIGNORE *(*paint)(struct akbasic_GraphicsBackend *self, int x, int y, akbasic_Color color); /** Clear the whole drawing surface to one color. */ akerr_ErrorContext AKERR_NOIGNORE *(*clear)(struct akbasic_GraphicsBackend *self, akbasic_Color color); /** * Save a rectangular region and yield an opaque handle to it. * * SSHAPE stores the handle in a BASIC string, not the pixels -- see TODO.md * section 5 for what that deviation costs. */ akerr_ErrorContext AKERR_NOIGNORE *(*save_shape)(struct akbasic_GraphicsBackend *self, int x1, int y1, int x2, int y2, int *handle); /** Blit a saved region back at (x, y). */ akerr_ErrorContext AKERR_NOIGNORE *(*paste_shape)(struct akbasic_GraphicsBackend *self, int handle, double x, double y); /** Release every saved region. NEW and CLR call this; nothing else reclaims a slot. */ akerr_ErrorContext AKERR_NOIGNORE *(*free_shapes)(struct akbasic_GraphicsBackend *self); } akbasic_GraphicsBackend; /** * @brief Convert a BASIC color index to the RGBA the backend draws with. * * BASIC 7.0 numbers its sixteen colors from 1, not 0; index 17 is the "current" * color on a real C128 and has no meaning here. * * @param index Palette index, 1 through 16. * @param dest Output destination populated by the function. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER When `dest` is NULL. * @throws AKBASIC_ERR_BOUNDS When `index` is outside 1..16. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_palette(int index, akbasic_Color *dest); /** * @brief Reset the graphics state to its power-on values. * * Text mode, the default color-source bindings, the pixel cursor at the origin * and scaling off. GRAPHIC CLR and NEW both come back through here. * * @param obj Object to initialize, inspect, or modify. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER When `obj` is NULL. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_state_init(akbasic_GraphicsState *obj); /** * @brief Resolve a COLOR source to the RGBA the backend draws with. * @param obj Graphics state to read the binding from. * @param source COLOR source number, 0 through 6. * @param dest Output destination populated by the function. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER When `obj` or `dest` is NULL. * @throws AKBASIC_ERR_BOUNDS When `source` is outside 0..6. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_source_color(akbasic_GraphicsState *obj, int source, akbasic_Color *dest); #endif // _AKBASIC_GRAPHICS_H_