package pdf import "core:mem" // Bitmap is an 8-bit-per-component raster image, the thing `render_page` // hands back and the thing an image XObject decodes into. // // Alpha is NOT premultiplied in a Bitmap that leaves the library: that is what // a caller uploading to a texture expects, and premultiplying would silently // change colours for anyone who blends with the usual // (SRC_ALPHA, ONE_MINUS_SRC_ALPHA) setup. Compositing internally does use // premultiplied values; `bitmap_unpremultiply` is where the conversion back // happens, and `premultiplied` records which state a bitmap is in. Bitmap :: struct { width: int, height: int, // Components per pixel: 1 = grey, 2 = grey+alpha, 3 = RGB, 4 = RGBA. n: int, // Bytes per row. Rows are contiguous by default but a bitmap can be a // window onto a larger buffer, so never assume width*n. stride: int, pixels: []u8, premultiplied: bool, // The allocator `pixels` came from, so `bitmap_destroy` needs no argument. allocator: mem.Allocator, // False when `pixels` is borrowed and must not be freed. owns_pixels: bool, } // bitmap_make allocates a zeroed bitmap. `n` must be 1, 2, 3 or 4. // // The returned Bitmap owns its pixels; free it with `bitmap_destroy`. bitmap_make :: proc(width, height, n: int, allocator := context.allocator) -> (bmp: Bitmap, err: Error) { if width <= 0 || height <= 0 || n < 1 || n > 4 { return {}, .Invalid_Argument } // Guard the multiplication itself, not just the product: on a 32-bit // target width*height*n can wrap before the comparison ever runs. if width > MAX_BITMAP_PIXELS || height > MAX_BITMAP_PIXELS { return {}, .Bitmap_Too_Large } if width * height > MAX_BITMAP_PIXELS { return {}, .Bitmap_Too_Large } stride := width * n pixels, alloc_err := make([]u8, stride * height, allocator) if alloc_err != nil { return {}, .Out_Of_Memory } return Bitmap{ width = width, height = height, n = n, stride = stride, pixels = pixels, allocator = allocator, owns_pixels = true, }, .None } bitmap_destroy :: proc(bmp: ^Bitmap) { if bmp == nil { return } if bmp.owns_pixels && bmp.pixels != nil { delete(bmp.pixels, bmp.allocator) } bmp^ = {} } // bitmap_row returns row `y`, or nil if it is out of range. bitmap_row :: proc(bmp: ^Bitmap, y: int) -> []u8 { if bmp == nil || y < 0 || y >= bmp.height { return nil } off := y * bmp.stride if off + bmp.stride > len(bmp.pixels) { return nil } return bmp.pixels[off:off + bmp.stride] } bitmap_pixel_offset :: proc(bmp: ^Bitmap, x, y: int) -> (int, bool) { if bmp == nil || x < 0 || y < 0 || x >= bmp.width || y >= bmp.height { return 0, false } off := y * bmp.stride + x * bmp.n if off + bmp.n > len(bmp.pixels) { return 0, false } return off, true } // bitmap_clear fills every pixel with `value` in all components. bitmap_clear :: proc(bmp: ^Bitmap, value: u8 = 0) { if bmp == nil { return } for &b in bmp.pixels { b = value } } // bitmap_fill paints a solid colour. Components beyond `bmp.n` are ignored, // and a missing alpha component means the colour is written opaque. bitmap_fill :: proc(bmp: ^Bitmap, color: [4]u8) { if bmp == nil { return } n := bmp.n for y in 0 ..< bmp.height { row := bitmap_row(bmp, y) for x := 0; x + n <= len(row); x += n { for c in 0 ..< n { row[x + c] = color[c] } } } } // bitmap_unpremultiply converts a premultiplied bitmap to straight alpha in // place. A no-op on a bitmap that is already straight, or has no alpha. bitmap_unpremultiply :: proc(bmp: ^Bitmap) { if bmp == nil || !bmp.premultiplied { return } if bmp.n != 2 && bmp.n != 4 { bmp.premultiplied = false return } ai := bmp.n - 1 for y in 0 ..< bmp.height { row := bitmap_row(bmp, y) for x := 0; x + bmp.n <= len(row); x += bmp.n { a := row[x + ai] if a == 0 { // Fully transparent: colour is unrecoverable, and leaving the // multiplied zeros makes bilinear filtering pull black halos // into the edges. Zero is the least-bad choice and matches // what every other renderer does. for c in 0 ..< ai { row[x + c] = 0 } continue } if a == 255 { continue } for c in 0 ..< ai { v := (int(row[x + c]) * 255 + int(a) / 2) / int(a) row[x + c] = u8(min(v, 255)) } } } bmp.premultiplied = false } // bitmap_premultiply converts straight alpha to premultiplied in place. bitmap_premultiply :: proc(bmp: ^Bitmap) { if bmp == nil || bmp.premultiplied { return } if bmp.n != 2 && bmp.n != 4 { bmp.premultiplied = true return } ai := bmp.n - 1 for y in 0 ..< bmp.height { row := bitmap_row(bmp, y) for x := 0; x + bmp.n <= len(row); x += bmp.n { a := int(row[x + ai]) if a == 255 { continue } for c in 0 ..< ai { row[x + c] = u8((int(row[x + c]) * a + 127) / 255) } } } bmp.premultiplied = true } // bitmap_to_rgba returns an RGBA8 copy, expanding grey and adding an opaque // alpha channel as needed. Callers uploading to a GPU texture want this. // The result is allocated with `allocator` and owned by the caller. bitmap_to_rgba :: proc(bmp: ^Bitmap, allocator := context.allocator) -> (out: Bitmap, err: Error) { if bmp == nil { return {}, .Invalid_Argument } out = bitmap_make(bmp.width, bmp.height, 4, allocator) or_return for y in 0 ..< bmp.height { src := bitmap_row(bmp, y) dst := bitmap_row(&out, y) for x in 0 ..< bmp.width { si := x * bmp.n di := x * 4 if si + bmp.n > len(src) || di + 4 > len(dst) { break } switch bmp.n { case 1: dst[di] = src[si]; dst[di + 1] = src[si]; dst[di + 2] = src[si]; dst[di + 3] = 255 case 2: dst[di] = src[si]; dst[di + 1] = src[si]; dst[di + 2] = src[si]; dst[di + 3] = src[si + 1] case 3: dst[di] = src[si]; dst[di + 1] = src[si + 1]; dst[di + 2] = src[si + 2]; dst[di + 3] = 255 case 4: dst[di] = src[si]; dst[di + 1] = src[si + 1]; dst[di + 2] = src[si + 2]; dst[di + 3] = src[si + 3] } } } out.premultiplied = bmp.premultiplied return out, .None }