cammy/src/image.c

428 lines
13 KiB
C

#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <cammy/image.h>
static uint32_t bayer_matrix[256] = {
/*
* Black gamut
*/
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
/*
* Black to dark gray gradient
*/
0x00018000, 0x00018000, 0x00018000, 0x00018000, 0x00018000,
0x00018020, 0x00018020, 0x00018020, 0x00018020, 0x00018020,
0x000180a0, 0x000180a0, 0x000180a0, 0x000180a0, 0x000180a0,
0x0001a0a0, 0x0001a0a0, 0x0001a0a0, 0x0001a0a0, 0x0001a0a0,
0x0001a4a0, 0x0001a4a0, 0x0001a4a0, 0x0001a4a0, 0x0001a4a0, 0x0001a4a0,
0x0001a4a1, 0x0001a4a1, 0x0001a4a1, 0x0001a4a1, 0x0001a4a1,
0x0001a4a5, 0x0001a4a5, 0x0001a4a5, 0x0001a4a5, 0x0001a4a5,
0x0001a5a5, 0x0001a5a5, 0x0001a5a5, 0x0001a5a5, 0x0001a5a5,
0x0001ada5, 0x0001ada5, 0x0001ada5, 0x0001ada5, 0x0001ada5, 0x0001ada5,
0x0001ada7, 0x0001ada7, 0x0001ada7, 0x0001ada7, 0x0001ada7,
0x0001adaf, 0x0001adaf, 0x0001adaf, 0x0001adaf, 0x0001adaf,
0x0001afaf, 0x0001afaf, 0x0001afaf, 0x0001afaf, 0x0001afaf,
0x0001df5f, 0x0001df5f, 0x0001df5f, 0x0001df5f, 0x0001df5f, 0x0001df5f,
0x0001df7f, 0x0001df7f, 0x0001df7f, 0x0001df7f, 0x0001df7f,
0x0001dfff, 0x0001dfff, 0x0001dfff, 0x0001dfff, 0x0001dfff,
0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff,
/*
* Dark gray to light gray gradient
*/
0x01028000, 0x01028000, 0x01028000, 0x01028000, 0x01028000, 0x01028000,
0x01028020, 0x01028020, 0x01028020, 0x01028020, 0x01028020,
0x010280a0, 0x010280a0, 0x010280a0, 0x010280a0, 0x010280a0,
0x0102a0a0, 0x0102a0a0, 0x0102a0a0, 0x0102a0a0, 0x0102a0a0,
0x0102a4a0, 0x0102a4a0, 0x0102a4a0, 0x0102a4a0, 0x0102a4a0, 0x0102a4a0,
0x0102a4a1, 0x0102a4a1, 0x0102a4a1, 0x0102a4a1, 0x0102a4a1,
0x0102a4a5, 0x0102a4a5, 0x0102a4a5, 0x0102a4a5, 0x0102a4a5,
0x0102a5a5, 0x0102a5a5, 0x0102a5a5, 0x0102a5a5, 0x0102a5a5,
0x0102ada5, 0x0102ada5, 0x0102ada5, 0x0102ada5, 0x0102ada5, 0x0102ada5,
0x0102ada7, 0x0102ada7, 0x0102ada7, 0x0102ada7, 0x0102ada7,
0x0102adaf, 0x0102adaf, 0x0102adaf, 0x0102adaf, 0x0102adaf,
0x0102afaf, 0x0102afaf, 0x0102afaf, 0x0102afaf, 0x0102afaf,
0x0102df5f, 0x0102df5f, 0x0102df5f, 0x0102df5f, 0x0102df5f, 0x0102df5f,
0x0102df7f, 0x0102df7f, 0x0102df7f, 0x0102df7f, 0x0102df7f,
0x0102dfff, 0x0102dfff, 0x0102dfff, 0x0102dfff, 0x0102dfff,
0x0102ffff, 0x0102ffff, 0x0102ffff, 0x0102ffff, 0x0102ffff,
/* Light gray to white gradient */
0x02038000, 0x02038000, 0x02038000, 0x02038000, 0x02038000, 0x02038000,
0x02038020, 0x02038020, 0x02038020, 0x02038020, 0x02038020,
0x020380a0, 0x020380a0, 0x020380a0, 0x020380a0, 0x020380a0,
0x0203a0a0, 0x0203a0a0, 0x0203a0a0, 0x0203a0a0, 0x0203a0a0,
0x0203a4a0, 0x0203a4a0, 0x0203a4a0, 0x0203a4a0, 0x0203a4a0, 0x0203a4a0,
0x0203a4a1, 0x0203a4a1, 0x0203a4a1, 0x0203a4a1, 0x0203a4a1,
0x0203a4a5, 0x0203a4a5, 0x0203a4a5, 0x0203a4a5, 0x0203a4a5,
0x0203a5a5, 0x0203a5a5, 0x0203a5a5, 0x0203a5a5, 0x0203a5a5,
0x0203ada5, 0x0203ada5, 0x0203ada5, 0x0203ada5, 0x0203ada5, 0x0203ada5,
0x0203ada7, 0x0203ada7, 0x0203ada7, 0x0203ada7, 0x0203ada7,
0x0203adaf, 0x0203adaf, 0x0203adaf, 0x0203adaf, 0x0203adaf,
0x0203afaf, 0x0203afaf, 0x0203afaf, 0x0203afaf, 0x0203afaf,
0x0203df5f, 0x0203df5f, 0x0203df5f, 0x0203df5f, 0x0203df5f, 0x0203df5f,
0x0203df7f, 0x0203df7f, 0x0203df7f, 0x0203df7f, 0x0203df7f,
0x0203dfff, 0x0203dfff, 0x0203dfff, 0x0203dfff, 0x0203dfff,
0x0203ffff, 0x0203ffff, 0x0203ffff, 0x0203ffff, 0x0203ffff,
};
static uint8_t level_2bpp_to_8bpp[4] = {
0, 85, 171, 255
};
static uint8_t rgb_to_grayscale(uint8_t r, uint8_t g, uint8_t b) {
return (uint8_t)((CAMMY_IMAGE_Y_COEFFICIENT_R * (float)r)
+ (CAMMY_IMAGE_Y_COEFFICIENT_G * (float)g)
+ (CAMMY_IMAGE_Y_COEFFICIENT_B * (float)b));
}
static uint8_t rgb_to_tile_2bpp(uint8_t r, uint8_t g, uint8_t b, size_t x, size_t y) {
uint8_t gray = rgb_to_grayscale(r, g, b);
uint32_t slot = bayer_matrix[gray];
uint8_t from = (slot & 0x03000000) >> 24;
uint8_t to = (slot & 0x00030000) >> 16;
return (slot & (0x8000 >> ((y & 3) << 2) >> (x & 3)))? to: from;
}
static inline uint8_t tile_read(cammy_tile *tiles,
size_t x,
size_t y,
int stride) {
cammy_tile *tile = CAMMY_TILE_INDEXED(tiles, x, y, stride);
int tile_x = x & 7,
tile_y = y & 7;
return
((tile->data[ tile_y<<1] & (0x80 >> tile_x)) >> (tile_x ^ 7)
| ((tile->data[(tile_y<<1)|1] & (0x80 >> tile_x)) >> (tile_x ^ 7) << 1));
}
static inline void tile_write(cammy_tile *tiles,
size_t x,
size_t y,
int stride,
uint8_t value) {
cammy_tile *tile = CAMMY_TILE_INDEXED(tiles, x, y, stride);
int tile_x = x & 7,
tile_y = y & 7;
tile->data[ tile_y<<1] &= ~(1 << (tile_x ^ 7));
tile->data[(tile_y<<1)|1] &= ~(1 << (tile_x ^ 7));
tile->data[ tile_y<<1] |= (value & 0x01) << (tile_x ^ 7);
tile->data[(tile_y<<1)|1] |= ((value & 0x02) >> 1) << (tile_x ^ 7);
}
static inline void buf_read(uint8_t *buf,
size_t x, size_t y, size_t stride,
uint8_t *r, uint8_t *g, uint8_t *b,
int depth) {
*r = buf[depth*stride*y + depth*x],
*g = buf[depth*stride*y + depth*x + 1],
*b = buf[depth*stride*y + depth*x + 2];
}
static inline void buf_write(uint8_t *buf,
size_t x, size_t y, size_t stride,
uint8_t r, uint8_t g, uint8_t b,
int depth) {
buf[depth*stride*y + depth*x] = r;
buf[depth*stride*y + depth*x + 1] = g;
buf[depth*stride*y + depth*x + 2] = b;
}
cammy_image *cammy_image_new(cammy_image_format format,
size_t width,
size_t height) {
cammy_image *image;
size_t size;
switch (format) {
case CAMMY_IMAGE_TILE: {
size_t tiles_width = width >> 3,
tiles_height = height >> 3;
if (width & 7) tiles_width++;
if (height & 7) tiles_height++;
size = CAMMY_TILE_SIZE * tiles_width * tiles_height;
break;
}
case CAMMY_IMAGE_RGB: {
size = 3 * width * height;
break;
}
case CAMMY_IMAGE_RGBA: {
size = 4 * width * height;
break;
}
default: {
errno = EINVAL;
goto error_invalid_format;
}
}
if ((image = malloc(sizeof(*image))) == NULL) {
goto error_malloc_image;
}
if ((image->buf = malloc(size)) == NULL) {
goto error_malloc_buf;
}
image->format = format;
image->size = size;
image->width = width;
image->height = height;
return image;
error_malloc_buf:
free(image);
error_malloc_image:
error_invalid_format:
return NULL;
}
cammy_image *cammy_image_open_tile(const char *filename,
size_t width,
size_t height) {
cammy_image *image;
int fd;
size_t off;
if ((fd = open(filename, O_RDONLY)) < 0) {
goto error_open;
}
if ((image = cammy_image_new(CAMMY_IMAGE_TILE, width, height)) == NULL) {
goto error_image_new;
}
for (off=0; off<image->size;) {
ssize_t len;
if ((len = read(fd, &image->buf[off], CAMMY_IMAGE_CHUNK_SIZE)) < 0) {
goto error_read;
}
off += len;
}
close(fd);
return image;
error_read:
cammy_image_destroy(image);
error_image_new:
close(fd);
error_open:
return NULL;
}
void cammy_image_close(cammy_image *image) {
free(image);
}
void cammy_image_destroy(cammy_image *image) {
free(image->buf);
free(image);
}
int cammy_image_save_tile(cammy_image *image, const char *filename) {
size_t off,
rest = image->size;
int fd;
if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644)) < 0) {
goto error_open;
}
for (off=0; off<image->size; off+=CAMMY_IMAGE_CHUNK_SIZE) {
size_t chunk = rest > CAMMY_IMAGE_CHUNK_SIZE?
CAMMY_IMAGE_CHUNK_SIZE: rest;
ssize_t len;
if ((len = write(fd, image->buf + off, chunk)) < 0) {
goto error_write;
}
rest -= len;
}
close(fd);
return 0;
error_write:
close(fd);
error_open:
return -1;
}
void cammy_image_dither(uint8_t *dest,
uint8_t *src,
size_t width,
size_t height,
int depth,
cammy_tile_palette *palette) {
size_t x, y;
for (y=0; y<height; y++) {
for (x=0; x<width; x++) {
uint8_t r, g, b, value;
buf_read(src, x, y, width, &r, &g, &b, depth);
value = rgb_to_tile_2bpp(r, g, b, x, y);
r = palette->colors[value][0];
g = palette->colors[value][1];
b = palette->colors[value][2];
buf_write(dest, x, y, width, r, g, b, 3);
}
}
}
void cammy_image_split_to_tiles(cammy_tile *destr,
cammy_tile *destg,
cammy_tile *destb,
uint8_t *src,
size_t width,
size_t height,
int depth) {
size_t x, y;
for (y=0; y<height; y++) {
for (x=0; x<width; x++) {
uint8_t r, g, b;
buf_read(src, x, y, width, &r, &g, &b, depth);
tile_write(destr, x, y, width, rgb_to_tile_2bpp(r, r, r, x, y) ^ 3);
tile_write(destg, x, y, width, rgb_to_tile_2bpp(g, g, g, x, y) ^ 3);
tile_write(destb, x, y, width, rgb_to_tile_2bpp(b, b, b, x, y) ^ 3);
}
}
}
void cammy_image_copy_from_tile(uint8_t *dest,
cammy_tile *src,
size_t width,
size_t height,
int depth,
cammy_tile_palette *palette) {
size_t x, y;
for (y=0; y<height; y++) {
for (x=0; x<width; x++) {
uint8_t value = tile_read(src, x, y, width) ^ 3;
buf_write(dest, x, y, width, palette->colors[value][0],
palette->colors[value][1],
palette->colors[value][2], depth);
}
}
}
void cammy_image_dither_to_tile(cammy_tile *dest,
uint8_t *src,
size_t width,
size_t height,
int depth) {
size_t x, y;
memset(dest, '\x00', CAMMY_TILE_SIZE * (width >> 3) * (height >> 3));
for (y=0; y<height; y++) {
for (x=0; x<width; x++) {
uint8_t r, g, b;
buf_read(src, x, y, width, &r, &g, &b, depth);
tile_write(dest, x, y, width, rgb_to_tile_2bpp(r, g, b, x, y) ^ 3);
}
}
}
void cammy_image_merge_tiles(uint8_t *dest,
cammy_tile *srcr,
cammy_tile *srcg,
cammy_tile *srcb,
size_t width,
size_t height,
int depth) {
size_t x, y;
for (y=0; y<height; y++) {
for (x=0; x<width; x++) {
uint8_t r = tile_read(srcr, x, y, width) ^ 3,
g = tile_read(srcg, x, y, width) ^ 3,
b = tile_read(srcb, x, y, width) ^ 3;
buf_write(dest, x, y, width, level_2bpp_to_8bpp[r],
level_2bpp_to_8bpp[g],
level_2bpp_to_8bpp[b], depth);
}
}
}
void cammy_image_copy(cammy_image *dest,
cammy_image *src,
size_t x_dest,
size_t y_dest,
size_t x_src,
size_t y_src,
size_t width,
size_t height) {
size_t x_offset,
y_offset;
if (dest->format != CAMMY_IMAGE_TILE || src->format != CAMMY_IMAGE_TILE) {
return;
}
for (y_offset=0; y_offset<height; y_offset++) {
if (y_dest + y_offset > dest->height) {
break;
}
for (x_offset=0; x_offset<width; x_offset++) {
uint8_t value = tile_read(src->tiles, x_src + x_offset,
y_src + y_offset,
src->width);
if (x_dest + x_offset > dest->width) {
break;
}
tile_write(dest->tiles, x_dest + x_offset,
y_dest + y_offset,
dest->width,
value);
}
}
}