/** * @file draw.h * @brief Declares the public draw API. * * Immediate-mode plotting against whichever renderer the caller hands in. This * is the shape a BASIC-style graphics vocabulary needs -- DRAW, BOX, CIRCLE, * PAINT, SSHAPE and GSHAPE all say "put this on the screen now" rather than * "add this to the scene" -- and it sits alongside the actor and tilemap * rendering rather than replacing it. * * Every entry point takes its color as an argument instead of reading a * current-color global. A caller that has a notion of a current color (a BASIC * COLOR statement, say) already owns that state and does not need the library * to keep a second copy that can disagree with it. The renderer's own draw * color is saved and restored around each call, so drawing a line never changes * what the next SDL_RenderClear() paints. */ #ifndef _AKGL_DRAW_H_ #define _AKGL_DRAW_H_ #include #include #include #include /** * @brief Spans akgl_draw_flood_fill() will hold while walking a region. * * The fill keeps a fixed stack of horizontal runs still to be examined rather * than recursing per pixel. A region complicated enough to need more than this * many pending runs at once reports AKERR_OUTOFBOUNDS instead of overflowing; * an ordinary convex or moderately concave shape needs a few dozen. */ #define AKGL_DRAW_MAX_FLOOD_SPANS 4096 /** * @brief Paint an 8x8 grey checkerboard over a region, the way an image editor shows transparency. * * A diagnostic backdrop rather than a general primitive -- `charviewer` uses it * so a sprite's transparent pixels are visible instead of blending into black. * * Until 0.5.0 this was the one function in the library outside the error * protocol: it returned `void`, drew through the *global* `akgl_renderer` * without checking it, and left the renderer's draw colour changed. It now * takes a backend like every other entry point here and restores the colour it * found, which is also what makes it testable without a world. * * @param self The backend to draw through. Required, along with its * `sdl_renderer`. * @param w Width of the region to cover, in pixels, starting at x = 0. Zero * or negative paints nothing and is not reported. * @param h Height of the region, starting at y = 0. Both round up to whole * 8px cells, so a 12-pixel height paints 16. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p self or `self->sdl_renderer` is `NULL`. * @throws AKGL_ERR_SDL If the draw colour cannot be read or set, or a cell * cannot be filled. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_background(akgl_RenderBackend *self, int w, int h); /** * @brief Plot a single pixel. * @param self The backend to draw through. Required, along with its * `sdl_renderer`. * @param x Horizontal position in render-target pixels. Outside the target * is clipped by SDL, not reported. * @param y Vertical position. * @param color Colour to plot in, including alpha. Blending follows the * renderer's current blend mode, which this does not change. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p self or `self->sdl_renderer` is `NULL`. * @throws AKGL_ERR_SDL If the draw colour cannot be read or set, or if the plot * itself fails. The message carries `SDL_GetError()`. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_point(akgl_RenderBackend *self, float32_t x, float32_t y, SDL_Color color); /** * @brief Draw a line between two points, endpoints included. * @param self The backend to draw through. Required, along with its * `sdl_renderer`. * @param x1 Horizontal position of the first endpoint. * @param y1 Vertical position of the first endpoint. * @param x2 Horizontal position of the second endpoint. * @param y2 Vertical position of the second endpoint. Coincident endpoints * draw a single pixel rather than nothing. * @param color Colour to draw in, including alpha. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p self or `self->sdl_renderer` is `NULL`. * @throws AKGL_ERR_SDL If the draw colour cannot be read or set, or if the line * cannot be drawn. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_line(akgl_RenderBackend *self, float32_t x1, float32_t y1, float32_t x2, float32_t y2, SDL_Color color); /** * @brief Draw the outline of a rectangle. * @param self The backend to draw through. Required, along with its * `sdl_renderer`. * @param rect The rectangle, in render-target pixels. Required. A zero or * negative width or height draws nothing and is not reported. * @param color Colour to draw in, including alpha. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p self, `self->sdl_renderer`, or @p rect is * `NULL`. * @throws AKGL_ERR_SDL If the draw colour cannot be read or set, or if the * outline cannot be drawn. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_rect(akgl_RenderBackend *self, SDL_FRect *rect, SDL_Color color); /** * @brief Fill a rectangle, outline included. * @param self The backend to draw through. Required, along with its * `sdl_renderer`. * @param rect The rectangle, in render-target pixels. Required. A zero or * negative width or height fills nothing and is not reported. * @param color Colour to fill with, including alpha. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p self, `self->sdl_renderer`, or @p rect is * `NULL`. * @throws AKGL_ERR_SDL If the draw colour cannot be read or set, or if the fill * cannot be drawn. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_filled_rect(akgl_RenderBackend *self, SDL_FRect *rect, SDL_Color color); /** * @brief Draw the outline of a circle. * * SDL3 has no circle primitive, so this plots one with the midpoint circle * algorithm -- integer arithmetic, eight-way symmetry, one pass per octant. A * radius of zero draws the center pixel and nothing else. * * @param self The backend to draw through. Required, along with its * `sdl_renderer`. * @param x Horizontal position of the centre. Rounded to the nearest whole * pixel -- the algorithm is integer-only. * @param y Vertical position of the centre, rounded the same way. * @param radius Radius in pixels, rounded to the nearest whole pixel. Must not * be negative; 0 draws the centre pixel alone. * @param color Colour to draw in, including alpha. The outline is one pixel * wide and is not anti-aliased. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p self or `self->sdl_renderer` is `NULL`. * @throws AKERR_OUTOFBOUNDS If @p radius is negative. The message reports it. * @throws AKGL_ERR_SDL If the draw colour cannot be read or set, or if any of * the plotting passes fails. Failures inside the loop are recorded and * reported once at the end rather than aborting mid-circle, so a partial * arc may already be on the target. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_circle(akgl_RenderBackend *self, float32_t x, float32_t y, float32_t radius, SDL_Color color); /** * @brief Flood the connected region containing one pixel with a color. * * SDL3 has no flood fill either, and unlike the shape primitives it cannot be * done on the GPU side: the region is defined by what is already on the screen. * This reads the render target back, walks the region on the CPU with a * bounded span stack, and blits the result over the area it touched. * * Filling a region that already holds @p color is a no-op rather than an error. * A seed outside the render target reports AKERR_OUTOFBOUNDS. * * The region is four-connected -- it spreads up, down, left and right, not * diagonally -- and its boundary is any pixel whose colour differs from the * seed's, exactly. There is no tolerance, so an anti-aliased edge stops the fill * at its first blended pixel and leaves a fringe. * * @param self The backend to draw through. Required, along with its * `sdl_renderer`. * @param x Horizontal position of the seed pixel, in render-target pixels. * Must be inside the target. * @param y Vertical position of the seed pixel. * @param color Colour to fill with. Written over the region rather than blended, * since the pixels going back are the ones just read out. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p self or `self->sdl_renderer` is `NULL`. * @throws AKERR_OUTOFBOUNDS If the seed is outside the render target -- the * message reports both the seed and the target size -- or if the region * needs more than #AKGL_DRAW_MAX_FLOOD_SPANS pending spans, in which * case **it is left partially filled**. * @throws AKGL_ERR_SDL If the render target cannot be read back, converted, * uploaded, or blitted. * * @warning Not reentrant, and not safe from two threads: the span stack is a * single file-scope array. Nothing that touches an `SDL_Renderer` is * thread-safe either way. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_flood_fill(akgl_RenderBackend *self, int x, int y, SDL_Color color); /** * @brief Read a rectangle of the render target into a surface. * * The save half of SSHAPE/GSHAPE. When `*dest` is `NULL` the function allocates * the surface and the caller owns it from then on -- release it with * SDL_DestroySurface(). When `*dest` already points at a surface of exactly * @p src's dimensions the pixels are copied into it instead, so a caller * saving the same region repeatedly does not churn allocations. * * @param self The backend to read from. Required, along with its `sdl_renderer`. * @param src Rectangle of the render target to read, in pixels. Required. It * must fit *entirely* inside the target: SDL would otherwise clip * the read and hand back a smaller surface than was asked for, which * a caller pasting it back would not notice. * @param dest Address of the destination surface. Required, and `*dest` must be * initialized -- `NULL` to have one allocated (the caller then owns * it and frees it with `SDL_DestroySurface`), or an existing surface * of exactly @p src's dimensions to reuse. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p self, `self->sdl_renderer`, @p src, or @p dest * is `NULL`. * @throws AKERR_OUTOFBOUNDS If @p src has no area, if it does not fit inside the * render target, or if a supplied `*dest` is a different size from * @p src. Each message reports both sets of dimensions. * @throws AKGL_ERR_SDL If the target size cannot be queried, the pixels cannot * be read, or the copy into a supplied `*dest` fails. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_copy_region(akgl_RenderBackend *self, SDL_Rect *src, SDL_Surface **dest); /** * @brief Draw a saved surface back onto the render target. * * The restore half of SSHAPE/GSHAPE, taking what akgl_draw_copy_region() * produced. The surface is not consumed and may be pasted as many times as the * caller likes. * * @param self The backend to draw through. Required, along with its * `sdl_renderer`. * @param src The surface to paste. Required. Drawn at its own size -- there is * no scaling -- and *replacing* what is on the target rather than * blending with it, which is GSHAPE's default behaviour and means a * saved region's transparent pixels come back as transparent rather * than letting the background show through. * @param x Horizontal position of the paste's left edge. * @param y Vertical position of its top edge. Parts falling outside the * target are clipped by SDL, not reported. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p self, `self->sdl_renderer`, or @p src is * `NULL`. * @throws AKGL_ERR_SDL If the surface cannot be uploaded as a texture, its blend * mode cannot be set, or the draw fails. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_paste_region(akgl_RenderBackend *self, SDL_Surface *src, float32_t x, float32_t y); #endif //_AKGL_DRAW_H_