#ifndef AGG_HEADLESS_COMMON_H
#define AGG_HEADLESS_COMMON_H
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include "agg_rendering_buffer.h"
#include "agg_color_rgba.h"
#ifndef AGG_ASSET_DIR
#define AGG_ASSET_DIR "."
#endif
namespace headless {
struct canvas {
unsigned width;
unsigned height;
unsigned bpp; std::vector<unsigned char> data;
agg::rendering_buffer rbuf;
canvas(unsigned w, unsigned h, unsigned bytes_per_pixel)
: width(w), height(h), bpp(bytes_per_pixel),
data(static_cast<size_t>(w) * h * bytes_per_pixel, 0) {
rbuf.attach(data.data(), w, h, static_cast<int>(w * bytes_per_pixel));
}
};
template <class Pixfmt>
inline bool write_raw(const char* path, Pixfmt& pixf, unsigned w, unsigned h) {
if (path == nullptr) return true;
FILE* f = fopen(path, "wb");
if (!f) {
fprintf(stderr, "Failed to open output '%s'\n", path);
return false;
}
uint32_t W = w, H = h;
bool ok = fwrite(&W, 4, 1, f) == 1 && fwrite(&H, 4, 1, f) == 1;
std::vector<unsigned char> row(static_cast<size_t>(w) * 4);
for (unsigned y = 0; ok && y < h; ++y) {
for (unsigned x = 0; x < w; ++x) {
typename Pixfmt::color_type c = pixf.pixel(static_cast<int>(x), static_cast<int>(y));
row[x * 4 + 0] = c.r;
row[x * 4 + 1] = c.g;
row[x * 4 + 2] = c.b;
row[x * 4 + 3] = c.a;
}
if (fwrite(row.data(), 1, row.size(), f) != row.size()) ok = false;
}
if (fclose(f) != 0) ok = false;
if (!ok) fprintf(stderr, "Failed to write output '%s'\n", path);
return ok;
}
struct image_rgba {
unsigned width = 0;
unsigned height = 0;
std::vector<unsigned char> rgba; bool ok = false;
};
inline image_rgba load_bmp_rgba(const std::string& path) {
image_rgba out;
FILE* f = fopen(path.c_str(), "rb");
if (!f) return out;
std::vector<unsigned char> d;
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, 0, SEEK_SET);
d.resize(sz);
if (fread(d.data(), 1, sz, f) != (size_t)sz) { fclose(f); return out; }
fclose(f);
if (d.size() < 54 || d[0] != 'B' || d[1] != 'M') return out;
uint32_t off = d[10] | (d[11] << 8) | (d[12] << 16) | (d[13] << 24);
int32_t w = d[18] | (d[19] << 8) | (d[20] << 16) | (d[21] << 24);
int32_t h = d[22] | (d[23] << 8) | (d[24] << 16) | (d[25] << 24);
uint16_t bpp = d[28] | (d[29] << 8);
bool top_down = h < 0;
unsigned width = (w < 0) ? -w : w;
unsigned height = (h < 0) ? -h : h;
unsigned bytes_pp = bpp / 8;
unsigned row_stride = ((width * bytes_pp + 3) / 4) * 4;
out.width = width;
out.height = height;
out.rgba.assign(static_cast<size_t>(width) * height * 4, 255);
for (unsigned y = 0; y < height; ++y) {
unsigned src_y = top_down ? y : (height - 1 - y);
size_t ro = off + static_cast<size_t>(src_y) * row_stride;
for (unsigned x = 0; x < width; ++x) {
size_t si = ro + static_cast<size_t>(x) * bytes_pp;
size_t di = (static_cast<size_t>(y) * width + x) * 4;
if (si + bytes_pp > d.size()) continue;
out.rgba[di + 0] = d[si + 2];
out.rgba[di + 1] = d[si + 1];
out.rgba[di + 2] = d[si + 0];
out.rgba[di + 3] = (bytes_pp == 4) ? d[si + 3] : 255;
}
}
out.ok = true;
return out;
}
inline image_rgba load_ppm_rgba(const std::string& path) {
image_rgba out;
FILE* f = fopen(path.c_str(), "rb");
if (!f) return out;
char magic[3] = {0};
if (fscanf(f, "%2s", magic) != 1 || strcmp(magic, "P6") != 0) { fclose(f); return out; }
auto read_uint = [&](unsigned& v) -> bool {
int c;
for (;;) {
c = fgetc(f);
if (c == '#') { while (c != '\n' && c != EOF) c = fgetc(f); }
else if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
else break;
}
if (c == EOF) return false;
v = 0;
while (c >= '0' && c <= '9') { v = v * 10 + (c - '0'); c = fgetc(f); }
return true;
};
unsigned width = 0, height = 0, maxv = 0;
if (!read_uint(width) || !read_uint(height) || !read_uint(maxv)) { fclose(f); return out; }
std::vector<unsigned char> rgb(static_cast<size_t>(width) * height * 3);
if (fread(rgb.data(), 1, rgb.size(), f) != rgb.size()) { fclose(f); return out; }
fclose(f);
out.width = width;
out.height = height;
out.rgba.assign(static_cast<size_t>(width) * height * 4, 255);
for (size_t i = 0; i < static_cast<size_t>(width) * height; ++i) {
out.rgba[i * 4 + 0] = rgb[i * 3 + 0];
out.rgba[i * 4 + 1] = rgb[i * 3 + 1];
out.rgba[i * 4 + 2] = rgb[i * 3 + 2];
out.rgba[i * 4 + 3] = 255;
}
out.ok = true;
return out;
}
inline std::string asset_path(const char* name) {
return std::string(AGG_ASSET_DIR) + "/" + name;
}
inline image_rgba load_spheres() {
#ifdef AGG_SPHERES_BMP
image_rgba img = load_bmp_rgba(AGG_SPHERES_BMP);
if (img.ok) return img;
#endif
return load_ppm_rgba(asset_path("spheres.ppm"));
}
inline void pack_image(const image_rgba& img, const char* order,
std::vector<unsigned char>& out_bytes,
agg::rendering_buffer& out_rbuf) {
bool has_alpha = (order[3] != 0);
unsigned bpp = has_alpha ? 4 : 3;
out_bytes.assign(static_cast<size_t>(img.width) * img.height * bpp, 0);
int ri = 0, gi = 1, bi = 2, ai = 3;
if (strcmp(order, "bgra") == 0) { ri = 2; gi = 1; bi = 0; ai = 3; }
else if (strcmp(order, "rgba") == 0) { ri = 0; gi = 1; bi = 2; ai = 3; }
else if (strcmp(order, "bgr") == 0) { ri = 2; gi = 1; bi = 0; }
else { ri = 0; gi = 1; bi = 2; }
for (size_t i = 0; i < static_cast<size_t>(img.width) * img.height; ++i) {
unsigned char r = img.rgba[i * 4 + 0];
unsigned char g = img.rgba[i * 4 + 1];
unsigned char b = img.rgba[i * 4 + 2];
unsigned char a = img.rgba[i * 4 + 3];
out_bytes[i * bpp + ri] = r;
out_bytes[i * bpp + gi] = g;
out_bytes[i * bpp + bi] = b;
if (has_alpha) out_bytes[i * bpp + ai] = a;
}
out_rbuf.attach(out_bytes.data(), img.width, img.height,
static_cast<int>(img.width * bpp));
}
}
#endif