image.rin

includes/image.rin · 135 lines

// Loading images: what stb_image is to LearnOpenGL.
//
//   image_load       .png / .jpg through WIC, Windows' own codecs, as RGBA8
//   image_load_hdr   Radiance .hdr, as linear float RGBA
//
// Pixels come back top row first, which is how D3D lays out a texture, so
// nothing is flipped. They live as long as the program.

import "win32.rin"
import "math.rin"
import "std/memops.rin"
import "std/cstr.rin"
import "std/print.rin"

ldexpf: proc[external](x: f32, exponent: i32) -> f32 = {}

image: struct = {
    pixels: [*]u8;       // RGBA8, width * height * 4 bytes
    width: i32;
    height: i32;
}

image_hdr: struct = {
    pixels: [*]vec4;     // linear RGB, a = 1
    width: i32;
    height: i32;
}

image_arena: memops_arena = {};
image_wic: *IWICImagingFactory = null;

image_init: proc() -> void = {
    if (image_wic != null) { return; }
    memops_arena_initialize(image_arena&);
    CoInitializeEx(null, COINIT_MULTITHREADED);
    CoCreateInstance(CLSID_WICImagingFactory&, null, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory&, cast(image_wic&, **void));
}

// Null pixels when the file cannot be read or decoded.
image_load: proc(path: [*]const char) -> image = {
    image_init();
    result: image = {};
    wide: [520]WCHAR = {};
    MultiByteToWideChar(CP_UTF8, 0, path, -1, wide[0]&, 520);
    decoder: *IWICBitmapDecoder = null;
    if (IWICImagingFactory_CreateDecoderFromFilename(image_wic, wide[0]&, null, GENERIC_READ,
                                                     WICDecodeOptions.WICDecodeMetadataCacheOnDemand, decoder&) < 0) {
        return result;
    }
    frame: *IWICBitmapFrameDecode = null;
    converter: *IWICFormatConverter = null;
    IWICBitmapDecoder_GetFrame(decoder, 0, frame&);
    IWICImagingFactory_CreateFormatConverter(image_wic, converter&);
    IWICFormatConverter_Initialize(converter, cast(frame, *IWICBitmapSource), GUID_WICPixelFormat32bppRGBA&,
                                   WICBitmapDitherType.WICBitmapDitherTypeNone, null, 0.0,
                                   WICBitmapPaletteType.WICBitmapPaletteTypeCustom);
    w: UINT = 0;
    h: UINT = 0;
    IWICFormatConverter_GetSize(converter, w&, h&);
    size: u64 = cast(w, u64) * cast(h, u64) * 4;
    result.pixels = cast(memops_arena_push(image_arena&, size, 16), [*]u8);
    IWICFormatConverter_CopyPixels(converter, null, w * 4, cast(size, UINT), result.pixels);
    IWICFormatConverter_Release(converter);
    IWICBitmapFrameDecode_Release(frame);
    IWICBitmapDecoder_Release(decoder);
    result.width = cast(w, i32);
    result.height = cast(h, i32);
    return result;
}

image_number: proc(text: [*]const u8, at: *u64) -> i32 = {
    value: i32 = 0;
    while (text[at[0]] >= '0' && text[at[0]] <= '9') {
        value = value * 10 + cast(text[at[0]] - '0', i32);
        at[0] += 1;
    }
    return value;
}

// Radiance .hdr stores each pixel as RGBE: three 8-bit mantissas sharing one
// 8-bit exponent. The header is text up to a blank line, then the size
// ("-Y height +X width", rows from the top). Each row is run-length encoded
// one channel at a time: a count above 128 repeats the next byte
// (count - 128) times, any other count is followed by that many bytes.
image_load_hdr: proc(path: [*]const char) -> image_hdr = {
    image_init();
    result: image_hdr = {};
    file: *FILE = fopen(path, "rb");
    if (file == null) { return result; }
    fseek(file, 0, 2);
    size: u64 = cast(ftell(file), u64);
    fseek(file, 0, 0);
    data: [*]u8 = cast(memops_arena_push(image_arena&, size + 1, 16), [*]u8);
    fread(data, 1, size, file);
    fclose(file);
    if (!cstr_starts_with(cast(data, [*]const char), "#?")) { return result; }

    at: u64 = 0;
    while (at + 1 < size && !(data[at] == '\n' && data[at + 1] == '\n')) { at += 1; }
    at += 5;   // the blank line and "-Y "
    result.height = image_number(data, at&);
    at += 4;   // " +X "
    result.width = image_number(data, at&);
    at += 1;

    w: u64 = cast(result.width, u64);
    result.pixels = cast(memops_arena_push(image_arena&, w * cast(result.height, u64) * sizeof(vec4), 16), [*]vec4);
    row: [*]u8 = cast(memops_arena_push(image_arena&, w * 4, 16), [*]u8);
    for (y: u64 = 0; y < cast(result.height, u64); y += 1) {
        at += 4;   // 2, 2, then the width again
        for (channel: u64 = 0; channel < 4; channel += 1) {
            x: u64 = 0;
            while (x < w) {
                count: u64 = cast(data[at], u64);
                at += 1;
                if (count > 128) {
                    count -= 128;
                    for (i: u64 = 0; i < count; i += 1) { row[(x + i) * 4 + channel] = data[at]; }
                    at += 1;
                } else {
                    for (i: u64 = 0; i < count; i += 1) { row[(x + i) * 4 + channel] = data[at + i]; }
                    at += count;
                }
                x += count;
            }
        }
        for (x: u64 = 0; x < w; x += 1) {
            e: i32 = cast(row[x * 4 + 3], i32);
            // mantissa / 256 * 2^(e - 128), and e = 0 means black
            scale: f32 = e == 0 ? 0.0 : ldexpf(1.0, e - 136);
            result.pixels[y * w + x] = v4(cast(row[x * 4], f32) * scale, cast(row[x * 4 + 1], f32) * scale, cast(row[x * 4 + 2], f32) * scale, 1.0);
        }
    }
    return result;
}
Part of rin-dx11, LearnOpenGL's lessons rewritten for Direct3D 11 and rin. Licensed CC BY-NC 4.0, after LearnOpenGL by Joey de Vries.