Packages

Render BMP images in Gleam

Current section

Files

Jump to
blimp src blimp.gleam
Raw

src/blimp.gleam

/// Build a BMP image by iterating over each pixel with some state.
///
/// The callback gets the X and Y position of the pixel, where 0,0 is
/// bottom-left.
///
/// Rendering starts at the top and goes over each row, left to right, top to
/// bottom.
///
pub fn render(
width width: Int,
height height: Int,
state state: state,
pixel plot: fn(state, Int, Int) -> #(state, Pixel),
) -> #(state, BitArray) {
let #(bmp, padding_size) = file_header(width, height)
let bmp = dib_header(bmp, width, height)
let padding_bits = padding_size * 8
let padding = <<0:size(padding_bits)>>
render_loop(bmp, width, 0, height - 1, padding, state, plot)
}
/// Build a BMP image by iterating over each pixel.
///
/// The callback gets the X and Y position of the pixel, where 0,0 is
/// bottom-left.
///
/// Rendering starts at the top and goes over each row, left to right, top to
/// bottom.
///
pub fn simple_render(
width width: Int,
height height: Int,
pixel plot: fn(Int, Int) -> Pixel,
) -> BitArray {
render(width, height, Nil, fn(state, x, y) { #(state, plot(x, y)) }).1
}
pub opaque type Pixel {
Pixel(data: BitArray)
}
pub fn rgb(red red: Int, green green: Int, blue blue: Int) -> Pixel {
Pixel(<<blue, green, red>>)
}
fn file_header(width: Int, height: Int) -> #(BitArray, Int) {
let padding = { 4 - { width * 3 } % 4 } % 4
let row_size = width * 3 + padding
let file_size = 54 + row_size * height
let pixel_offset = 54
let reserved = 0
let bmp = <<
"BM",
file_size:little-size(32),
reserved:little-size(16),
reserved:little-size(16),
pixel_offset:little-size(32),
>>
#(bmp, padding)
}
fn dib_header(bmp: BitArray, width: Int, height: Int) -> BitArray {
let dib_header_size = 40
let planes = 1
// RGB: 8 * 3
let bits_per_pixel = 24
let compression = 0
let image_size = 0
let x_pixels_per_meter = 0
let y_pixels_per_meter = 0
let colours_in_palette = 0
let important_colours = 0
// Negative height is used in order to render from top to bottom
let height = -height
<<
bmp:bits,
dib_header_size:little-32,
width:little-32,
height:little-32,
planes:little-16,
bits_per_pixel:little-16,
compression:little-32,
image_size:little-32,
x_pixels_per_meter:little-32,
y_pixels_per_meter:little-32,
colours_in_palette:little-32,
important_colours:little-32,
>>
}
fn render_loop(
bmp: BitArray,
width: Int,
x: Int,
y: Int,
padding: BitArray,
state: state,
render: fn(state, Int, Int) -> #(state, Pixel),
) -> #(state, BitArray) {
let #(state, Pixel(pixel)) = render(state, x, y)
let bmp = <<bmp:bits, pixel:bits>>
let x2 = { x + 1 } % width
case x2, y {
0, 0 -> #(state, <<bmp:bits, padding:bits>>)
0, _ -> {
let bmp = <<bmp:bits, padding:bits>>
render_loop(bmp, width, 0, y - 1, padding, state, render)
}
_, _ -> render_loop(bmp, width, x2, y, padding, state, render)
}
}