Files
multirom_m86/lib/framebuffer_png.c
2016-12-18 14:13:41 +03:00

366 lines
9.3 KiB
C

/*
* This file is part of MultiROM.
*
* MultiROM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MultiROM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MultiROM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
#include <png.h>
#include "log.h"
#include "framebuffer.h"
#include "util.h"
#include "containers.h"
#if 0
#define PNG_LOG(x...) INFO(x)
#else
#define PNG_LOG(x...) ;
#endif
struct png_cache_entry
{
char *path;
px_type *data;
int width;
int height;
int refcnt;
};
static struct png_cache_entry **png_cache = NULL;
// http://willperone.net/Code/codescaling.php
static px_type *scale_png_img(px_type *fi_data, int orig_w, int orig_h, int new_w, int new_h)
{
if(orig_w == new_w && orig_h == new_h)
return fi_data;
uint32_t *in = (uint32_t*)fi_data;
#if PIXEL_SIZE == 2
// need another byte for alpha. Make it 4 to make it simpler
uint32_t *out = malloc(4 * new_w * new_h);
#else
uint32_t *out = malloc(PIXEL_SIZE * new_w * new_h);
#endif
const int YD = (orig_h / new_h) * orig_w - orig_w;
const int YR = orig_h % new_h;
const int XD = orig_w / new_w;
const int XR = orig_w % new_w;
int in_off = 0, out_off = 0;
int x, y, YE, XE;
for(y = new_h, YE = 0; y > 0; --y)
{
for(x = new_w, XE = 0; x > 0; --x)
{
out[out_off++] = in[in_off];
in_off += XD;
XE += XR;
if(XE >= new_w)
{
XE -= new_w;
++in_off;
}
}
in_off += YD;
YE += YR;
if(YE >= new_h)
{
YE -= new_h;
in_off += orig_w;
}
}
free(fi_data);
return (px_type*)out;
}
static px_type *load_png(const char *path, int destW, int destH)
{
FILE *fp;
unsigned char header[8];
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
uint32_t bytes_per_row;
px_type *data_dest = NULL, *data_itr;
size_t i, y;
int si, alpha;
int px_per_row;
uint32_t src_pix;
png_bytep *rows = NULL;
fp = fopen(path, "rbe");
if(!fp)
return NULL;
size_t bytesRead = fread(header, 1, sizeof(header), fp);
if (bytesRead != sizeof(header)) {
goto exit;
}
if (png_sig_cmp(header, 0, sizeof(header))) {
goto exit;
}
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr) {
goto exit;
}
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
goto exit;
}
if (setjmp(png_jmpbuf(png_ptr))) {
goto exit;
}
png_set_packing(png_ptr);
png_init_io(png_ptr, fp);
png_set_sig_bytes(png_ptr, sizeof(header));
png_read_info(png_ptr, info_ptr);
png_uint_32 width, height;
size_t stride, pixelSize;
int color_type, bit_depth, channels;
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
NULL, NULL, NULL);
channels = png_get_channels(png_ptr, info_ptr);
stride = 4 * width;
pixelSize = stride * height;
if (!(bit_depth == 8 &&
((channels == 3 && color_type == PNG_COLOR_TYPE_RGB) ||
(channels == 4 && color_type == PNG_COLOR_TYPE_RGBA)))) {
goto exit;
}
if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png_ptr);
png_set_interlace_handling(png_ptr);
png_read_update_info(png_ptr, info_ptr);
#if PIXEL_SIZE == 2
// need another byte for alpha. Make it 4 to make it simpler
data_dest = malloc(4 * width * height);
#else
data_dest = malloc(PIXEL_SIZE * width * height);
#endif
data_itr = data_dest;
bytes_per_row = png_get_rowbytes(png_ptr, info_ptr);
rows = malloc(sizeof(png_bytep)*height);
for(y = 0; y < height; ++y)
rows[y] = malloc(bytes_per_row);
png_read_image(png_ptr, rows);
for(y = 0; y < height; ++y)
{
for(i = 0, si = 0; i < width; ++i)
{
if(channels == 4)
{
src_pix = ((uint32_t*)rows[y])[i];
src_pix = (src_pix & 0xFF00FF00) | ((src_pix & 0xFF0000) >> 16) | ((src_pix & 0xFF) << 16);
}
else //if(channels == 3) - no other option
{
src_pix = (rows[y][si++] << 16); // R
src_pix |= (rows[y][si++] << 8); // G
src_pix |= (rows[y][si++]); // B
src_pix |= 0xFF000000; // A
}
*data_itr = (px_type)fb_convert_color(src_pix);
++data_itr;
#if PIXEL_SIZE == 2
// Store alpha value for 5 and 6 bit values in next two bytes
alpha = ((src_pix & 0xFF000000) >> 24);
((uint8_t*)data_itr)[0] = ((((alpha*100)/0xFF)*31)/100);
((uint8_t*)data_itr)[1] = ((((alpha*100)/0xFF)*63)/100);
++data_itr;
#endif
}
free(rows[y]);
}
free(rows);
data_dest = scale_png_img(data_dest, width, height, destW, destH);
exit:
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
fclose(fp);
return data_dest;
}
static void destroy_png_cache_entry(void *entry)
{
struct png_cache_entry *e = (struct png_cache_entry*)entry;
free(e->path);
free(e->data);
free(e);
}
px_type *fb_png_get(const char *path, int w, int h)
{
// Try to find it in cache
struct png_cache_entry **itr;
for(itr = png_cache; itr && *itr; ++itr)
{
if((*itr)->width == w && (*itr)->height == h && strcmp(path, (*itr)->path) == 0)
{
++(*itr)->refcnt;
PNG_LOG("PNG %s (%dx%d) %p found in cache, refcnt increased to %d\n", path, w, h, (*itr)->data, (*itr)->refcnt);
return (*itr)->data;
}
}
// not in cache yet, load and create cache entry
px_type *data = load_png(path, w, h);
if(!data)
{
PNG_LOG("PNG %s (%dx%d) failed to load\n", path, w, h);
return NULL;
}
PNG_LOG("PNG %s (%dx%d) loaded\n", path, w, h);
struct png_cache_entry *e = mzalloc(sizeof(struct png_cache_entry));
e->path = strdup(path);
e->data = data;
e->width = w;
e->height = h;
e->refcnt = 1;
list_add(&png_cache, e);
PNG_LOG("PNG %s (%dx%d) %p added into cache\n", path, w, h, data);
return data;
}
void fb_png_release(px_type *data)
{
struct png_cache_entry **itr;
for(itr = png_cache; itr && *itr; ++itr)
{
if((*itr)->data == data)
{
--(*itr)->refcnt;
PNG_LOG("PNG %s (%dx%d) %p released, refcnt is %d\n", (*itr)->path, (*itr)->width, (*itr)->height, data, (*itr)->refcnt);
return;
}
}
PNG_LOG("PNG %p not found in cache!\n", data);
}
void fb_png_drop_unused(void)
{
struct png_cache_entry **itr;
for(itr = png_cache; itr && *itr;)
{
if((*itr)->refcnt <= 0)
{
PNG_LOG("PNG %s (%dx%d) %p removed from cache\n", (*itr)->path, (*itr)->width, (*itr)->height, data);
list_rm(&png_cache, *itr, &destroy_png_cache_entry);
itr = png_cache;
}
else
{
++itr;
}
}
}
static inline void convert_fb_px_to_rgb888(px_type src, uint8_t *dest)
{
dest[0] = PX_GET_R(src);
dest[1] = PX_GET_G(src);
dest[2] = PX_GET_B(src);
}
int fb_png_save_img(const char *path, int w, int h, int stride, px_type *data)
{
FILE *fp = NULL;
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
int res = -1;
int y, x;
uint8_t *row = NULL;
const int stride_leftover = stride - w;
const int w_png_bytes = w * 3;
px_type * volatile itr = data;
fp = fopen(path, "we");
if(!fp)
{
ERROR("Failed to open %s for writing\n", path);
return -1;
}
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
goto exit;
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL)
goto exit;
if (setjmp(png_jmpbuf(png_ptr)))
goto exit;
png_init_io(png_ptr, fp);
png_set_IHDR(png_ptr, info_ptr, w, h,
8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_write_info(png_ptr, info_ptr);
row = malloc(w*3);
for(y = 0; y < h; ++y)
{
for(x = 0; x < w_png_bytes; x += 3)
{
convert_fb_px_to_rgb888(*itr, row + x);
++itr;
}
itr += stride_leftover;
png_write_row(png_ptr, row);
}
png_write_end(png_ptr, NULL);
res = 0;
exit:
if(info_ptr)
png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
if(png_ptr)
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
if(fp)
fclose(fp);
if(row)
free(row);
return res;
}