gint/include/gint/image.h

373 lines
14 KiB
C

//---
// gint:image - Image manipulation and rendering
//
// Note: this module is currently only available on fx-CG.
//
// This header provides image manipulation functions. This mainly consists of a
// reference-based image format, various access and modification functions, and
// a number of high-performance transformations and rendering effects. If you
// find yourself limited by rendering time, note that RAM writing speed is
// often the bottleneck, and image rendering is much faster in Azur (which is
// what the renderer was initially designed for).
//
// We support 3 bit depths: full-color 16-bit (RGB565), indexed 8-bit (P8) and
// indexed 4-bit (P4). All three have an "alpha" variation where one color is
// treated as transparent, leading to 6 total formats.
//
// The image renderers support so-called *dynamic effects*, which are image
// transformations performed on-the-fly while rendering, without generating an
// intermediate image. They comprise straightforward transformations that
// achieve similar performance to straight rendering and can be combined to
// some extent, which makes them reliable whenever applicable.
//
// TODO: Switch to libimg-style image refs.
//---
#ifndef GINT_IMAGE
#define GINT_IMAGE
#ifdef __cplusplus
extern "C" {
#endif
#ifndef FXCG50
#error <gint/image.h> is only supported on FXCG50
#else
#include <gint/defs/attributes.h>
#include <gint/defs/types.h>
//---
// Image structures
//---
/* Image formats. Note that transparency really only indicates the default
rendering method, as a transparent background can always be added or removed
by a dynamic effect on any image. */
enum {
IMAGE_RGB565 = 0, /* RGB565 without alpha */
IMAGE_RGB565A = 1, /* RGB565 with one transparent color */
IMAGE_P8_RGB565 = 4, /* 8-bit palette, all opaque colors */
IMAGE_P8_RGB565A = 5, /* 8-bit with one transparent color */
IMAGE_P4_RGB565 = 6, /* 4-bit palette, all opaque colors */
IMAGE_P4_RGB565A = 3, /* 4-bit with one transparent color */
IMAGE_DEPRECATED_P8 = 2,
};
/* image_t: gint's native bitmap image format
Images of this format can be created through this header's API but also by
using the fxSDK's built-in image converters with fxconv. */
typedef struct
{
/* Color format, one of the IMAGE_* values defined above. */
uint16_t profile;
/* For formats with alpha, value or index used for transparency. */
uint16_t alpha;
/* Full width and height, in pixels */
uint16_t width;
uint16_t height;
/* Here we lose structure because of the flexible array.
RGB565, RGB565A:
* Pixels in row-major order, 16 bits per pixel
P8:
* Palette with 256 entries (512 bytes total)
* Pixels in row-major order, 8 bits per pixel
P8_RGB565A, P8_RGB565:
* Number of entries in palette, N (2 bytes)
* Palette with N entries (2N bytes)
* Pixels in row-major order, 8 bits per pixel (signed indices in
an uint16_t array starting at <palette>+<256 bytes>)
P4/P4_RGB565A, P4_RGB565:
* Palette with 16 entries (32 bytes total)
* Pixels in row-major order, 4 bits per pixel, each row
byte-padded */
uint16_t data[];
} GPACKED(4) image_t;
/* Dynamic effects: these transformations can be applied on images while
rendering. Not all effects can be combined; unless specified otherwise:
- HFLIP and VFLIP can both be added regardless of any other effect
- At most one color effect can be applied */
enum {
/* Value 0x01 is reserved, because it is DIMAGE_NOCLIP, which although
part of the old API still needs to be supported. */
/* [Any]: Skip clipping the command against the source image */
IMAGE_NOCLIP_INPUT = 0x04,
/* [Any]: Skip clipping the command against the output VRAM */
IMAGE_NOCLIP_OUTPUT = 0x08,
/* [Any]: Skip clipping both */
IMAGE_NOCLIP = IMAGE_NOCLIP_INPUT | IMAGE_NOCLIP_OUTPUT,
// Geometric effects. These values should remain at exactly bit 8 and
// following, or change gint_image_mkcmd() along with it.
/* [Any]: Flip image vertically */
IMAGE_VFLIP = 0x0100,
/* [Any]: Flip image horizontally */
IMAGE_HFLIP = 0x0200,
// Color effects
/* [RGB565, P8_RGB565, P4_RGB565]: Make a color transparent
Adds one argument:
* Color to clear (RGB16: 16-bit value; P8/P4: palette index) */
IMAGE_CLEARBG = 0x10,
/* [RGB565, P8_RGB565, P4_RGB565]: Turn a color into another
Adds two arguments:
* Color to replace (RGB16: 16-bit value; P8/P4: palette index)
* Replacement color (16-bit value) */
IMAGE_SWAPCOLOR = 0x20,
/* [RGB565A, P8_RGB565A, P4_RGB565A]: Add a background
Adds one argument:
* Background color (16-bit value) */
IMAGE_ADDBG = 0x40,
/* [RGB565A, P8_RGB565A, P4_RGB565A]: Dye all non-transparent pixels
Adds one argument:
* Dye color (16-bit value) */
IMAGE_DYE = 0x80,
};
//---
// Image access and information
//---
/* TODO: Expand */
int image_get_pixel(image_t const *img, int x, int y);
int image_decode_pixel(image_t const *img, int pixel);
//---
// Image rendering functions
//
// The following functions extend dimage() and dsubimage(). The [effects]
// parameter takes a combination of IMAGE_* flags and effects, limited to the
// combinations previously described, with additional arguments depending on
// the color effect being applied.
//
// dimage_effect(x, y, img, effects, ...)
// dsubimage_effect(x, y, img, left, top, w, h, effects, ...)
//
// However if you use these super-generic functions you will link the code for
// all effects and all formats into your add-in, which takes a fair amount of
// space. If that's a problem, you can use the more specific functions below:
//
// * dimage_<FORMAT>_<EFFECT>() for one particular format (rgb16, p8, p4) along
// with one particular color effect (clearbg, swapcolor, addbg, dye).
// * dimage_<FORMAT>() is like the above when no color effect is applied.
//
// All of them support the HFLIP and VFLIP flags. For effect-specific functions
// the corresponding effect flag can be omitted (fi. IMAGE_CLEARBG is implicit
// when using dimage_p8_clearbg()).
//---
/* dimage_effect(): Generalized dimage() supporting dynamic effects */
#define dimage_effect(x, y, img, eff, ...) \
dsubimage_effect(x, y, img, 0, 0, (img)->width, (img)->height, eff, \
##__VA_ARGS__)
/* dsubimage_effect(): Generalized dsubimage() supporting dynamic effects */
void dsubimage_effect(int x, int y, image_t const *img,
int left, int top, int w, int h, int effects, ...);
/* Specific versions for each format */
#define DIMAGE_SIG1(NAME, ...) \
void dimage_ ## NAME(int x, int y, image_t const *img,##__VA_ARGS__); \
void dsubimage_ ## NAME(int x, int y, image_t const *img, \
int left, int top, int w, int h, ##__VA_ARGS__);
#define DIMAGE_SIG(NAME, ...) \
DIMAGE_SIG1(rgb16 ## NAME, ##__VA_ARGS__) \
DIMAGE_SIG1(p8 ## NAME, ##__VA_ARGS__) \
DIMAGE_SIG1(p4 ## NAME, ##__VA_ARGS__)
/* d[sub]image_{rgb16,p8,p4}_effect(..., effects, <extra arguments>) */
DIMAGE_SIG(_effect, int effects, ...)
/* d[sub]image_{rgb16,p8,p4}(..., effects) (no color effect, like dimage()) */
DIMAGE_SIG(, int effects)
/* d[sub]image_{rgb16,p8,p4}_clearbg(..., effects, bg_color_or_index) */
DIMAGE_SIG(_clearbg, int effects, int bg_color_or_index)
/* d[sub]image_{rgb16,p8,p4}_swapcolor(..., effects, source, replacement) */
DIMAGE_SIG(_swapcolor, int effects, int source, int replacement)
/* d[sub]image_{rgb16,p8,p4}_addbg(..., effects, bg_color) */
DIMAGE_SIG(_addbg, int effects, int bg_color)
/* d[sub]image_{rgb16,p8,p4}_dye(..., effects, dye_color) */
DIMAGE_SIG(_dye, int effects, int dye_color)
/* d[sub]image_p4_clearbg_alt(..., effects, bg_index)
This is functionally identical to CLEARBG, but it uses an alternative
rendering method that is faster for larger images with wide transparent
areas. You can swap it with the normal CLEARBG freely. */
DIMAGE_SIG1(p4_clearbg_alt, int effects, int bg_index)
#define dimage_rgb16_effect(x, y, img, eff, ...) \
dsubimage_rgb16_effect(x, y, img, 0, 0, (img)->width, (img)->height, \
eff, ##__VA_ARGS__)
#define dimage_p8_effect(x, y, img, eff, ...) \
dsubimage_p8_effect(x, y, img, 0, 0, (img)->width, (img)->height, \
eff, ##__VA_ARGS__)
#define dimage_p4_effect(x, y, img, eff, ...) \
dsubimage_p4_effect(x, y, img, 0, 0, (img)->width, (img)->height, \
eff, ##__VA_ARGS__)
#undef DIMAGE_SIG
#undef DIMAGE_SIG1
//---
// Clipping utilities
//---
/* Double box specifying both a source and target area */
struct gint_image_box
{
/* Target location of top-left corner */
int x, y;
/* Width and height of rendered sub-image */
int w, h;
/* Source bounding box (low included, high excluded) */
int left, top;
};
/* Clip the provided box against the input. If, after clipping, the box no
longer intersects the output (whose size is specified as out_w/out_h),
returns false. Otherwise, returns true. */
bool gint_image_clip_input(image_t const *img, struct gint_image_box *box,
int out_w, int out_h);
/* Clip the provided box against the output. */
void gint_image_clip_output(struct gint_image_box *b, int out_w, int out_h);
//---
// Internal image rendering routines
//
// The following functions (or non-functions) are implemented in assembler and
// make up the internal interface of the image renderer. If you just want to
// display images, use dimage() and variations; these are only useful if you
// have a different rendering system and wish to use image rendering with
// dynamic effects in it.
//---
/* Renderer command. This structure includes most of the information used by
the image renderer to perform blits. Some of the information on the target
is also passed as direct arguments, which is more convenient and slightly
faster.
Most of the values here can be set with gint_image_mkcmd(). The last two
members, along with the return values of the gint_image_FORMAT_loop()
functions, are used to update the command if one needs to draw *parts* of
the image and resume the rendering later. This is used in Azur. */
struct gint_image_cmd
{
/* Shader ID. This is used in Azur, and ignored in gint */
uint8_t shader_id;
/* Dynamic effects
Bit 0: VFLIP
Bit 1: HFLIP
Bits 2-7: 0=NONE, 1=CLEARBG, 2=SWAPCOLOR, 3=DYE */
uint8_t effect;
/* Number of pixels to render per line. For formats that force either x
or width alignment (most of them), this is already adjusted to a
suitable multiple (usually a multiple of 2). */
int16_t columns;
/* Stride of the input image (number of pixels between each row), in
pixels, without subtracting the number of columns */
int16_t input_stride;
/* Number of lines in the command. This can be adjusted freely, and is
particularly useful in Azur for fragmented rendering. */
uint8_t lines;
/* [Any effect]: Offset of first edge */
int8_t edge_1;
/* Core loop; this is an internal label of the renderer */
void const *loop;
/* Output pixel array, offset by target x/y */
void const *output;
/* Input pixel array, offset by source x/y. For formats that force x
alignment, this is already adjusted. */
void const *input;
/* Palette, when applicable */
uint16_t const *palette;
/* [Any effect]: Offset of right edge */
int16_t edge_2;
/* [CLEARBG, SWAPCOLOR]: Source color */
uint16_t color_1;
/* [SWAPCOLOR]: Destination color */
uint16_t color_2;
/* Remaining height (for updates between fragments) */
int16_t height;
/* Local x position (for updates between fragments) */
int16_t x;
};
/* gint_image_mkcmd(): Prepare a rendering command with dynamic effects
This function crafts an image renderer command. It loads all the settings
except for effect-dependent parameters: the [.loop] label, the color section
of [.effect], and color effect settings. See the effect-specific functions
to see how they are defined.
The benefit of this approach is that the rendering code does not need to be
linked in unless an effect is actually used, which avoids blowing up the
size of the add-in as the number of support dynamic effects increases.
@box Requested on-screen box (will be clipped depending on effects)
@img Source image
@effects Set of dynamic effects to be applied, as an [IMAGE_*] bitmask
@left_edge Whether to force 2-alignment on the input (box->left)
@right_edge Whether to force 2-alignment on the width
@cmd Command to be filled
@out_width Output width (usually DWIDTH)
@out_height Output height (usually DHEIGHT)
Returns false if there is nothing to render because of clipping (in which
case [cmd] is unchanged), true otherwise. [*box] is also updated to reflect
the final box after clipping but not accounting for edges. */
bool gint_image_mkcmd(struct gint_image_box *box, image_t const *img,
int effects, bool left_edge, bool right_edge,
struct gint_image_cmd *cmd, int out_width, int out_height);
/* Entry point of the renderers. These functions can be called normally as long
as you can build the commands (eg. by using gint_image_mkcmd() then filling
the effect-specific information). */
void *gint_image_rgb16_loop (int output_width, struct gint_image_cmd *cmd);
void *gint_image_p8_loop (int output_width, struct gint_image_cmd *cmd);
void *gint_image_p4_loop (int output_width, struct gint_image_cmd *cmd);
/* Renderer fragments. The following can absolutely not be called from C code
as they aren't full functions (and this isn't their prototype). These are
continuations to be specified in the [.loop] field of a command before using
one of the functions above. */
void gint_image_rgb16_normal(void);
void gint_image_rgb16_clearbg(void);
void gint_image_rgb16_swapcolor(void);
void gint_image_rgb16_dye(void);
void gint_image_p8_normal(void);
void gint_image_p8_clearbg(void);
void gint_image_p8_swapcolor(void);
void gint_image_p8_dye(void);
void gint_image_p4_normal(void);
void gint_image_p4_clearbg(void);
void gint_image_p4_clearbg_alt(void);
void gint_image_p4_swapcolor(void);
void gint_image_p4_dye(void);
#endif /* FXCG50 */
#ifdef __cplusplus
}
#endif
#endif /* GINT_IMAGE */