initial commit
This commit is contained in:
+187
@@ -0,0 +1,187 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user