std1.0.0-beta-7 SafeC docs

Source gui/gui_png_image.sc

View in the repository

// SafeC Standard Library — GUI: GuiImage helpers (see gui_png.h): blank
// images, screenshots, PNG files, alpha conversion and resampling.
#pragma once
#include <std/stddef.h>
#include <std/gui/gui.h>
#include <std/gui/gui_png.h>
#include <std/mem.h>
#include <std/io_file.h>
#include <std/io_traits.h>

namespace std {

struct GuiImage GuiImage::new(int width, int height) {
    struct GuiImage img;
    if (width < 1) { width = 1; }
    if (height < 1) { height = 1; }
    unsafe { img.pixels = (&heap unsigned char)alloc_zeroed_raw_((unsigned long)width * (unsigned long)height * 4UL); }
    img.width = width;
    img.height = height;
    img.premultiplied = 1;
    return img;
}

struct GuiImage GuiImage::from_window(const &GuiWindow win) {
    struct GuiImage img = GuiImage::new(win.width, win.height);
    unsafe {
        unsigned long n = (unsigned long)win.width * (unsigned long)win.height * 4UL;
        safe_memcpy(((unsigned char*)img.pixels)[0..n], ((const unsigned char*)win.pixels)[0..n]);
    }
    return img;
}

int GuiImage::load_png_file(const &char path) {
    unsafe {
        self.pixels = (&heap unsigned char)0;
        self.width = 0;
        self.height = 0;
        self.premultiplied = 0;
    }
    ?&File fq = File::open(path, "rb");
    match (fq) {
        case null: return 0;
        case some(f): {
            long long n = f.size();
            if (n <= 0LL || n > 0x40000000LL) { f.close(); return 0; }
            unsigned char* buf;
            unsafe { buf = (unsigned char*)alloc_raw_((unsigned long)n); }
            int ok;
            unsafe { ok = read_exact(f, ((unsigned char*)buf)[0..(unsigned long)n]); }
            f.close();
            int r = 0;
            if (ok) { unsafe { r = self.load_png_n_((const unsigned char*)buf, (unsigned long)n); } }
            unsafe { dealloc(buf); }
            return r;
        }
    }
    return 0;
}

// ── PNG writing ───────────────────────────────────────────────────────────

static unsigned int gGuiCrcTable[256];
static int gGuiCrcReady = 0;

static unsigned int __gui_crc(unsigned int crc, const unsigned char* p, unsigned long n) {
    if (!gGuiCrcReady) {
        unsigned int i = 0U;
        while (i < 256U) {
            unsigned int c = i;
            int k = 0;
            while (k < 8) {
                c = (c & 1U) != 0U ? (0xEDB88320U ^ (c >> 1)) : (c >> 1);
                k = k + 1;
            }
            gGuiCrcTable[i] = c;
            i = i + 1U;
        }
        gGuiCrcReady = 1;
    }
    unsigned long i = 0UL;
    unsafe {
        while (i < n) {
            crc = gGuiCrcTable[(crc ^ (unsigned int)p[i]) & 0xFFU] ^ (crc >> 8);
            i = i + 1UL;
        }
    }
    return crc;
}

static void __gui_be32(unsigned char* p, unsigned int v) {
    unsafe {
        p[0] = (unsigned char)(v >> 24);
        p[1] = (unsigned char)(v >> 16);
        p[2] = (unsigned char)(v >> 8);
        p[3] = (unsigned char)v;
    }
}

static int __gui_png_chunk(&File f, const char* type, const unsigned char* data, unsigned long n) {
    unsigned char head[8];
    unsafe {
        __gui_be32(&head[0], (unsigned int)n);
        head[4] = (unsigned char)type[0]; head[5] = (unsigned char)type[1];
        head[6] = (unsigned char)type[2]; head[7] = (unsigned char)type[3];
    }
    unsigned int crc = __gui_crc(0xFFFFFFFFU, &head[4], 4UL);
    crc = __gui_crc(crc, data, n) ^ 0xFFFFFFFFU;
    unsigned char tail[4];
    __gui_be32(&tail[0], crc);
    int ok = 1;
    unsafe {
        if (write_all(f, head[0..8UL]) == 0) { ok = 0; }
        if (n > 0UL && write_all(f, ((const unsigned char*)data)[0..n]) == 0) { ok = 0; }
        if (write_all(f, tail[0..4UL]) == 0) { ok = 0; }
    }
    return ok;
}

int GuiImage::save_png(const &char path) const {
    int w = self.width;
    int h = self.height;
    if (w < 1 || h < 1) { return 0; }
    // Raw scanlines: filter byte 0 + RGBA (straight alpha).
    unsigned long rowBytes = (unsigned long)w * 4UL + 1UL;
    unsigned long rawLen = rowBytes * (unsigned long)h;
    unsigned char* raw;
    unsafe { raw = (unsigned char*)alloc_raw_(rawLen); }
    int y = 0;
    unsafe {
        while (y < h) {
            unsigned char* dst = raw + (unsigned long)y * rowBytes;
            dst[0] = (unsigned char)0;
            const unsigned char* src = (const unsigned char*)self.pixels + (unsigned long)y * (unsigned long)w * 4UL;
            int x = 0;
            while (x < w) {
                unsigned int a = (unsigned int)src[3];
                unsigned int r = (unsigned int)src[0];
                unsigned int g = (unsigned int)src[1];
                unsigned int b = (unsigned int)src[2];
                if (self.premultiplied && a > 0U && a < 255U) {
                    r = (r * 255U + a / 2U) / a; if (r > 255U) { r = 255U; }
                    g = (g * 255U + a / 2U) / a; if (g > 255U) { g = 255U; }
                    b = (b * 255U + a / 2U) / a; if (b > 255U) { b = 255U; }
                }
                dst[1 + x * 4] = (unsigned char)r;
                dst[2 + x * 4] = (unsigned char)g;
                dst[3 + x * 4] = (unsigned char)b;
                dst[4 + x * 4] = (unsigned char)a;
                src = src + 4;
                x = x + 1;
            }
            y = y + 1;
        }
    }
    // zlib stream of stored DEFLATE blocks (<= 65535 bytes each).
    unsigned long nblocks = (rawLen + 65534UL) / 65535UL;
    unsigned long zLen = 2UL + rawLen + nblocks * 5UL + 4UL;
    unsigned char* z;
    unsafe { z = (unsigned char*)alloc_raw_(zLen); }
    unsigned long o = 0UL;
    unsigned int s1 = 1U;
    unsigned int s2 = 0U;
    unsafe {
        z[0] = (unsigned char)0x78; z[1] = (unsigned char)0x01;
        o = 2UL;
        unsigned long pos = 0UL;
        while (pos < rawLen) {
            unsigned long n = rawLen - pos;
            if (n > 65535UL) { n = 65535UL; }
            z[o] = (unsigned char)(pos + n >= rawLen ? 1 : 0);
            z[o + 1UL] = (unsigned char)(n & 0xFFUL);
            z[o + 2UL] = (unsigned char)((n >> 8) & 0xFFUL);
            z[o + 3UL] = (unsigned char)(~n & 0xFFUL);
            z[o + 4UL] = (unsigned char)((~n >> 8) & 0xFFUL);
            o = o + 5UL;
            safe_memcpy((z + o)[0..n], (raw + pos)[0..n]);
            unsigned long k = 0UL;
            while (k < n) {
                s1 = (s1 + (unsigned int)raw[pos + k]) % 65521U;
                s2 = (s2 + s1) % 65521U;
                k = k + 1UL;
            }
            o = o + n;
            pos = pos + n;
        }
        __gui_be32(z + o, (s2 << 16) | s1);
        o = o + 4UL;
    }
    ?&File fq = File::open(path, "wb");
    int ok = 0;
    match (fq) {
        case null: { }
        case some(f): {
            unsigned char sig[8];
            sig[0] = (unsigned char)137; sig[1] = (unsigned char)80; sig[2] = (unsigned char)78; sig[3] = (unsigned char)71;
            sig[4] = (unsigned char)13; sig[5] = (unsigned char)10; sig[6] = (unsigned char)26; sig[7] = (unsigned char)10;
            unsigned char ihdr[13];
            __gui_be32(&ihdr[0], (unsigned int)w);
            __gui_be32(&ihdr[4], (unsigned int)h);
            ihdr[8] = (unsigned char)8;     // bit depth
            ihdr[9] = (unsigned char)6;     // RGBA
            ihdr[10] = (unsigned char)0; ihdr[11] = (unsigned char)0; ihdr[12] = (unsigned char)0;
            ok = 1;
            unsafe {
                if (write_all(f, sig[0..8UL]) == 0) { ok = 0; }
            }
            if (!__gui_png_chunk(f, "IHDR", &ihdr[0], 13UL)) { ok = 0; }
            if (!__gui_png_chunk(f, "IDAT", z, o)) { ok = 0; }
            unsafe { if (!__gui_png_chunk(f, "IEND", (const unsigned char*)&ihdr[0], 0UL)) { ok = 0; } }
            f.close();
        }
    }
    unsafe {
        dealloc(raw);
        dealloc(z);
    }
    return ok;
}

// ── alpha and copies ──────────────────────────────────────────────────────

void GuiImage::premultiply() {
    if (self.premultiplied) { return; }
    unsigned long n = (unsigned long)self.width * (unsigned long)self.height;
    unsafe {
        unsigned char* p = (unsigned char*)self.pixels;
        unsigned long i = 0UL;
        while (i < n) {
            unsigned int a = (unsigned int)p[3];
            if (a != 255U) {
                p[0] = (unsigned char)(((unsigned int)p[0] * a + 127U) / 255U);
                p[1] = (unsigned char)(((unsigned int)p[1] * a + 127U) / 255U);
                p[2] = (unsigned char)(((unsigned int)p[2] * a + 127U) / 255U);
            }
            p = p + 4;
            i = i + 1UL;
        }
    }
    self.premultiplied = 1;
}

void GuiImage::unpremultiply() {
    if (!self.premultiplied) { return; }
    unsigned long n = (unsigned long)self.width * (unsigned long)self.height;
    unsafe {
        unsigned char* p = (unsigned char*)self.pixels;
        unsigned long i = 0UL;
        while (i < n) {
            unsigned int a = (unsigned int)p[3];
            if (a != 255U && a != 0U) {
                unsigned int r = ((unsigned int)p[0] * 255U + a / 2U) / a;
                unsigned int g = ((unsigned int)p[1] * 255U + a / 2U) / a;
                unsigned int b = ((unsigned int)p[2] * 255U + a / 2U) / a;
                p[0] = (unsigned char)(r > 255U ? 255U : r);
                p[1] = (unsigned char)(g > 255U ? 255U : g);
                p[2] = (unsigned char)(b > 255U ? 255U : b);
            }
            p = p + 4;
            i = i + 1UL;
        }
    }
    self.premultiplied = 0;
}

struct GuiImage GuiImage::copy() const {
    struct GuiImage img = GuiImage::new(self.width, self.height);
    unsafe {
        unsigned long n = (unsigned long)self.width * (unsigned long)self.height * 4UL;
        safe_memcpy(((unsigned char*)img.pixels)[0..n], ((const unsigned char*)self.pixels)[0..n]);
    }
    img.premultiplied = self.premultiplied;
    return img;
}

unsigned int GuiImage::get_pixel(int x, int y) const {
    if (x < 0 || y < 0 || x >= self.width || y >= self.height) { return 0U; }
    unsafe {
        unsigned long i = ((unsigned long)y * (unsigned long)self.width + (unsigned long)x) * 4UL;
        return ((unsigned int)self.pixels[i] << 24) | ((unsigned int)self.pixels[i + 1UL] << 16) |
               ((unsigned int)self.pixels[i + 2UL] << 8) | (unsigned int)self.pixels[i + 3UL];
    }
}

void GuiImage::set_pixel(int x, int y, unsigned int rgba) {
    if (x < 0 || y < 0 || x >= self.width || y >= self.height) { return; }
    unsafe {
        unsigned long i = ((unsigned long)y * (unsigned long)self.width + (unsigned long)x) * 4UL;
        self.pixels[i] = (unsigned char)(rgba >> 24);
        self.pixels[i + 1UL] = (unsigned char)(rgba >> 16);
        self.pixels[i + 2UL] = (unsigned char)(rgba >> 8);
        self.pixels[i + 3UL] = (unsigned char)rgba;
    }
}

// Resampling works on premultiplied values (so transparent pixels don't
// bleed their color into the result).
struct GuiImage GuiImage::scaled(int width, int height) const {
    if (width < 1) { width = 1; }
    if (height < 1) { height = 1; }
    struct GuiImage src = self.copy();
    src.premultiply();
    struct GuiImage dst = GuiImage::new(width, height);
    int sw = src.width;
    int sh = src.height;
    double fx = (double)sw / (double)width;
    double fy = (double)sh / (double)height;
    unsafe {
        const unsigned char* sp = (const unsigned char*)src.pixels;
        unsigned char* dp = (unsigned char*)dst.pixels;
        int y = 0;
        while (y < height) {
            int x = 0;
            while (x < width) {
                double acc0 = 0.0; double acc1 = 0.0; double acc2 = 0.0; double acc3 = 0.0;
                if (fx > 1.0 || fy > 1.0) {
                    // Area average over the source box this pixel covers.
                    double bx0 = (double)x * fx; double bx1 = bx0 + fx;
                    double by0 = (double)y * fy; double by1 = by0 + fy;
                    if (fx < 1.0) { double c = (bx0 + bx1) * 0.5; bx0 = c - 0.5; bx1 = c + 0.5; }
                    if (fy < 1.0) { double c = (by0 + by1) * 0.5; by0 = c - 0.5; by1 = c + 0.5; }
                    double total = 0.0;
                    int iy = (int)by0;
                    while ((double)iy < by1) {
                        double wy = ((double)(iy + 1) < by1 ? (double)(iy + 1) : by1) - ((double)iy > by0 ? (double)iy : by0);
                        int cy = iy < 0 ? 0 : (iy >= sh ? sh - 1 : iy);
                        int ix = (int)bx0;
                        while ((double)ix < bx1) {
                            double wx = ((double)(ix + 1) < bx1 ? (double)(ix + 1) : bx1) - ((double)ix > bx0 ? (double)ix : bx0);
                            int cx = ix < 0 ? 0 : (ix >= sw ? sw - 1 : ix);
                            double wgt = wx * wy;
                            const unsigned char* q = sp + ((unsigned long)cy * (unsigned long)sw + (unsigned long)cx) * 4UL;
                            acc0 = acc0 + (double)q[0] * wgt; acc1 = acc1 + (double)q[1] * wgt;
                            acc2 = acc2 + (double)q[2] * wgt; acc3 = acc3 + (double)q[3] * wgt;
                            total = total + wgt;
                            ix = ix + 1;
                        }
                        iy = iy + 1;
                    }
                    if (total > 0.0) { acc0 = acc0 / total; acc1 = acc1 / total; acc2 = acc2 / total; acc3 = acc3 / total; }
                } else {
                    // Bilinear between the four nearest source pixels.
                    double sx = ((double)x + 0.5) * fx - 0.5;
                    double sy = ((double)y + 0.5) * fy - 0.5;
                    if (sx < 0.0) { sx = 0.0; }
                    if (sy < 0.0) { sy = 0.0; }
                    int x0 = (int)sx; int y0 = (int)sy;
                    int x1 = x0 + 1 < sw ? x0 + 1 : sw - 1;
                    int y1 = y0 + 1 < sh ? y0 + 1 : sh - 1;
                    double tx = sx - (double)x0; double ty = sy - (double)y0;
                    const unsigned char* q00 = sp + ((unsigned long)y0 * (unsigned long)sw + (unsigned long)x0) * 4UL;
                    const unsigned char* q10 = sp + ((unsigned long)y0 * (unsigned long)sw + (unsigned long)x1) * 4UL;
                    const unsigned char* q01 = sp + ((unsigned long)y1 * (unsigned long)sw + (unsigned long)x0) * 4UL;
                    const unsigned char* q11 = sp + ((unsigned long)y1 * (unsigned long)sw + (unsigned long)x1) * 4UL;
                    double w00 = (1.0 - tx) * (1.0 - ty); double w10 = tx * (1.0 - ty);
                    double w01 = (1.0 - tx) * ty; double w11 = tx * ty;
                    acc0 = (double)q00[0] * w00 + (double)q10[0] * w10 + (double)q01[0] * w01 + (double)q11[0] * w11;
                    acc1 = (double)q00[1] * w00 + (double)q10[1] * w10 + (double)q01[1] * w01 + (double)q11[1] * w11;
                    acc2 = (double)q00[2] * w00 + (double)q10[2] * w10 + (double)q01[2] * w01 + (double)q11[2] * w11;
                    acc3 = (double)q00[3] * w00 + (double)q10[3] * w10 + (double)q01[3] * w01 + (double)q11[3] * w11;
                }
                unsigned char* o = dp + ((unsigned long)y * (unsigned long)width + (unsigned long)x) * 4UL;
                o[0] = (unsigned char)(int)(acc0 + 0.5);
                o[1] = (unsigned char)(int)(acc1 + 0.5);
                o[2] = (unsigned char)(int)(acc2 + 0.5);
                o[3] = (unsigned char)(int)(acc3 + 0.5);
                x = x + 1;
            }
            y = y + 1;
        }
    }
    src.free();
    if (!self.premultiplied) { dst.unpremultiply(); }
    return dst;
}

} // namespace std