image: new image format and base for the image library

This commit is contained in:
Lephe 2022-05-13 23:30:52 +01:00
parent 7822899b1f
commit 5a69e44078
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
25 changed files with 847 additions and 153 deletions

View File

@ -157,6 +157,23 @@ set(SOURCES_FX
set(SOURCES_CG
# R61524 driver
src/r61524/r61524.c
# Image library
src/image/image_alloc.c
src/image/image_clear.c
src/image/image_copy.c
src/image/image_copy_palette.c
src/image/image_create.c
src/image/image_create_vram.c
src/image/image_data_size.c
src/image/image_decode_pixel.c
src/image/image_fill.c
src/image/image_free.c
src/image/image_get_pixel.c
src/image/image_palette_size.c
src/image/image_set_pixel.c
src/image/image_sub.c
src/image/image_target.c
src/image/image_valid.c
# Rendering
src/render-cg/dclear.c
src/render-cg/dpixel.c

View File

@ -10,9 +10,9 @@
// 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.
// This module supports 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
@ -20,7 +20,11 @@
// 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.
// For images of the RGB16 and P8 bit depths, the module supports a rich API
// with image subsurfaces and a fairly large sets of geometric and color
// transforms, including some in-place. P4 is not supported in most of these
// functions because the dense bit packing is both impractical and slower for
// these applications.
//---
#ifndef GINT_IMAGE
@ -45,14 +49,38 @@ extern "C" {
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_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_DEPRECATED_P8 = 2,
};
/* Quick macros to compare formats by storage size */
#define IMAGE_IS_RGB16(format) \
((format) == IMAGE_RGB565 || (format) == IMAGE_RGB565A)
#define IMAGE_IS_P8(format) \
((format) == IMAGE_P8_RGB565 || (format) == IMAGE_P8_RGB565A)
#define IMAGE_IS_P4(format) \
((format) == IMAGE_P4_RGB565 || (format) == IMAGE_P4_RGB565A)
/* Check whether image format has an alpha color */
#define IMAGE_IS_ALPHA(format) \
((format) == IMAGE_RGB565A || \
(format) == IMAGE_P8_RGB565A || \
(format) == IMAGE_P4_RGB565A)
/* Check whether image format uses a palette */
#define IMAGE_IS_INDEXED(format) \
(IMAGE_IS_P8(format) || IMAGE_IS_P4(format))
/* Image flags. These are used for memory management, mostly. */
enum {
IMAGE_FLAGS_DATA_RO = 0x01, /* Data is read-only */
IMAGE_FLAGS_PALETTE_RO = 0x02, /* Palette is read-only */
IMAGE_FLAGS_DATA_ALLOC = 0x04, /* Data is malloc()'d */
IMAGE_FLAGS_PALETTE_ALLOC = 0x08, /* Palette is malloc()'d */
};
/* image_t: gint's native bitmap image format
@ -60,31 +88,37 @@ enum {
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;
/* Color format, one of the IMAGE_* values defined above */
uint8_t format;
/* Additional flags, a combination of IMAGE_FLAGS_* values */
uint8_t flags;
/* 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;
/* Byte stride between lines */
int stride;
/* Here we lose structure because of the flexible array.
/* Pixel data in row-major order, left to right.
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[];
RGB16:
- 2 bytes per entry, each row padded to 4 bytes for alignment
- Each 2-byte value is an RGB565 color
P8:
- 1 byte per entry
- Each byte is a shifted palette index (to access the palette, use:
palette.colors[<value>+128])
P4:
- 4 bits per entry, each row padded to a full byte
- Each entry is a palette index (0...15) */
void *data;
/* For P8 and P4, palette. The color count does not account for alpha
(which is usually the last entry, but not materialized) and instead
indicates how much memory is allocated. */
int color_count;
uint16_t *palette;
} GPACKED(4) image_t;
@ -93,55 +127,279 @@ typedef struct
- 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. */
/* 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,
/* [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.
// 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,
/* [Any]: Flip image vertically */
IMAGE_VFLIP = 0x0100,
/* [Any]: Flip image horizontally */
IMAGE_HFLIP = 0x0200,
// Color effects
// 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,
/* [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
// Image creation and destruction
//---
/* TODO: Expand */
/* image_alloc(): Create a new (uninitialized) image
This function allocates a new image of the specified dimensions and format.
It always allocates a new data array, though a palette from another image
can be reused. (If you need to reuse a data array, see image_create() below
or use img_create_sub().)
The first parameters [width] and [height] specify the dimensions of the new
image in pixels. The [format] should be one of the IMAGE_* formats, for
example IMAGE_RGB565A or IMAGE_P4_RGB565.
By default, a new palette is allocated for formats with palettes; the new
image owns the palette and frees it when freed. This can be overriden by
setting the [palette] pointer to the desired palette; in this case, the new
image does not own the palette and does not free it when freed.
Regardless of whether the palette is allocated or specified by hand, for P8
the palette size must be indicated. A value of -1 can be specified to use
the default (256 colors). For all other formats, set a value of -1.
For images with alpha, the last parameter [alpha] indicates the palette
index or color value that denotes transparent pixels.
The returned image structure must be freed with image_free() after use.
@width Width of the new image
@height Height of the new image
@format Pixel format; one of the IMAGE_* formats defined above
@palette If not NULL, specifies the palette instead of allocating it
@palette_size For P8, indicates the palette size to use
@alpha For formats with alpha, color or index denoting alpha */
image_t *image_alloc(int width, int height, int format,
void *palette, int palette_size, int alpha);
/* image_create(): Create a bare image with no data/palette
This function allocates a new image structure but without data or palette.
The [data] and [palette] members are NULL, and [color_count] is either 0 or
-1 depending on whether the format normally has a palette.
This function is useful to create images that reuse externally-provided
information. It is intended that the user of this function sets the [data],
[stride] and [palette] and [color_count] members themselves. The
IMAGE_FLAGS_DATA_ALLOC and the IMAGE_FLAGS_PALETTE_ALLOC flags can be set on
the image if the user wishes for the image to free its data and palette when
freed.
For images with alpha, the last parameter [alpha] indicates the palette
index or color value that denotes transparent pixels.
The returned image structure must be freed with image_free() after use. */
image_t *image_create(int width, int height, int format, int alpha);
/* image_create_vram(): Create a reference to gint_vram
This function creates a new RGB565 image that references gint_vram. Using
this image as target for transformation functions can effectively render
transformed images to VRAM.
The value of gint_vram is captured when this function is called, and does
not update after dupdate() when triple-buffering is used. The user should
account for this option. (Using this function twice then replacing one of
the [data] pointers is allowed.)
The VRAM image ows no data but it does own its own structure so it must
still be freed with image_free() after use. */
image_t *image_create_vram(void);
/* image_free(): Free and image and the data it owns
This function frees the provided image structure and the data that it owns.
Images converted by fxconv should not be freed; nonetheless, this functions
distinguishes them and should work. Images are not expected to be created on
the stack.
If the image has the IMAGE_FLAGS_DATA_ALLOC flag, the data pointer is also
freed. Similarly, the image has the IMAGE_FLAGS_PALETTE_ALLOC flag, the
palette is freed. Make sure to not free images when references to them still
exist, as this could cause the reference's pointers to become dangling. */
void image_free(image_t *img);
/* image_copy_palette(): Duplicate an image's palette
This function duplicates the palette and returns a new one allocated with
malloc(). If the input image is not in a palette format or has no palette
assigned, returns NULL. If the returned pointer is not NULL, free() after
use or set the IMAGE_FLAGS_PALETTE_ALLOC flag on the image holding it so
that free() is automatically called when the image is freed. */
uint16_t *image_copy_palette(image_t const *img);
//---
// Basic image access and information
//---
/* image_valid(): Check if an image is valid
An image is considered valid if it has a valid profile, a non-NULL data
pointer, and for palette formats a valid palette pointer. */
bool image_valid(image_t const *img);
/* image_get_pixel(): Read a pixel from the data array
This function reads a pixel from the image's data array at position (x,y).
It returns the pixel's value, which is either a full-color value (RGB16) or
a possibly-negative palette index (P8/P4). See the description of the [data]
field of image_t for more details. The value of the pixel can be decoded
into a 16-bit color either manually or by using the image_decode_pixel()
function.
Note that reading large amounts of image data with this function will be
slow; if you need reasonable performance, consider iterating on the data
array manually. */
int image_get_pixel(image_t const *img, int x, int y);
/* image_decode_pixel(): Decode a pixel value
This function decodes a pixel's value obtained from the data array (for
instance with image_get_pixel()). For RGB16 formats this does nothing, but
for palette formats this accesses the palette at a suitable position.
Note that reading large amounts of data with this function will be slow; if
you need reasonable performance, consider inlining the format-specific
method or iterating on the data array manually. */
int image_decode_pixel(image_t const *img, int pixel);
/* image_data_size(): Compute the size of the [data] array
This function returns the size of the data array, in bytes. This can be used
to duplicate it. Note that for sub-images this is a subsection of another
image's data array, and might be much larger than the sub-image. */
int image_data_size(image_t const *img);
/* image_palette_size(): Compute the size of the [palette] array
This function returns the size of the palette array, in bytes. This can be
used to duplicate the palette. For images without a palette, returns -1. */
int image_palette_size(image_t const *img);
//---
// Basic image modifications
//---
/* image_set_pixel(): Set a pixel in the data array
This function writes a pixel into the image's data array at position (x,y).
The pixel value must be of the proper format, as specified in the definition
of the [data] field of image_t.
Formats: RGB16, P8, P4 */
void image_set_pixel(image_t const *img, int x, int y, int value);
/* image_copy(): Copy an image into another one
This function copies an image into another image. The target must be a valid
image with the same format as the source, otherwise this function is a
no-op. Unlike transforms, this function does clip, so there are no
conditions on the size of the target.
If [copy_alpha] is true, transparent pixels are copied verbatim, which
effectively replaces the top-left corner of [dst] with [src]. If it's false,
transparent pixels of [src] are skipped, effectively rendering [src] over
the top-left corner of [src].
The color scheme of src and dst should normally match. In RGB16, if
src->alpha and dst->alpha differ, this function adopts a resonable behavior;
inputs of value src->alpha are turned into dst->alpha if copy_alpha is true,
ignored otherwise; and opaque inputs of value dst->alpha are turned into
[dst->alpha ^ 1] to keep the visuals consistent. In P8, no attempt is made
to merge the palettes.
Formats: RGB16, P8 */
void image_copy(image_t const *src, image_t *dst, bool copy_alpha);
/* image_fill(): Fill an image with a single pixel value */
void image_fill(image_t *img, int value);
/* image_clear(): Fill a transparent image with its transparent value */
void image_clear(image_t *img);
/* TODO: Expand by taking from libimg */
//---
// Sub-image extraction
//---
/* image_sub(): Build a reference to a sub-image
This function is used to create references to sub-images of RGB16 and P8
images. The [data] pointer of the sub-image points somewhere within the data
array of the source, and its [palette] pointer is identical to the source's.
The last parameter is a pointer to a preallocated image_t structure (usually
on the stack) that gets filled with the data. Doing this instead of
allocating a new object with malloc() means that there is no need to
image_free() the sub-image, and thus it can be used inline:
image_t tmp;
image_hflip(src, image_sub(dst, x, y, w, h, &tmp));
A preprocessor macro is used to make the last parameter optional. If it's
not specified, a pointer to a static image_t will be returned instead. This
is useful in inline calls as shown above, which then simplify to:
image_hflip(src, image_sub(dst, x, y, w, h));
However, another call to image_sub() or image_at() will override the
sub-image, so you should only use this in such temporary settings.
If the requested rectangle does not intersect the source, the sub-image will
be of dimension 0x0. If the image format does not support sub-images (P4),
the sub-image will test invalid with image_valid(). */
image_t *image_sub(image_t const *src, int x, int y, int w, int h,
image_t *dst);
/* Make the last parameter optional */
#define image_sub1(src, x, y, w, h, dst, ...) image_sub(src, x, y, w, h, dst)
#define image_sub(...) image_sub(__VA_ARGS__, NULL)
/* image_at(): Build a reference to a position within a sub-image */
#define image_at(img, x, y) image_sub(img, x, y, -1, -1)
//---
// Geometric image transforms
//---
/* TODO: Geometric transforms */
//---
// Color transforms
//---
/* TODO: Color transforms */
//---
// Image rendering functions
//
@ -168,21 +426,21 @@ int image_decode_pixel(image_t const *img, int pixel);
/* 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(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, ...);
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__);
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__)
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, ...)
@ -204,14 +462,14 @@ DIMAGE_SIG(_dye, int effects, int dye_color)
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__)
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__)
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__)
dsubimage_p4_effect(x, y, img, 0, 0, (img)->width, (img)->height, \
eff, ##__VA_ARGS__)
#undef DIMAGE_SIG
#undef DIMAGE_SIG1
@ -223,12 +481,12 @@ DIMAGE_SIG1(p4_clearbg_alt, int effects, int bg_index)
/* 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;
/* 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
@ -261,50 +519,50 @@ void gint_image_clip_output(struct gint_image_box *b, int out_w, int out_h);
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 not already dispatched by renderer
Bit 0: VFLIP
Bit 1: HFLIP */
uint8_t effect;
/* Shader ID. This is used in Azur, and ignored in gint */
uint8_t shader_id;
/* Dynamic effects not already dispatched by renderer
Bit 0: VFLIP
Bit 1: HFLIP */
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;
/* 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;
/* 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;
/* 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;
/* [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;
/* 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;
/* [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;
/* 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
@ -331,8 +589,8 @@ struct gint_image_cmd
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);
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
@ -362,6 +620,55 @@ void gint_image_p4_clearbg_alt(void);
void gint_image_p4_swapcolor(void);
void gint_image_p4_dye(void);
//---
// Image library utilities
//
// The following functions and macros are mostly internal utilities; they are
// exposed here in case user applications want to extend the set of image
// transforms with custom additions.
//---
/* image_target(): Check if an image can be used as target for a transform
This function is used to quickly check whether a transform from [src] to
[dst] is possible. It requires image_valid(src) and image_valid(dst), plus
any optional constraints specified as variadic arguments. These constraints
can be:
* NOT_P4: fails if [dst] is P4.
* DATA_RW: fails if [dst] is not data-writable.
* PALETTE_RW: fails if [dst] is not palette-writable.
* SAME_SIZE: fails if [dst] is not at least as large as [src].
For example, in image_hflip(), we write:
if(!image_target(src, dst, NOT_P4, DATA_RW, SAME_SIZE)) return; */
enum {
IMAGE_TARGET_NONE,
IMAGE_TARGET_NOT_P4,
IMAGE_TARGET_DATA_RW,
IMAGE_TARGET_PALETTE_RW,
IMAGE_TARGET_SAME_SIZE,
IMAGE_TARGET_SAME_FORMAT,
IMAGE_TARGET_SAME_DEPTH,
};
bool image_target(image_t const *src, image_t *dst, ...);
#define image_target(src, dst, ...) \
image_target(src, dst, image_target_arg1(__VA_ARGS__ __VA_OPT__(,) NONE))
#define image_target_arg1(c, ...) \
IMAGE_TARGET_ ## c __VA_OPT__(, image_target_arg2(__VA_ARGS__))
#define image_target_arg2(c, ...) \
IMAGE_TARGET_ ## c __VA_OPT__(, image_target_arg3(__VA_ARGS__))
#define image_target_arg3(c, ...) \
IMAGE_TARGET_ ## c __VA_OPT__(, image_target_arg4(__VA_ARGS__))
#define image_target_arg4(c, ...) \
IMAGE_TARGET_ ## c __VA_OPT__(, image_target_arg5(__VA_ARGS__))
#define image_target_arg5(c, ...) \
IMAGE_TARGET_ ## c __VA_OPT__(, image_target_arg6(__VA_ARGS__))
#define image_target_arg6(c, ...) \
IMAGE_TARGET_ ## c __VA_OPT__(, image_target_too_many_args(__VA_ARGS__))
#endif /* FXCG50 */
#ifdef __cplusplus

45
src/image/image_alloc.c Normal file
View File

@ -0,0 +1,45 @@
#include <gint/image.h>
#include <stdlib.h>
#include <gint/defs/util.h>
image_t *image_alloc(int width, int height, int format,
void *palette, int palette_size, int alpha)
{
image_t *img = image_create(width, height, format, alpha);
if(!img)
return NULL;
if(IMAGE_IS_RGB16(format)) {
img->stride = ((width + 1) >> 1) * 4;
palette_size = -1;
}
else if(IMAGE_IS_P8(format)) {
img->stride = width;
palette_size = max(0, min(256, palette_size));
}
else if(IMAGE_IS_P4(format)) {
img->stride = ((width + 1) >> 1);
palette_size = 32;
}
void *data = malloc(height * img->stride);
if(!data) {
image_free(img);
return NULL;
}
img->data = data;
img->flags |= IMAGE_FLAGS_DATA_ALLOC;
if(!palette && palette_size > 0) {
palette = malloc(palette_size * 2);
if(!palette) {
image_free(img);
return NULL;
}
}
img->palette = palette;
img->color_count = palette_size;
return img;
}

9
src/image/image_clear.c Normal file
View File

@ -0,0 +1,9 @@
#include <gint/image.h>
void image_clear(image_t *img)
{
if(!IMAGE_IS_ALPHA(img->format))
return;
image_fill(img, img->alpha);
}

49
src/image/image_copy.c Normal file
View File

@ -0,0 +1,49 @@
#include <gint/image.h>
#include <gint/defs/util.h>
void image_copy(image_t const *src, image_t *dst, bool copy_alpha)
{
if(!image_target(src, dst, NOT_P4, DATA_RW, SAME_DEPTH))
return;
if(!IMAGE_IS_ALPHA(src->format))
copy_alpha = true;
/* Clip the input to match the size of the output */
int w = min(src->width, dst->width);
int h = min(src->height, dst->height);
if(w <= 0 || h <= 0)
return;
void *src_px = src->data;
void *dst_px = dst->data;
if(IMAGE_IS_RGB16(src->format)) {
int src_alpha = copy_alpha ? -1 : src->alpha;
int dst_alpha = IMAGE_IS_ALPHA(dst->format) ? -1 : dst->alpha;
do {
for(int x = 0; x < w; x++) {
int px = ((uint16_t *)src_px)[x];
if(px != src_alpha) {
px ^= (px == dst_alpha);
((uint16_t *)dst_px)[x] = px;
}
}
src_px += src->stride;
dst_px += dst->stride;
} while(--h > 0);
}
else if(IMAGE_IS_P8(src->format)) {
int src_alpha = copy_alpha ? 256 : src->alpha;
do {
for(int x = 0; x < w; x++) {
int px = ((int8_t *)src_px)[x];
if(px != src_alpha)
((int8_t *)dst_px)[x] = px;
}
src_px += src->stride;
dst_px += dst->stride;
} while(--h > 0);
}
}

View File

@ -0,0 +1,16 @@
#include <gint/image.h>
#include <stdlib.h>
#include <string.h>
uint16_t *image_copy_palette(image_t const *img)
{
int size = image_palette_size(img);
if(size < 0 || !img->palette)
return NULL;
void *palette = malloc(size);
if(!palette)
return NULL;
return memcpy(palette, img->palette, size);
}

28
src/image/image_create.c Normal file
View File

@ -0,0 +1,28 @@
#include <gint/image.h>
#include <stdlib.h>
image_t *image_create(int width, int height, int format, int alpha)
{
if(!IMAGE_IS_RGB16(format) && !IMAGE_IS_P8(format) && !IMAGE_IS_P4(format))
return NULL;
if(width <= 0 || width > 0xffff || height <= 0 || height > 0xffff)
return NULL;
image_t *img = malloc(sizeof *img);
if(!img)
return NULL;
img->format = format;
img->flags = 0;
img->alpha = alpha;
img->width = width;
img->height = height;
img->stride = 0;
img->data = NULL;
img->color_count = IMAGE_IS_INDEXED(format) ? 0 : -1;
img->palette = NULL;
return img;
}

View File

@ -0,0 +1,13 @@
#include <gint/image.h>
#include <gint/display.h>
image_t *image_create_vram(void)
{
image_t *img = image_create(DWIDTH, DHEIGHT, IMAGE_RGB565, 0);
if(!img)
return NULL;
img->stride = 2 * DWIDTH;
img->data = gint_vram;
return img;
}

View File

@ -0,0 +1,6 @@
#include <gint/image.h>
int image_data_size(image_t const *img)
{
return img->stride * img->height;
}

View File

@ -0,0 +1,12 @@
#include <gint/image.h>
int image_decode_pixel(image_t const *img, int pixel)
{
if(IMAGE_IS_RGB16(img->format))
return pixel;
else if(IMAGE_IS_P8(img->format))
return img->palette[pixel+128];
else if(IMAGE_IS_P4(img->format))
return img->palette[pixel];
return -1;
}

26
src/image/image_fill.c Normal file
View File

@ -0,0 +1,26 @@
#include <gint/image.h>
void image_fill(image_t *img, int value)
{
if(!image_target(img, img, NOT_P4, DATA_RW))
return;
void *img_px = img->data;
if(IMAGE_IS_RGB16(img->format)) {
for(int y = 0; y < img->height; y++) {
for(int x = 0; x < img->width; x++) {
((uint16_t *)img_px)[x] = value;
}
img_px += img->stride;
}
}
else if(IMAGE_IS_P8(img->format)) {
for(int y = 0; y < img->height; y++) {
for(int x = 0; x < img->width; x++) {
((int8_t *)img_px)[x] = value;
}
img_px += img->stride;
}
}
}

16
src/image/image_free.c Normal file
View File

@ -0,0 +1,16 @@
#include <gint/image.h>
#include <gint/mmu.h>
#include <stdlib.h>
void image_free(image_t *img)
{
if(!img || mmu_is_rom(img))
return;
if(img->flags & IMAGE_FLAGS_DATA_ALLOC)
free(img->data);
if(img->flags & IMAGE_FLAGS_PALETTE_ALLOC)
free(img->palette);
free(img);
}

View File

@ -0,0 +1,25 @@
#include <gint/image.h>
int image_get_pixel(image_t const *img, int x, int y)
{
if((unsigned)x >= img->width || (unsigned)y >= img->height)
return 0;
void *data = img->data + y * img->stride;
uint8_t *data_u8 = data;
uint16_t *data_u16 = data;
if(IMAGE_IS_RGB16(img->format)) {
return data_u16[x];
}
else if(IMAGE_IS_P8(img->format)) {
return data_u8[x];
}
else if(IMAGE_IS_P4(img->format)) {
if(x & 1)
return data_u8[x >> 1] & 0x0f;
else
return data_u8[x >> 1] >> 4;
}
return 0;
}

View File

@ -0,0 +1,7 @@
#include <gint/image.h>
int image_palette_size(image_t const *img)
{
return (img->color_count >= 0) ? img->color_count* 2 : -1;
}

View File

@ -0,0 +1,26 @@
#include <gint/image.h>
void image_set_pixel(image_t const *img, int x, int y, int value)
{
if(!image_valid(img))
return;
if((unsigned)x >= img->width || (unsigned)y >= img->height)
return;
void *data = img->data + y * img->stride;
uint8_t *data_u8 = data;
uint16_t *data_u16 = data;
if(IMAGE_IS_RGB16(img->format)) {
data_u16[x] = value;
}
else if(IMAGE_IS_P8(img->format)) {
data_u8[x] = value;
}
else if(IMAGE_IS_P4(img->format)) {
if(x & 1)
data_u8[x >> 1] = (data_u8[x >> 1] & 0xf0) | (value & 0x0f);
else
data_u8[x >> 1] = (data_u8[x >> 1] & 0x0f) | (value << 4);
}
}

40
src/image/image_sub.c Normal file
View File

@ -0,0 +1,40 @@
#include <gint/image.h>
#include <string.h>
#undef image_sub
static image_t image_sub_default;
image_t *image_sub(image_t const *src, int left, int top, int w, int h,
image_t *dst)
{
if(!dst)
dst = &image_sub_default;
if(w < 0)
w = src->width - left;
if(h < 0)
h = src->height - top;
struct gint_image_box box = { 0, 0, w, h, left, top };
if(!image_valid(src) || IMAGE_IS_P4(src->format) ||
!gint_image_clip_input(src, &box, w, h)) {
memset(dst, 0, sizeof *dst);
return dst;
}
int const ro_flags = IMAGE_FLAGS_DATA_RO | IMAGE_FLAGS_PALETTE_RO;
dst->format = src->format;
dst->flags = src->flags & ro_flags;
dst->alpha = src->alpha;
dst->stride = src->stride;
dst->width = box.w;
dst->height = box.h;
if(IMAGE_IS_RGB16(src->format))
dst->data = src->data + box.top * src->stride + 2 * box.left;
else if(IMAGE_IS_P8(src->format))
dst->data = src->data + box.top * src->stride + box.left;
dst->color_count = src->color_count;
dst->palette = src->palette;
return dst;
}

39
src/image/image_target.c Normal file
View File

@ -0,0 +1,39 @@
#include <gint/image.h>
#undef image_target
bool image_target(image_t const *src, image_t *dst, ...)
{
if(!image_valid(src) || !image_valid(dst))
return false;
va_list args;
va_start(args, dst);
int req = -1;
while((req = va_arg(args, int)) != IMAGE_TARGET_NONE) {
if(req == IMAGE_TARGET_NOT_P4 && IMAGE_IS_P4(dst->format))
return false;
if(req == IMAGE_TARGET_DATA_RW && (dst->flags & IMAGE_FLAGS_DATA_RO))
return false;
if(req == IMAGE_TARGET_PALETTE_RW && (dst->flags &
IMAGE_FLAGS_PALETTE_RO))
return false;
if(req == IMAGE_TARGET_SAME_SIZE &&
(dst->width < src->width || dst->height < src->height))
return false;
if(req == IMAGE_TARGET_SAME_FORMAT && src->format != dst->format)
return false;
if(req == IMAGE_TARGET_SAME_DEPTH) {
if(IMAGE_IS_RGB16(src->format) && IMAGE_IS_RGB16(dst->format))
continue;
if(IMAGE_IS_P8(src->format) && IMAGE_IS_P8(dst->format))
continue;
if(IMAGE_IS_P4(src->format) && IMAGE_IS_P4(dst->format))
continue;
return false;
}
}
va_end(args);
return true;
}

17
src/image/image_valid.c Normal file
View File

@ -0,0 +1,17 @@
#include <gint/image.h>
bool image_valid(image_t const *img)
{
if(!img)
return false;
if(IMAGE_IS_RGB16(img->format)) {
return (img->data != NULL);
}
if(IMAGE_IS_P8(img->format) || IMAGE_IS_P4(img->format)) {
return (img->data != NULL) && (img->palette != NULL);
}
/* Invalid format */
return false;
}

View File

@ -4,11 +4,10 @@
void dsubimage(int x, int y, image_t const *img, int left, int top,
int w, int h, int flags)
{
int p = img->profile;
if(p == IMAGE_RGB565 || p == IMAGE_RGB565A)
if(IMAGE_IS_RGB16(img->format))
return dsubimage_rgb16(x, y, img, left, top, w, h, flags);
if(p == IMAGE_P8_RGB565 || p == IMAGE_P8_RGB565A)
else if(IMAGE_IS_P8(img->format))
return dsubimage_p8(x, y, img, left, top, w, h, flags);
if(p == IMAGE_P4_RGB565 || p == IMAGE_P4_RGB565A)
else if(IMAGE_IS_P4(img->format))
return dsubimage_p4(x, y, img, left, top, w, h, flags);
}

View File

@ -47,28 +47,28 @@ bool gint_image_mkcmd(struct gint_image_box *box, image_t const *img,
cmd->effect = (effects & (IMAGE_VFLIP | IMAGE_HFLIP)) >> 8;
cmd->columns = box->w;
cmd->input_stride = img->width;
cmd->input_stride = img->stride;
cmd->x = box->x;
cmd->edge_1 = -1;
cmd->edge_2 = -1;
int p = img->profile;
int f = img->format;
int input_row = (effects & IMAGE_VFLIP) ? box->top+box->h-1 : box->top;
if(p == IMAGE_RGB565 || p == IMAGE_RGB565A) {
if(IMAGE_IS_RGB16(f)) {
cmd->input_stride += (cmd->input_stride & 1);
cmd->input = (void *)img->data +
(input_row * cmd->input_stride + box->left) * 2;
input_row * img->stride + (box->left * 2);
}
else if(p == IMAGE_P8_RGB565 || p == IMAGE_P8_RGB565A) {
cmd->input = (void *)img->data + img->data[0] * 2 + 2 +
(input_row * img->width + box->left);
cmd->palette = (void *)img->data + 258;
else if(IMAGE_IS_P8(f)) {
cmd->input = (void *)img->data +
(input_row * img->stride) + box->left;
cmd->palette = (void *)img->palette + 256;
}
else {
cmd->input = (void *)img->data + 32 +
input_row * ((img->width + 1) >> 1) + (box->left >> 1);
cmd->palette = img->data;
cmd->input = (void *)img->data +
input_row * img->stride + (box->left >> 1);
cmd->palette = (void *)img->palette;
/* By default, use edge_1 to indicate (box->left & 1), so that
functions that don't use edge_1 can still work properly */
if(!left_edge)

View File

@ -54,7 +54,7 @@ _gint_image_p4_loop:
add r6, r6
mov.b @r8+, r1 /* cmd.lines */
shlr r4
nop
mov.l r10, @-r15
extu.b r1, r1
@ -62,9 +62,6 @@ _gint_image_p4_loop:
mov.b @r8+, r10 /* cmd.edge_1 */
nop
mov #0, r9
addc r9, r4 /* r4 = (img.width + 1) >> 1 */
mov.l @r8+, r9
shlr r0 /* T bit is now VFLIP */

View File

@ -9,7 +9,7 @@ void dimage_p4(int x, int y, image_t const *img, int eff)
void dsubimage_p4(int x, int y, image_t const *img,
int left, int top, int w, int h, int eff)
{
if(img->profile == IMAGE_P4_RGB565A)
if(img->format == IMAGE_P4_RGB565A)
return dsubimage_p4_clearbg(x, y, img, left, top, w, h, eff,
img->alpha);

View File

@ -9,7 +9,7 @@ void dimage_p8(int x, int y, image_t const *img, int eff)
void dsubimage_p8(int x, int y, image_t const *img,
int left, int top, int w, int h, int eff)
{
if(img->profile == IMAGE_P8_RGB565A)
if(img->format == IMAGE_P8_RGB565A)
return dsubimage_p8_clearbg(x, y, img, left, top, w, h, eff,
img->alpha);

View File

@ -1,6 +1,6 @@
.global _gint_image_rgb16_loop
/* gint's image renderer: 16-bit RGB entry piont
/* gint's image renderer: 16-bit RGB entry point
These formats are the simplest of the bunch. RGB565 can use longword access
in cases when alignment is favorable and no geometric effect is applied. In
@ -52,10 +52,10 @@ _gint_image_rgb16_loop:
nop
mov.l @r8+, r3 /* cmd.input */
nop
add #4, r8 /* cmd.palette (don't care) */
bf.s _NO_VFLIP
add #4, r8 /* cmd.palette (don't care) */
shlr r4
_VFLIP:
neg r4, r4

View File

@ -9,7 +9,7 @@ void dimage_rgb16(int x, int y, image_t const *img, int eff)
void dsubimage_rgb16(int x, int y, image_t const *img,
int left, int top, int w, int h, int eff)
{
if(img->profile == IMAGE_RGB565A)
if(img->format == IMAGE_RGB565A)
return dsubimage_rgb16_clearbg(x, y, img, left, top, w, h, eff,
img->alpha);