calculator build system

This commit is contained in:
Lephenixnoir 2020-03-09 19:09:15 +01:00
parent 0fb79006f2
commit 92bf083dcc
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
6 changed files with 143 additions and 52 deletions

6
.gitignore vendored
View File

@ -1,3 +1,9 @@
# Calculator build system
/build-fx
/build-cg
/libimg-fx.a
/libimg-cg.a
# Testing build system
/testing/libimg
/testing/transform.bin

68
Makefile Normal file
View File

@ -0,0 +1,68 @@
#! /usr/bin/make -f
# libimg Makefile
cflags := -ffreestanding -nostdlib -I.
header := libimg.h
# {target,cflags,lib}-{fx,cg} are provided by libimg.conf
include libimg.conf
# Files
src := $(wildcard src/*.c)
obj-fx := $(src:src/%=build-fx/%.o)
obj-cg := $(src:src/%=build-cg/%.o)
# Compiler paths
define compilerpath
ifneq "$(lib-$1)" ""
prefix-$1 := $$(shell $(target-$1)-gcc -print-search-dirs | grep install | \
sed 's/install: //')
ifeq "$$(prefix-$1)" ""
$$(error "Cannot determine $1 compiler install path")
endif
endif
endef
$(eval $(call compilerpath,fx))
$(eval $(call compilerpath,cg))
#
# Rules
#
all: $(lib-fx) $(lib-cg)
libimg-fx.a: $(obj-fx)
$(target-fx)-ar rcs $@ $^
libimg-cg.a: $(obj-cg)
$(target-cg)-ar rcs $@ $^
build-fx/%.c.o: src/%.c | build-fx/
$(target-fx)-gcc -c $< -o $@ $(cflags) $(cflags-fx) -DFX9860G
build-cg/%.c.o: src/%.c | build-cg/
$(target-cg)-gcc -c $< -o $@ $(cflags) $(cflags-cg) -DFXCG50
clean:
@ rm -rf build-fx build-cg
distclean: clean
@ rm -f $(lib-fx) $(lib-cg)
%/:
@ mkdir -p $@
#
# Installing
#
install:
if [[ ! -z $(lib-fx) ]]; then \
cp $(lib-fx) $(prefix-fx); \
cp $(header) $(prefix-fx)/include; \
fi
if [[ ! -z $(lib-cg) ]]; then \
cp $(lib-cg) $(prefix-cg); \
cp $(header) $(prefix-cg)/include; \
fi
.PRECIOUS: %/
.PHONY: all clean distclean install

100
README.md
View File

@ -1,6 +1,6 @@
# libimg: Image transform and blending for gint
This library contains a variety of function to perform blending, geometric and color transforms on images in [gint](/Lephenixnoir/gint), namely:
This library contains a variety of functions to perform blending, geometric and color transforms on images in [gint](/Lephenixnoir/gint), namely:
* Extract references to subimages
* Blending with alpha (only full opaque and full transparent)
@ -28,27 +28,29 @@ There is no function to convert between `image_t` and `img_t`. Both types of ima
TODO
This repository uses a simple testing system for PC. Make from the `testing` library to produce a `libimg` executable that reads RGB565 bitmap files into `img_t` images on Linux. Use `make all` to also generate the `transform.png` file that is shown at the top of this README.
## Converting images and rendering to VRAM
The first step to use libimg in a program is to convert assets to the `img_t` format. This is done by setting the `type` parameter of the resource to `libimg_image`. For example:
```
```c
IMG.sprite.png = type:libimg_image name:sprite
```
Then the newly-converted image can be accessed from a C program by extern-declaring as ny other fxconv resource. It is ready to use immediately. The definition of `img_t` and the libimg functions are provided by `<libimg.h>`, which needs to be included.
Then the newly-converted image can be accessed from a C program by extern-declaring it as any other fxconv resource. It is ready to use immediately. The definition of `img_t` and the libimg functions are provided by `<libimg.h>`, which needs to be included.
```
```c
#include <libimg.h>
extern img_t const sprite;
```
Note that **converted images are read-only**, as is everything generated by fxconv. This means that the declared image cannot be written to or transformed in-place. But more on that later. To remember this read-only status, I added the `const` keyword; this is purely optional.
To render this shiny image to VRAM, use `img_render_vram()`. On fx-9860G (Graph 35+E family), this is the only way to render to VRAM because the VRAM has a different format than `img_t`. On fx-CG 50 (Graph 90+E), more powerful methods are available.
To render this shiny image to VRAM, use `img_render_vram()`. On fx-9860G (Graph 35+E family), this is the only way to render to VRAM because the VRAM has a different format than `img_t`. On fx-CG 50 (Graph 90+E), more powerful methods are available, which we'll see later.
```
img_render_vram(sprite, <x>, <y>);
```c
img_render_vram(sprite, x, y);
```
`img_render_vram()`, as every other libimg function, accounts for transparent pixels. Only opaque pixels will be copied to VRAM. This makes it very easy to blend images together by just rendering in sequence.
@ -59,19 +61,19 @@ Because our converted sprite is read-only, we can't do much more for now. To sta
`img_create()` will create a new uninitialized image. Note that the value of the pixels is completely random at this point. We'll see how to fill the whole image with `img_fill()` later. Sometimes filling is not needed and would only decrease performance, so libimg tries to not get in your way and does not do it automatically.
```
```c
img_t new_32x48_image = img_create(32, 48);
```
`img_copy()` will create a copy of an image. The returned copy will be of the same size as the source, but with duplicated pixels. A copy can always be written to, even if the source is read-only.
```
```c
img_t copy_of_sprite = img_copy(sprite);
```
Of course, creating new images *can fail*. Both functions use `malloc()` under-the-hood, and `malloc()` fails if there is not enough memory. In this case, `img_create()` and `img_copy()` return a *null image*, which is like the `NULL` pointer for images. To see if an image is null, call `img_null()`.
```
```c
if(img_null(copy_of_sprite)) {
/* Argh, copy has failed! */
}
@ -84,7 +86,7 @@ A null image does not have pixels, and reading or writing it would be an error.
Also, because there is an `malloc()`, there has to be a `free()`. Images created by `img_create()` and `img_copy()` need to be destroyed once they are no longer needed. Calling `img_destroy()` will do just that.
```
```c
img_destroy(new_32x48_image);
img_destroy(copy_of_sprite);
```
@ -93,9 +95,9 @@ Note that converted images such as `sprite`, null images and subimages (describe
## Basic transforms
Let's start playing with these new shiny images. We'll start by flipping the sprite horizontally to simulate walking in the other direction. Let's make a new image of the required size and fill it with white pixels. This is important, because a sprite has transparent pixels, so if we don't clear the new image, random pixels will be visible around the sprite.
Let's start playing with these new shiny images. We'll start by flipping the sprite horizontally to simulate walking in the other direction. Let's make a new image of the required size and fill it with white pixels. This is important because our original sprite has transparent pixels. If we don't clear the new image, some of the random pixels will be visible around the flipped sprite.
```
```c
#include <gint/display.h>
img_t flipped_sprite = img_create(sprite.width, sprite.height);
@ -109,31 +111,31 @@ This example introduces several useful elements:
Now we've got an blank image with the same size as `sprite`, so we can go ahead and flip `sprite` horizontally into it:
```
```c
img_hflip(sprite, flipped_sprite);
```
That's all. Now `img_render_vram(flipped_sprite, <x>, <y>)` will show the flipped version. As long as `flipped_sprite` is not destroyed, it can be rendered an unlimited amount of times. (Note that the transform wouldn't work if `flipped_sprite` were read-only. We couldn't use `sprite` as a target, for instance.)
That's all. Now `img_render_vram(flipped_sprite, x, y)` will show the flipped version. As long as `flipped_sprite` is not destroyed, it can be rendered an unlimited amount of times. (Note that the transform wouldn't work if `flipped_sprite` were read-only. We couldn't use `sprite` as a target, for instance.)
But wait, since `flipped_sprite` was originally white, this just made a horrible white rectangle on the screen. What we actually wanted is to make the background transparent. We can use the special color `IMG_ALPHA` to do this.
```
```c
/* Makes flipped_sprite completely transparent: */
img_fill(flipped_sprite, IMG_ALPHA);
```
On fx-9860G (Graph 35+E family), libimg colors are exactly the same as gint colors, so `IMG_ALPHA` is equivalent to `C_NONE`. On fx-CG 50 (Graph 90+E), things are a bit different. Colors are in the RGB565 format, and all 16-bit values are valid RGB565 colors, so there is no room for a special transparent color. libimg solves this dilemma by deciding that `0x0001` represents transparency. `0x0001` has been chosen because it is an extremely dark color, undistinguishable from black, and it's easy to compare numbers to `1` so it's technically faster than other alternatives. Just remember than `IMG_ALPHA` should be used to talk about transparency in libimg.
On fx-9860G (Graph 35+E family), libimg colors are exactly the same as gint colors, so `IMG_ALPHA` is equivalent to `C_NONE`. On fx-CG 50 (Graph 90+E), things are a bit different. Colors are in the RGB565 format, and all 16-bit values are valid RGB565 colors, so there is no room for a special transparent color. libimg solves this dilemma by deciding that `0x0001` represents transparency. `0x0001` has been chosen because it is an extremely dark color, undistinguishable from black, and it's easy to compare numbers to `1` so it's technically faster than other alternatives. Just remember that `IMG_ALPHA` should be used to talk about transparency in libimg.
Because filling a new image with transparent pixels is a common operation, a shortcut called `img_clear()` is provided.
```
```c
/* Also makes flipped_sprite completely transparent: */
img_clear(flipped_sprite);
```
So now the code does `img_create()`, `img_clear()`, and `img_hflip()` as a transform. This is also a common sequence, so a shortcut called `img_hflip_create()` is provided to do exactly that.
```
```c
img_t flipped_sprite = img_hflip_create(sprite);
```
@ -158,13 +160,13 @@ This is because libimg has a flexible positioning system that is more powerful t
Let's see an exemple. We'll create a spritesheet of size 32x16 with one 16x16 sprite on the left and one on the right.
```
```c
img_t spritesheet = img_create(32, 16);
```
The right sprite is at position (16,0) within `spritesheet`. If we want to apply a lot of transforms on it, we'll have to keep mentioning these coordinates over and over, which is painful and error-prone. Instead, we can use a function called `img_sub()` to create a reference to the right half of `spritesheet`.
```
```c
img_t right_sprite = img_sub(spritesheet, 16, 0, 16, 16);
```
@ -172,7 +174,7 @@ img_t right_sprite = img_sub(spritesheet, 16, 0, 16, 16);
`img_sub()` **does not create a new image**. It only gives a new view into the same pixels. Drawing to `right_sprite` right now will totally draw into the right half of `spritesheet`! In fact, let's just do this.
```
```c
img_fill(right_sprite, C_BLACK);
```
@ -180,7 +182,7 @@ And just like that, we have filled *only half* of `spritesheet`. Calling `img_fi
With that in mind, we can now create the full spritesheet.
```
```c
/* Copy the normal sprite */
img_render(sprite, spritesheet);
/* Flip into the right half */
@ -189,9 +191,9 @@ img_hflip(sprite, right_sprite);
Of course, this works with all transforms. Please remember though that the destination image or subimage of a transform needs to be *at least as large* as the transformed result. If the destination is smaller, the transform will not happen.
The subimage does not create new pixels, so it does not need to be destroyed. `img_destroy()` can still be called on it and do nothing. But not destroying it means we don't have to store it in a variable, so we create the flipped sprite like this:
The subimage does not create new pixels, so it does not need to be destroyed. `img_destroy()` can still be called on it and do nothing. But not destroying it means we don't have to store it in a variable, so we can create the flipped sprite like this:
```
```c
img_hflip(sprite, img_sub(spritesheet, 16, 0, 16, 16));
```
@ -199,19 +201,19 @@ This reads as "flip `sprite` horizontally, and write the result in `spritesheet`
In this case we know that the size of the rectangle *has* to be 16x16, because it's the size of the sprite. So we can omit it by setting the width and height to `-1`. This will create a reference to the subimage that starts at position (16,0) of `spritesheet` and extends all the way to the bottom right corner. `img_hflip()` will still only touch the first 16x16 pixels in the top left corner, so it's not a problem if the reference is larger than needed.
```
```c
img_hflip(sprite, img_sub(spritesheet, 16, 0, -1, -1));
```
If you feel like this is a common construction, then you're absolutely right! So a shortcut has been made for it. Instead of setting the width and height to -1 we can just use `img_at()` which will set them for us. `img_at(img, x y)` is the same as `img_sub(img, x, y, -1, -1)`. So the transform becomes:
If you feel like this is a common construction, then you're absolutely right! So a shortcut has been made for it. Instead of setting the width and height to -1 we can just use `img_at()` which will set them for us. `img_at(img, x, y)` is the same as `img_sub(img, x, y, -1, -1)`. So the transform becomes:
```
img_hflip(sprite, img_at(spritehseet, 16, 0));
```c
img_hflip(sprite, img_at(spritesheet, 16, 0));
```
This reads as "flip `sprite` horizontally and put the result in `spritesheet` at position (16,0)". It's much shorter than before, and because the reference to subimage does not need to be destroyed, there is no risk of leaking memory.
References to subimages are powerful and can be used in a variety of ways. But there is a golden rule: **destroying an image also destroys the subimages**. Once we call `img_destroy(spritesheet)`, all subimages including `right_sprite` become *invalid* and using them is an error. Always keep an eye on the full versions!
References to subimages are powerful and can be used in a variety of ways. But there is a golden rule: **destroying an image also destroys the subimages**. Once we call `img_destroy(spritesheet)`, all subimages including `right_sprite` become *invalid* and using them is an error. Always keep an eye on the originals!
As an example of versatility of references to subimages, see how `img_sub()` can be used on the *source image* to transform only a part of it!
@ -223,7 +225,7 @@ On fx-9860G, the VRAM does not have the same format as an `img_t`, so we can't u
But on fx-CG 50, the VRAM does have the same format as an `img_t`. Use `img_vram()` to get a reference to it:
```
```c
img_t vram_as_an_image = img_vram();
```
@ -231,43 +233,43 @@ Because it is only a reference, it does not need to be destroyed with `img_destr
This VRAM reference allows to directly transform to VRAM without using temporary images. For instance, we can do:
```
```c
img_hflip(sprite, img_at(img_vram(), <x>, <y>));
```
Similarly, the following calls both fill a rectangle of the VRAM (although `drect()` is faster).
```
```c
#include <gint/display.h>
drect(x, y, w, h, color);
img_fill(img_sub(img_vram(), x, y, w, h), color);
```
This is where the `img_render()` function becomes the most useful. This function copies its source to the top-left corner of its destination, without transforming it. Unlike transforms, it clips, so if the destination is smaller than the source, only the visible part will be copied. (Transforms do nothing in this situation.) In fact, on fx-CG 50, `img_render_vram()` is just a simple form for:
This is where the `img_render()` function becomes the most useful. This function copies its source to the top-left corner of its destination, without transforming it. Unlike transforms, it clips, so if the destination is smaller than the source, only the visible part will be copied. (Transforms do nothing in this situation.) In fact, on fx-CG 50, `img_render_vram()` is just a simple form for the following call.
```
img_render(<src>, img_at(img_vram(), <x>, <y>));
```c
img_render(<src>, img_at(img_vram(), x, y));
```
`img_render()` is used most of the time in this way, to copy (render) images to VRAM. This is why it is called this way.
`img_render()` is used most of the time in this way, to copy (render) images to VRAM. Hence the name.
## In-place transforms
Some transforms can operate on a single image without requiring to have a different destination. The image is overwritten during the transform, so the original is lost but no additional meomry is needed. This is called an *in-place transform*.
In-place transforms are possible only when the size of the input is the same as the size of the output. Currently, this includes everything except rotation by 90 or 270 degrees, and upscaling by a non-trivial factor.
In-place transforms are possible only when the size of the input is the same as the size of the output. Currently, this includes everything except rotation of a non-square image by 90 or 270 degrees, and upscaling by a non-trivial factor.
To perform an in-place transform, just use the same source and destination:
```
```c
img_t sprite_copy = img_copy(sprite);
img_hflip(sprite_copy, sprite_copy);
```
It is also possible to use a section of an image as the source and another section as the target. The following class horizontally flips the left half of `spritesheet` into the right half:
```
```c
img_hflip(img_sub(spritesheet, 0, 0, 16, 16), img_at(spritesheet, 16, 0));
```
@ -293,20 +295,20 @@ The format is row-major order with stride. Here is how a 8x3 image with stride 1
+-------------------------+-------------+
<------------ img.stride ------------->
For instance, the second pixel of second row is `img.pixels[13]`. See how one needs to add `img.stride` to the index to go down one line, instead of `img.width`. Also, indices represented by dots such as `pixesl[8]` are *not part of the image* and should never be accessed.
For instance, the third pixel of the second row is `img.pixels[14]`. See how one needs to add `img.stride` to the index to go down one line, instead of `img.width`. Also, indices represented by dots such as `pixels[8]` are *not part of the image* and should never be accessed.
The reason with this format with stride is used is to support references to subimages. When creating a spritesheet of size 32x16, the stride is set to 32. And when we extract a 16x16 half, even though we are only considering half of each row, the rows are still 32 pixels apart from each other! This is why the stride of an image is often not equal to its width.
The reason why this format with stride is used is to support references to subimages. When creating a spritesheet of size 32x16, the stride is set to 32. And when we extract a 16x16 half, even though we are only considering half of each row, the rows are still 32 pixels apart from each other! This is why the stride of an image is often not equal to its width.
This also means that images are not contiguous in memory, so it is not possible to replace all the pixels in a single call to `memcpy()`.
Here is how one would iterate on all the pixels of an image. The following piece of code turns all transparent pixels white:
Here is how one would iterate over all the pixels of an image. The following piece of code turns all transparent pixels white:
```
```c
img_pixel_t *px = img.pixels;
for(int y=0; y<img.height; y++)
for(int y = 0; y < img.height; y++)
{
for(int x=0; x<img.width; x++)
for(int x = 0; x < img.width; x++)
{
/* Do something with px[x] */
if(px[x] == IMG_ALPHA) px[x] = C_WHITE;
@ -319,10 +321,8 @@ for(int y=0; y<img.height; y++)
Pixels are of type `img_pixel_t`. The way to manipulate them depends on the platform:
* On fx-9860G, `img_pixel_t` is `uint8_t` and its values are the colors from `<gint/display.h>`. The transparent color `IMG_ALPHA` is equal to `C_NONE`.
* On fx-CG 50, `img_pixel_t` is `uint16_t` and its values are RGB565 colors. Opaque colors from `<gint/display.h>` can be used, except for `IMG_ALHA = 0x0001` which represents transparency. Note that `IMG_ALPHA` is *not* equal to `C_NONE`.
* On fx-CG 50, `img_pixel_t` is `uint16_t` and its values are RGB565 colors. Opaque colors from `<gint/display.h>` can be used, except for `IMG_ALPHA = 0x0001` which represents transparency. Note that `IMG_ALPHA` is *not* equal to `C_NONE`.
## Issues and contributing
Bug reports and additions to the library (whethers issues or PRs) are very welcome. Since this is a static and overall simple library, I don't mind adding whichever transform is useful to game developers.
This repository uses a simple testing system for PC. Make from the `testing` library to produce a `libimg` executable that reads RGB565 bitmap files into `img_t` images on Linux. Use `make all` to also generate the `transform.png` file that is shown at the top of this README.
Bug reports and additions to the library (whether issues or PRs) are very welcome. Since this is a static and overall simple library, I don't mind adding whichever transform is useful to game developers.

17
libimg.conf Normal file
View File

@ -0,0 +1,17 @@
# libimg configuration file
# Edit this file to select your configuration options.
# Library file name. You should not change these names, but you can comment
# these two lines to disable the corresponding target (f.i. if you don't have
# an fx-9860G-compatible compiler).
lib-fx = libimg-fx.a
lib-cg = libimg-cg.a
# Default toolchain for each platform. Modern setups use sh-elf. If you have
# either sh3eb-elf or sh4eb-elf, set them here.
target-fx = sh-elf
target-cg = sh-elf
# Additional compiler flags for each platform.
cflags-fx = -Wall -Wextra -Os -m3 -mb
cflags-cg = -Wall -Wextra -Os -m4-nofpu -mb

View File

@ -47,7 +47,7 @@ void img_render(img_t src, img_t dst)
}
#ifdef FX9860G
void img_render_vram(img_t src, int x, int y)
void img_render_vram(img_t img, int x, int y)
{
img_pixel_t *px = img.pixels;

View File

@ -79,7 +79,7 @@ img_t img_rotate_create(img_t src, int angle)
int w = src.width, h = src.height;
if(angle == 90 || angle == 270) w = src.height, h = src.width;
img_t dst = img_create(src.height, src.width);
img_t dst = img_create(w, h);
img_fill(dst, IMG_ALPHA);
img_rotate(src, dst, angle);
return dst;