crt/img.cpp
#include "img.hpp"
#include <stdio.h>
#include <fcntl.h>
#include <math.h>
#define VAL_MAX 255
#define VAL_SCN "hhu"
#define VAL_SCN_WIDTH "3"
Img::Img(unsigned ww, unsigned hh): buf((rgb_t*)calloc(ww*hh, sizeof(rgb_t))), freebuf(buf), w(ww), h(hh) {
assert(w && h);
}
Img::Img(unsigned ww, unsigned hh, rgb_t* ib, void* fb): buf(ib), freebuf(fb), w(ww), h(hh) {
assert(w && h);
}
Img::~Img() {
free(freebuf);
}
Img* Img::from_ppm(const char* fn) {
size_t size;
char* buf = file_read(fn, size);
if (!buf) {
return NULL;
}
char* p = buf;
unsigned type;
unsigned ww, hh, max;
int len = 0;
if (sscanf(p, "P%u %u %u %u %n", &type, &ww, &hh, &max, &len) == 4) {
} else if (sscanf(p, "P%u #%*[^\n] %u %u %u %n", &type, &ww, &hh, &max, &len) == 4) {
} else {
LOG("'%s': invalid PPM header", fn);
return NULL;
}
if (type != 3 && type != 6) {
LOG("'%s': invalid PPM type", fn);
return NULL;
}
if (!ww || !hh || max != VAL_MAX) {
LOG("'%s': invalid PPM header values", fn);
return NULL;
}
p += len;
Img* rv = NULL;
if (type == 6) {
if (size != ww*hh*3+len) {
LOG("'%s': invalid PPM size", fn);
free(buf);
return NULL;
}
#ifdef USE_X
rv = new Img(ww, hh);
for (unsigned y=0; y<hh; y++) {
for (unsigned x=0; x<ww; x++) {
rgb_t& px = rv->at(x, y);
px.r = p[0];
px.g = p[1];
px.b = p[2];
p += 3;
}
}
free(buf);
#else
rv = new Img(ww, hh, (rgb_t*)p, (void*)buf);
#endif
} else {
rv = new Img(ww, hh);
for (unsigned y=0; y<hh; y++) {
for (unsigned x=0; x<ww; x++) {
rgb_t& px = rv->at(x, y);
if (sscanf(p, "%" VAL_SCN " %" VAL_SCN " %" VAL_SCN "%n", &px.r, &px.g, &px.b, &len) != 3) {
LOG("'%s': cannot parse PPM pixel", fn);
delete rv;
free(buf);
return NULL;
}
p += len;
}
}
free(buf);
}
return rv;
}
bool Img::to_ppm(const char* fn) const {
int fd = open(fn, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
if (fd == -1) {
LOG_ERRNO("open(%s)", fn);
return false;
}
if (!to_ppm(fd)) {
close(fd);
return false;
}
close(fd);
return true;
}
bool Img::to_ppm(int fd) const {
#ifdef WRITE_PLAIN_PPM
dprintf(fd, "P3\n%u %u\n%u\n", w, h, VAL_MAX);
for (unsigned y=0; y<h; y++) {
for (unsigned x=0; x<w; x++) {
const rgb_t& px = at(x, y);
dprintf(fd, "%3" VAL_SCN " %3" VAL_SCN " %3" VAL_SCN " ", px.r, px.g, px.b);
}
dprintf(fd, "\n");
}
#else
dprintf(fd, "P6\n%u %u\n%u\n", w, h, VAL_MAX);
#ifdef USE_X
for (unsigned y=0; y<h; y++) {
for (unsigned x=0; x<w; x++) {
const rgb_t& px = at(x, y);
unsigned char rgb[3];
rgb[0] = px.r;
rgb[1] = px.g;
rgb[2] = px.b;
if (write(fd, rgb, sizeof(rgb)) != sizeof(rgb)) {
LOG("cannot write");
return false;
}
}
}
#else
if (write(fd, buf, w*h*3) != (ssize_t)(w*h*3)) {
LOG("cannot write");
return false;
}
#endif
#endif
return true;
}
Img::rgb_t Img::avg(const unsigned x, const unsigned y, const unsigned xw, const unsigned yw) const {
unsigned r=0, g=0, b=0;
for (unsigned yy=y; yy<y+yw; ++yy) {
for (unsigned xx=x; xx<x+xw; ++xx) {
const rgb_t& p = at(xx, yy);
#ifdef IMAGE_LIN_AVG
r += p.r;
g += p.g;
b += p.b;
#else
r += p.r * p.r;
g += p.g * p.g;
b += p.b * p.b;
#endif
}
}
#ifdef IMAGE_LIN_AVG
return rgb_t(r/(xw*yw), g/(xw*yw), b/(xw*yw));
#else
// http://stackoverflow.com/questions/649454/what-is-the-best-way-to-average-two-colors-that-define-a-linear-gradient/29576746#29576746
// TODO: use lab space?
return rgb_t(sqrt(r/(xw*yw)), sqrt(g/(xw*yw)), sqrt(b/(xw*yw)));
#endif
}
Img::rgb_t Img::interpolate(coord_t x, coord_t y) const {
#ifdef IMAGE_INTERPOLATE
coord_t xf = floor(x);
coord_t xc = ceil(x);
coord_t yf = floor(y);
coord_t yc = ceil(y);
Img::rgb_t p[4] = {
at(xf, yf),
at(xf, yc),
at(xc, yf),
at(xc, yc)
};
xc = xc - x;
xf = 1.0 - xc;
yc = yc - y;
yf = 1.0 - yc;
p[0] *= xc * yc;
p[1] *= xc * yf;
p[2] *= xf * yc;
p[3] *= xf * yf;
// TODO: support IMGAGE_LIN_AVG
return rgb_t(
p[0].r + p[1].r + p[2].r + p[3].r,
p[0].g + p[1].g + p[2].g + p[3].g,
p[0].b + p[1].b + p[2].b + p[3].b
);
#else
return at(x, y);
#endif
}
bool Img::supersample(unsigned fac, Img* rv) const {
if (fac <= 1 || w % fac != 0 || h % fac != 0) {
return false;
}
if (rv->w != w/fac || rv->h != h/fac) {
return false;
}
for (unsigned y=0; y<rv->h; ++y) {
for (unsigned x=0; x<rv->w; ++x) {
rv->at(x, y) = avg(x*fac, y*fac, fac, fac);
}
}
return true;
}
void Img::mirror_x() {
for (unsigned x=0; x<w; ++x) {
for (unsigned y=0; y<h/2; ++y) {
rgb_t tmp = at(x, y);
at(x, y) = at(x, h-y-1);
at(x, h-y-1) = tmp;
}
}
}
void Img::mirror_y() {
for (unsigned y=0; y<h; ++y) {
for (unsigned x=0; x<w/2; ++x) {
rgb_t tmp = at(x, y);
at(x, y) = at(w-x-1, y);
at(w-x-1, y) = tmp;
}
}
}
Img* Img::rotate() const {
Img* rv = new Img(h, w);
for (unsigned x=0; x<w; ++x) {
for (unsigned y=0; y<h; ++y) {
rv->at(y, w-x-1) = at(x, y);
}
}
return rv;
}