README update and image

This commit is contained in:
Lephenixnoir 2020-03-10 15:34:49 +01:00
parent 92bf083dcc
commit 8af23cf69a
4 changed files with 44 additions and 24 deletions

1
.gitignore vendored
View File

@ -7,4 +7,3 @@
# Testing build system
/testing/libimg
/testing/transform.bin
/testing/transform.png

View File

@ -32,6 +32,10 @@ $(eval $(call compilerpath,cg))
all: $(lib-fx) $(lib-cg)
all-fx: $(lib-fx)
all-cg: $(lib-cg)
libimg-fx.a: $(obj-fx)
$(target-fx)-ar rcs $@ $^
libimg-cg.a: $(obj-cg)
@ -65,4 +69,4 @@ install:
fi
.PRECIOUS: %/
.PHONY: all clean distclean install
.PHONY: all all-fx all-cg clean distclean install

View File

@ -1,6 +1,6 @@
# libimg: Image transform and blending for gint
This library contains a variety of functions 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, namely:
* Extract references to subimages
* Blending with alpha (only full opaque and full transparent)
@ -12,30 +12,45 @@ This library contains a variety of functions to perform blending, geometric and
![(Sample image showcasing the capabilities of libimg.)](testing/transform.png)
## Differences between `img_t` and `image_t`
This library has been developed for [gint](/Lephenixnoir/gint), though only two VRAM-related functions are specific to gint. I'm willing to help port it to libfxcg if anyone wants to do that.
## Differences between img_t and image_t
The type of images in this library is called `img_t`. It is **not the same as `image_t`** (and `image_t` is the one that should be renamed to avoid the confusion).
* `image_t` objects are used by a gint component called *bopti* which specializes in maximizing rendering performance. They are very fast, especially on fx-9860G, but not flexible. They should be probably be called `bopti_image_t`.
* `image_t` objects are used by a gint component called *bopti* which specializes in maximizing rendering performance. They are very fast, especially on fx-9860G, but not flexible. They should probably be called `bopti_image_t`.
* `img_t` objects are part of *libimg*. They're not rendered as fast, but they are more versatile and can be transformed in many ways.
There is no function to convert between `image_t` and `img_t`. Both types of images are generated by fxconv at compile time. In the unlikely event that an image needs to be available in both formats, the proper way is to convert it twice with different parameters. But normally, only one format is needed:
* If the image needs to be read or transformed, use libimg. Set the fxconv parameters of the resource to `type:libimg_image` and declare it as `extern img_t myimage`.
* If the image needs to be read or transformed, use libimg. Set the fxconv parameters of the resource to `type:libimg-image` and declare it as `extern img_t myimage`.
* Otherwise, bopti should be used for maximum performance. Set the fxconv parameters to `type:bopti_image` and declare it as `extern image_t myimage`.
## Compiling libimg
TODO
Building libimg for the calculator requires a SuperH compiler. If you have `sh-elf-gcc`, then you're ready to go. libimg will be compiled for both fx-9860G (Graph 35+E-like) and fx-CG 50 (Graph 90+E), and it will install itself in the proper compiler-managed directory.
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.
```
% make
% make install
```
If you have an older setup with `sh3eb-elf` and/or `sh4eb-elf`, edit the configuration file `libimg.conf` before building, as follows:
* Set either the `target-fx` or `target-cg` line to use the proper compiler.
* If you need to completely disable one of the platforms, comment out the line that sets `lib-fx` or `lib-cg`.
Additionally, this repository has a simple testing system for Linux in the `testing` folder.
* Use `make` from that folder to compile `main.c` into a `libimg` executable that reads RGB565 bitmap files into `img_t` images and performs a few transforms.
* Use `make all` to also run it to generate the `transform.png` file that is shown at the top of this README (uses ImageMagick's `convert` to get the PNG).
## 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:
The first step to use libimg in a calculator 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
IMG.sprite.png = type:libimg-image name:sprite
```
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.
@ -91,7 +106,7 @@ img_destroy(new_32x48_image);
img_destroy(copy_of_sprite);
```
Note that converted images such as `sprite`, null images and subimages (described later) don't need to be destroyed because they are not created with `malloc()`. To make life easier, `img_destroy()` can tell them apart, so when in doubt just destroy everything after use.
Note that converted images such as `sprite`, null images, and subimages (described later) don't need to be destroyed because they are not created with `malloc()`. To make life easier, `img_destroy()` will tell them apart and do nothing, so when in doubt just destroy everything after use.
## Basic transforms
@ -103,19 +118,19 @@ Let's start playing with these new shiny images. We'll start by flipping the spr
img_t flipped_sprite = img_create(sprite.width, sprite.height);
img_fill(flipped_sprite, C_WHITE);
```
This example introduces several useful elements:
This example introduces several useful features:
* The dimension of an image can be accessed by accessing its `width` and `height` attributes.
* The `img_fill()` function will paint all the pixels of an image in a uniform color.
* Colors used in libimg are the same as colors used in gint, except for transparency on fx-CG 50 (Graph 90+E), which is `0x0001`. More on that soon.
Now we've got an blank image with the same size as `sprite`, so we can go ahead and flip `sprite` horizontally into it:
Now we've got a 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 it. 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 we were able to transform because `flipped_sprite` is not read-only. If we tried to flip with `sprite` as a destination, nothing would happen.
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.
@ -124,7 +139,7 @@ But wait, since `flipped_sprite` was originally white, this just made a horrible
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 that `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.
@ -149,7 +164,7 @@ All transforms work like this. This includes:
We can already mention a few conventions within the library:
* All transforms first take the `src` (source image) and `dst` (destination images) parameters, then any other parameter of the transform (such as the rotation angle).
* All transforms first take the `src` (source image) and `dst` (destination image) parameters, then any other parameter of the transform (such as the rotation angle).
* All transforms have a `_create()` variant which does not have a `dst` argument. The destination image is created automatically with just the size of the transformed result and returned.
## Subsurfaces and positioning
@ -189,9 +204,9 @@ img_render(sprite, spritesheet);
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.
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 function will just do nothing.
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:
The subimage does not have new pixels, so it does not need to be destroyed. `img_destroy()` can still be called on it; 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,7 +214,7 @@ img_hflip(sprite, img_sub(spritesheet, 16, 0, 16, 16));
This reads as "flip `sprite` horizontally, and write the result in `spritesheet`, at position (16,0) in a rectangle of size 16x16".
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.
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));
@ -234,7 +249,7 @@ 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>));
img_hflip(sprite, img_at(img_vram(), x, y));
```
Similarly, the following calls both fill a rectangle of the VRAM (although `drect()` is faster).
@ -249,7 +264,7 @@ 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 the following call.
```c
img_render(<src>, img_at(img_vram(), x, y));
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. Hence the name.
@ -267,7 +282,7 @@ 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:
It is also possible to use a section of an image as the source and another section as the target. The following call 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));
@ -285,7 +300,7 @@ Every image's pixels can be read manually, and also written if the image is not
* `img.stride` is the number of pixels on each row. Most of the time this is larger than `img.width`!
* `img.pixels` is the pixel array.
The format is row-major order with stride. Here is how a 8x3 image with stride 12 looks like in memory. The numbers in the diagram correspond to indices in `img.pixels`.
The format is row-major order with stride. Here is how an 8x3 image with stride 12 looks like in memory. The numbers in the diagram correspond to indices in `img.pixels`.
<------ img.width ------>
+-------------------------+-------------+
@ -297,7 +312,7 @@ The format is row-major order with stride. Here is how a 8x3 image with stride 1
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 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.
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, as one could expect. But 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()`.
@ -326,3 +341,5 @@ Pixels are of type `img_pixel_t`. The way to manipulate them depends on the plat
## Issues and contributing
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.
Have fun playing around with this library!

BIN
testing/transform.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB