blob: e23014c99c31ea814beab761e9cdcf736d0e9613 [file] [log] [blame]
// Copyright 2020 The Wuffs Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub status "#bad header"
pub status "#bad RLE compression"
pub status "#unsupported BMP file"
pri status "@internal note: short read"
pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0
pri const COMPRESSION_NONE : base.u32 = 0
pri const COMPRESSION_RLE8 : base.u32 = 1
pri const COMPRESSION_RLE4 : base.u32 = 2
pri const COMPRESSION_BITFIELDS : base.u32 = 3
pri const COMPRESSION_JPEG : base.u32 = 4
pri const COMPRESSION_PNG : base.u32 = 5
pri const COMPRESSION_ALPHABITFIELDS : base.u32 = 6
pri const COMPRESSION_LOW_BIT_DEPTH : base.u32 = 0x100
pub struct decoder? implements base.image_decoder(
width : base.u32[..= 0x7FFF_FFFF],
height : base.u32[..= 0x7FFF_FFFF],
// Call sequence states:
// - 0x00: initial state.
// - 0x03: image config decoded.
// - 0x04: frame config decoded.
// - 0xFF: end-of-data, usually after (the non-animated) frame decoded.
//
// State transitions:
//
// - 0x00 -> 0x03: via DIC
// - 0x00 -> 0x04: via DFC with implicit DIC
// - 0x00 -> 0xFF: via DF with implicit DIC and DFC
//
// - 0x03 -> 0x04: via DFC
// - 0x03 -> 0xFF: via DF with implicit DFC
//
// - 0x04 -> 0xFF: via DFC
// - 0x04 -> 0xFF: via DF
//
// - ???? -> 0x03: via RF for ???? > 0x00
//
// Where:
// - DF is decode_frame
// - DFC is decode_frame_config, implicit means nullptr args.dst
// - DIC is decode_image_config, implicit means nullptr args.dst
// - RF is restart_frame
call_sequence : base.u8,
top_down : base.bool,
pad_per_row : base.u32[..= 3],
src_pixfmt : base.u32,
io_redirect_fourcc : base.u32,
io_redirect_pos : base.u64,
frame_config_io_position : base.u64,
bitmap_info_len : base.u32,
padding : base.u32,
bits_per_pixel : base.u32,
compression : base.u32,
// channel_etc's indexes are: B, G, R, A. This is in Wuffs' default order,
// which isn't the RGBA order they're listed in the wire format.
channel_masks : array[4] base.u32,
channel_shifts : array[4] base.u8[..= 31],
channel_num_bits : array[4] base.u8[..= 32],
dst_x : base.u32,
dst_y : base.u32,
dst_y_inc : base.u32,
pending_pad : base.u32[..= 3],
rle_state : base.u32,
rle_length : base.u32[..= 0xFF],
rle_delta_x : base.u8,
rle_padded : base.bool,
swizzler : base.pixel_swizzler,
util : base.utility,
)(
// scratch is one of:
// - 2048 bytes = 256 * (8 bytes per BGRA_NONPREMUL_4X16LE).
// - 1024 bytes = 1024 * (1 byte per INDEXED__BGRA_BINARY), plus
// 1024 bytes = 256 * (4 bytes per dst_palette entry).
scratch : array[2048] base.u8,
src_palette : array[4 * 256] base.u8,
)
pub func decoder.set_quirk_enabled!(quirk: base.u32, enabled: base.bool) {
}
pub func decoder.decode_image_config?(dst: nptr base.image_config, src: base.io_reader) {
var magic : base.u32
var width : base.u32
var height : base.u32
var planes : base.u32
var dst_pixfmt : base.u32
var byte_width : base.u32
if (this.call_sequence <> 0) or (this.io_redirect_fourcc == 1) {
return base."#bad call sequence"
} else if this.io_redirect_fourcc <> 0 {
return base."@I/O redirect"
}
// Read the BITMAPFILEHEADER (14 bytes).
magic = args.src.read_u16le_as_u32?()
if magic <> 'BM'le {
return "#bad header"
}
args.src.skip_u32?(n: 8)
this.padding = args.src.read_u32le?()
if this.padding < 14 {
return "#bad header"
}
this.padding -= 14
this.io_redirect_pos = (this.padding as base.u64) ~sat+ args.src.position()
// Read the BITMAPINFOHEADER:
// - OS/2 is 12, 16 or 64 bytes.
// - Windows is 40, 108 or 124 bytes.
this.bitmap_info_len = args.src.read_u32le?()
if this.padding < this.bitmap_info_len {
return "#bad header"
}
this.padding -= this.bitmap_info_len
if this.bitmap_info_len == 12 {
this.width = args.src.read_u16le_as_u32?()
this.height = args.src.read_u16le_as_u32?()
planes = args.src.read_u16le_as_u32?()
if planes <> 1 {
return "#unsupported BMP file"
}
this.bits_per_pixel = args.src.read_u16le_as_u32?()
} else if this.bitmap_info_len == 16 {
width = args.src.read_u32le?()
if width >= 0x8000_0000 {
return "#bad header"
}
this.width = width
height = args.src.read_u32le?()
if height >= 0x8000_0000 {
return "#bad header"
}
this.height = height
planes = args.src.read_u16le_as_u32?()
if planes <> 1 {
return "#unsupported BMP file"
}
this.bits_per_pixel = args.src.read_u16le_as_u32?()
} else {
width = args.src.read_u32le?()
if width >= 0x8000_0000 {
return "#bad header"
}
this.width = width
height = args.src.read_u32le?()
if height == 0x8000_0000 {
return "#bad header"
} else if height >= 0x8000_0000 {
// The &0x7FFF_FFFF is redundant, but proves to the compiler that
// the result is within this.height's refined bounds.
this.height = (0 ~mod- height) & 0x7FFF_FFFF
this.top_down = true
} else {
this.height = height
}
planes = args.src.read_u16le_as_u32?()
if planes <> 1 {
return "#unsupported BMP file"
}
this.bits_per_pixel = args.src.read_u16le_as_u32?()
this.compression = args.src.read_u32le?()
if this.bits_per_pixel == 0 {
if this.compression == COMPRESSION_JPEG {
this.io_redirect_fourcc = 'JPEG'be
return base."@I/O redirect"
} else if this.compression == COMPRESSION_PNG {
this.io_redirect_fourcc = 'PNG 'be
return base."@I/O redirect"
}
return "#unsupported BMP file"
}
// We've already read 20 bytes from the BITMAPINFOHEADER: size (4),
// width (4), height (4), planes (2), bpp (2), compression (4). Skip
// the rest of the version 3 BITMAPINFOHEADER (whose total size is 40).
args.src.skip_u32?(n: 40 - 20)
if this.bitmap_info_len == 40 {
if this.bits_per_pixel >= 16 {
// It's poorly documented, but "length 40" data can be silently
// augmented with RGB or RGBA channel_masks (12 or 16 bytes).
if this.padding >= 16 {
this.bitmap_info_len = 56
this.padding -= 16
} else if this.padding >= 12 {
this.bitmap_info_len = 52
this.padding -= 12
}
}
} else if (this.bitmap_info_len <> 52) and
(this.bitmap_info_len <> 56) and
(this.bitmap_info_len <> 64) and
(this.bitmap_info_len <> 108) and
(this.bitmap_info_len <> 124) {
return "#unsupported BMP file"
}
// Treat COMPRESSION_ALPHABITFIELDS the same as COMPRESSION_BITFIELDS.
if this.compression == COMPRESSION_ALPHABITFIELDS {
this.compression = COMPRESSION_BITFIELDS
}
// Read the channel_masks when this.compression is
// COMPRESSION_BITFIELDS.
if this.compression == COMPRESSION_BITFIELDS {
if this.bitmap_info_len >= 52 {
this.channel_masks[2] = args.src.read_u32le?()
this.channel_masks[1] = args.src.read_u32le?()
this.channel_masks[0] = args.src.read_u32le?()
if this.bitmap_info_len >= 56 {
this.channel_masks[3] = args.src.read_u32le?()
// Skip the rest of the BITMAPINFOHEADER.
args.src.skip_u32?(n: this.bitmap_info_len ~mod- 56)
}
// If the explicit channel_masks are what the implicit ones
// would be for no compression, treat it as no compression.
if (this.channel_masks[0] == 0x0000_00FF) and
(this.channel_masks[1] == 0x0000_FF00) and
(this.channel_masks[2] == 0x00FF_0000) {
if this.bits_per_pixel == 24 {
this.compression = COMPRESSION_NONE
} else if this.bits_per_pixel == 32 {
if (this.channel_masks[3] == 0) or
(this.channel_masks[3] == 0xFF00_0000) {
this.compression = COMPRESSION_NONE
}
}
}
this.process_masks?()
}
} else if this.bitmap_info_len >= 40 {
// Skip the rest of the BITMAPINFOHEADER.
args.src.skip_u32?(n: this.bitmap_info_len - 40)
} else {
return "#unsupported BMP file"
}
}
if this.compression <> COMPRESSION_BITFIELDS {
// The palette follows the BITMAPINFOHEADER.
if this.bits_per_pixel < 16 {
this.read_palette?(src: args.src)
}
}
if this.compression == COMPRESSION_NONE {
if (this.bits_per_pixel == 1) or
(this.bits_per_pixel == 2) or
(this.bits_per_pixel == 4) {
this.src_pixfmt = base.PIXEL_FORMAT__INDEXED__BGRA_BINARY
this.compression = COMPRESSION_LOW_BIT_DEPTH
} else if this.bits_per_pixel == 8 {
this.src_pixfmt = base.PIXEL_FORMAT__INDEXED__BGRA_BINARY
} else if this.bits_per_pixel == 16 {
// BMP's 16-bit default is BGRX_5551.
this.compression = COMPRESSION_BITFIELDS
this.channel_masks[0] = 0x001F
this.channel_masks[1] = 0x03E0
this.channel_masks[2] = 0x7C00
this.channel_masks[3] = 0x0000
this.process_masks?()
this.src_pixfmt = base.PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE
} else if this.bits_per_pixel == 24 {
this.src_pixfmt = base.PIXEL_FORMAT__BGR
} else if this.bits_per_pixel == 32 {
if this.channel_masks[3] == 0 {
this.src_pixfmt = base.PIXEL_FORMAT__BGRX
} else {
this.src_pixfmt = base.PIXEL_FORMAT__BGRA_NONPREMUL
}
} else {
return "#unsupported BMP file"
}
} else if this.compression == COMPRESSION_RLE8 {
if this.bits_per_pixel == 8 {
this.src_pixfmt = base.PIXEL_FORMAT__INDEXED__BGRA_BINARY
} else {
return "#unsupported BMP file"
}
} else if this.compression == COMPRESSION_RLE4 {
if this.bits_per_pixel == 4 {
this.src_pixfmt = base.PIXEL_FORMAT__INDEXED__BGRA_BINARY
} else {
return "#unsupported BMP file"
}
} else if this.compression == COMPRESSION_BITFIELDS {
if (this.bits_per_pixel == 16) or (this.bits_per_pixel == 32) {
this.src_pixfmt = base.PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE
} else {
return "#unsupported BMP file"
}
} else {
return "#unsupported BMP file"
}
// OS/2 BMP files have fewer valid bits_per_pixel values.
if ((this.bitmap_info_len < 40) or (this.bitmap_info_len == 64)) and
(this.bits_per_pixel <> 1) and
(this.bits_per_pixel <> 4) and
(this.bits_per_pixel <> 8) and
(this.bits_per_pixel <> 24) {
return "#bad header"
}
// The "((x + 3) >> 2) << 2" dance rounds x up to a multiple of 4.
if this.bits_per_pixel == 1 {
// byte_width is this.width divided by 8, rounding up.
byte_width = (this.width >> 3) + (((this.width & 7) + 7) >> 3)
this.pad_per_row = (4 - (byte_width & 3)) & 3
} else if this.bits_per_pixel == 2 {
// byte_width is this.width divided by 4, rounding up.
byte_width = (this.width >> 2) + (((this.width & 3) + 3) >> 2)
this.pad_per_row = (4 - (byte_width & 3)) & 3
} else if this.bits_per_pixel == 4 {
// byte_width is this.width divided by 2, rounding up.
byte_width = (this.width >> 1) + (this.width & 1)
this.pad_per_row = (4 - (byte_width & 3)) & 3
} else if this.bits_per_pixel == 8 {
this.pad_per_row = (4 - (this.width & 3)) & 3
} else if this.bits_per_pixel == 16 {
this.pad_per_row = (this.width & 1) * 2
} else if this.bits_per_pixel == 24 {
this.pad_per_row = this.width & 3
} else if this.bits_per_pixel == 32 {
this.pad_per_row = 0
}
this.frame_config_io_position = args.src.position()
if args.dst <> nullptr {
dst_pixfmt = base.PIXEL_FORMAT__BGRA_NONPREMUL
if (this.channel_num_bits[0] > 8) or
(this.channel_num_bits[1] > 8) or
(this.channel_num_bits[2] > 8) or
(this.channel_num_bits[3] > 8) {
dst_pixfmt = base.PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE
}
args.dst.set!(
pixfmt: dst_pixfmt,
pixsub: 0,
width: this.width,
height: this.height,
first_frame_io_position: this.frame_config_io_position,
first_frame_is_opaque: this.channel_masks[3] == 0)
}
this.call_sequence = 3
}
pub func decoder.decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) {
if this.call_sequence < 3 {
this.decode_image_config?(dst: nullptr, src: args.src)
} else if this.call_sequence == 3 {
if this.frame_config_io_position <> args.src.position() {
return base."#bad restart"
}
} else if this.call_sequence == 4 {
this.call_sequence = 0xFF
return base."@end of data"
} else {
return base."@end of data"
}
if args.dst <> nullptr {
args.dst.set!(bounds: this.util.make_rect_ie_u32(
min_incl_x: 0,
min_incl_y: 0,
max_excl_x: this.width,
max_excl_y: this.height),
duration: 0,
index: 0,
io_position: this.frame_config_io_position,
disposal: 0,
opaque_within_bounds: true,
overwrite_instead_of_blend: false,
background_color: 0xFF00_0000)
}
this.call_sequence = 4
}
pub func decoder.decode_frame?(dst: ptr base.pixel_buffer, src: base.io_reader, blend: base.pixel_blend, workbuf: slice base.u8, opts: nptr base.decode_frame_options) {
var status : base.status
if this.call_sequence < 4 {
this.decode_frame_config?(dst: nullptr, src: args.src)
} else if this.call_sequence == 4 {
// No-op.
} else {
return base."@end of data"
}
args.src.skip_u32?(n: this.padding)
if (this.width > 0) and (this.height > 0) {
this.dst_x = 0
if this.top_down {
this.dst_y = 0
this.dst_y_inc = 1
} else {
this.dst_y = this.height ~mod- 1
this.dst_y_inc = 0xFFFF_FFFF // -1 as a base.u32.
}
status = this.swizzler.prepare!(
dst_pixfmt: args.dst.pixel_format(),
dst_palette: args.dst.palette_or_else(fallback: this.scratch[1024 ..]),
src_pixfmt: this.util.make_pixel_format(repr: this.src_pixfmt),
src_palette: this.src_palette[..],
blend: args.blend)
if not status.is_ok() {
return status
}
while true {
if this.compression == COMPRESSION_NONE {
status = this.swizzle_none!(dst: args.dst, src: args.src)
} else if this.compression < COMPRESSION_BITFIELDS {
status = this.swizzle_rle!(dst: args.dst, src: args.src)
} else if this.compression == COMPRESSION_BITFIELDS {
status = this.swizzle_bitfields!(dst: args.dst, src: args.src)
} else {
status = this.swizzle_low_bit_depth!(dst: args.dst, src: args.src)
}
if status.is_ok() {
break
} else if status <> "@internal note: short read" {
return status
}
yield? base."$short read"
} endwhile
args.src.skip_u32?(n: this.pending_pad)
this.pending_pad = 0
}
this.call_sequence = 0xFF
}
pri func decoder.swizzle_none!(dst: ptr base.pixel_buffer, src: base.io_reader) base.status {
var dst_pixfmt : base.pixel_format
var dst_bits_per_pixel : base.u32[..= 256]
var dst_bytes_per_pixel : base.u64[..= 32]
var dst_bytes_per_row : base.u64
var dst_palette : slice base.u8
var tab : table base.u8
var dst : slice base.u8
var i : base.u64
var n : base.u64
// TODO: the dst_pixfmt variable shouldn't be necessary. We should be able
// to chain the two calls: "args.dst.pixel_format().bits_per_pixel()".
dst_pixfmt = args.dst.pixel_format()
dst_bits_per_pixel = dst_pixfmt.bits_per_pixel()
if (dst_bits_per_pixel & 7) <> 0 {
return base."#unsupported option"
}
dst_bytes_per_pixel = (dst_bits_per_pixel / 8) as base.u64
dst_bytes_per_row = (this.width as base.u64) * dst_bytes_per_pixel
dst_palette = args.dst.palette_or_else(fallback: this.scratch[1024 ..])
tab = args.dst.plane(p: 0)
while.outer true {
while this.pending_pad > 0 {
if args.src.length() <= 0 {
return "@internal note: short read"
}
this.pending_pad -= 1
args.src.skip_u32_fast!(actual: 1, worst_case: 1)
} endwhile
while.inner true {
if this.dst_x == this.width {
this.dst_x = 0
this.dst_y ~mod+= this.dst_y_inc
if this.dst_y >= this.height {
if this.height > 0 {
this.pending_pad = this.pad_per_row
}
break.outer
} else if this.pad_per_row <> 0 {
this.pending_pad = this.pad_per_row
continue.outer
}
}
dst = tab.row(y: this.dst_y)
if dst_bytes_per_row < dst.length() {
dst = dst[.. dst_bytes_per_row]
}
i = (this.dst_x as base.u64) * dst_bytes_per_pixel
if i >= dst.length() {
// TODO: advance args.src if the dst pixel_buffer bounds is
// smaller than this BMP's image bounds?
continue.inner
}
n = this.swizzler.swizzle_interleaved_from_reader!(
dst: dst[i ..],
dst_palette: dst_palette,
src: args.src)
if n == 0 {
return "@internal note: short read"
}
this.dst_x ~sat+= (n & 0xFFFF_FFFF) as base.u32
} endwhile.inner
} endwhile.outer
return ok
}
pri const RLE_STATE_NEUTRAL : base.u32 = 0
pri const RLE_STATE_RUN : base.u32 = 1
pri const RLE_STATE_ESCAPE : base.u32 = 2
pri const RLE_STATE_LITERAL : base.u32 = 3
pri const RLE_STATE_DELTA_X : base.u32 = 4
pri const RLE_STATE_DELTA_Y : base.u32 = 5
pri func decoder.swizzle_rle!(dst: ptr base.pixel_buffer, src: base.io_reader) base.status {
var dst_pixfmt : base.pixel_format
var dst_bits_per_pixel : base.u32[..= 256]
var dst_bytes_per_pixel : base.u64[..= 32]
var dst_bytes_per_row : base.u64
var dst_palette : slice base.u8
var tab : table base.u8
var row : slice base.u8
var dst : slice base.u8
var i : base.u64
var n : base.u64
var p0 : base.u32[..= 259]
var code : base.u8
var indexes : array[2] base.u8
var rle_state : base.u32
var chunk_bits : base.u32
var chunk_count : base.u32[..= 64]
// TODO: the dst_pixfmt variable shouldn't be necessary. We should be able
// to chain the two calls: "args.dst.pixel_format().bits_per_pixel()".
dst_pixfmt = args.dst.pixel_format()
dst_bits_per_pixel = dst_pixfmt.bits_per_pixel()
if (dst_bits_per_pixel & 7) <> 0 {
return base."#unsupported option"
}
dst_bytes_per_pixel = (dst_bits_per_pixel / 8) as base.u64
dst_bytes_per_row = (this.width as base.u64) * dst_bytes_per_pixel
dst_palette = args.dst.palette_or_else(fallback: this.scratch[1024 ..])
tab = args.dst.plane(p: 0)
rle_state = this.rle_state
while.outer true {
row = tab.row(y: this.dst_y)
if dst_bytes_per_row < row.length() {
row = row[.. dst_bytes_per_row]
}
while.middle true {
i = (this.dst_x as base.u64) * dst_bytes_per_pixel
if i <= row.length() {
dst = row[i ..]
} else {
dst = this.util.empty_slice_u8()
}
while.goto_suspend true {{
while.inner true {
if rle_state == RLE_STATE_NEUTRAL {
if args.src.length() < 1 {
break.goto_suspend
}
code = args.src.peek_u8()
args.src.skip_u32_fast!(actual: 1, worst_case: 1)
if code == 0 {
rle_state = RLE_STATE_ESCAPE
continue.inner
}
this.rle_length = code as base.u32
rle_state = RLE_STATE_RUN
continue.inner
} else if rle_state == RLE_STATE_RUN {
if args.src.length() < 1 {
break.goto_suspend
}
code = args.src.peek_u8()
args.src.skip_u32_fast!(actual: 1, worst_case: 1)
if this.bits_per_pixel == 8 {
p0 = 0
while p0 < this.rle_length {
assert p0 < 255 via "a < b: a < c; c <= b"(c: this.rle_length)
this.scratch[p0] = code
p0 += 1
} endwhile
} else {
indexes[0] = (code >> 4) as base.u8
indexes[1] = code & 0x0F
p0 = 0
while p0 < this.rle_length {
assert p0 < 255 via "a < b: a < c; c <= b"(c: this.rle_length)
this.scratch[p0 + 0] = indexes[0]
this.scratch[p0 + 1] = indexes[1]
p0 += 2
} endwhile
}
this.swizzler.swizzle_interleaved_from_slice!(
dst: dst,
dst_palette: dst_palette,
src: this.scratch[.. this.rle_length])
this.dst_x ~sat+= this.rle_length
rle_state = RLE_STATE_NEUTRAL
continue.middle
} else if rle_state == RLE_STATE_ESCAPE {
if args.src.length() < 1 {
break.goto_suspend
}
code = args.src.peek_u8()
args.src.skip_u32_fast!(actual: 1, worst_case: 1)
if code < 2 { // 0=EOL, 1=EOF.
if (this.dst_y >= this.height) and (code == 0) {
return "#bad RLE compression"
}
this.swizzler.swizzle_interleaved_transparent_black!(
dst: dst,
dst_palette: dst_palette,
num_pixels: 0xFFFF_FFFF_FFFF_FFFF)
this.dst_x = 0
this.dst_y ~mod+= this.dst_y_inc
if code > 0 {
break.outer
}
rle_state = RLE_STATE_NEUTRAL
continue.outer
} else if code == 2 { // 2=DELTA.
rle_state = RLE_STATE_DELTA_X
continue.inner
}
this.rle_length = code as base.u32
this.rle_padded = (this.bits_per_pixel == 8) and ((code & 1) <> 0)
rle_state = RLE_STATE_LITERAL
continue.inner
} else if rle_state == RLE_STATE_LITERAL {
if this.bits_per_pixel == 8 {
n = this.swizzler.limited_swizzle_u32_interleaved_from_reader!(
up_to_num_pixels: this.rle_length,
dst: dst,
dst_palette: dst_palette,
src: args.src)
this.dst_x ~sat+= (n & 0xFFFF_FFFF) as base.u32
this.rle_length ~sat-= (n & 0xFFFF_FFFF) as base.u32
} else {
// Calculate the remaining number of 16-bit chunks. At
// 4 bits per pixel there are 4 pixels per chunk.
// Division rounds up.
chunk_count = (this.rle_length + 3) / 4
p0 = 0
while (chunk_count > 0) and (args.src.length() >= 2) {
chunk_bits = args.src.peek_u16be_as_u32()
args.src.skip_u32_fast!(actual: 2, worst_case: 2)
this.scratch[p0 + 0x00] = (0x0F & (chunk_bits >> 0x0C)) as base.u8
this.scratch[p0 + 0x01] = (0x0F & (chunk_bits >> 0x08)) as base.u8
this.scratch[p0 + 0x02] = (0x0F & (chunk_bits >> 0x04)) as base.u8
this.scratch[p0 + 0x03] = (0x0F & (chunk_bits >> 0x00)) as base.u8
p0 = (p0 & 255) + 0x04
chunk_count -= 1
} endwhile
p0 = p0.min(a: this.rle_length)
this.swizzler.swizzle_interleaved_from_slice!(
dst: dst,
dst_palette: dst_palette,
src: this.scratch[.. p0])
this.dst_x ~sat+= p0
this.rle_length ~sat-= p0
}
if this.rle_length > 0 {
break.goto_suspend
}
if this.rle_padded {
if args.src.length() < 1 {
break.goto_suspend
}
args.src.skip_u32_fast!(actual: 1, worst_case: 1)
this.rle_padded = false
}
rle_state = RLE_STATE_NEUTRAL
continue.middle
} else if rle_state == RLE_STATE_DELTA_X {
if args.src.length() < 1 {
break.goto_suspend
}
this.rle_delta_x = args.src.peek_u8()
args.src.skip_u32_fast!(actual: 1, worst_case: 1)
rle_state = RLE_STATE_DELTA_Y
continue.inner
} // else (rle_state == RLE_STATE_DELTA_Y).
if args.src.length() < 1 {
break.goto_suspend
}
code = args.src.peek_u8()
args.src.skip_u32_fast!(actual: 1, worst_case: 1)
if this.rle_delta_x > 0 {
this.swizzler.swizzle_interleaved_transparent_black!(
dst: dst,
dst_palette: dst_palette,
num_pixels: this.rle_delta_x as base.u64)
this.dst_x ~sat+= this.rle_delta_x as base.u32
this.rle_delta_x = 0
if this.dst_x > this.width {
return "#bad RLE compression"
}
}
// Paint the skipped rows (and the partial final row) with
// transparent black.
if code > 0 {
code -= 1
while true {
this.dst_y ~mod+= this.dst_y_inc
if this.dst_y >= this.height {
return "#bad RLE compression"
}
row = tab.row(y: this.dst_y)
if dst_bytes_per_row < row.length() {
row = row[.. dst_bytes_per_row]
}
if code <= 0 {
this.swizzler.swizzle_interleaved_transparent_black!(
dst: row,
dst_palette: dst_palette,
num_pixels: this.dst_x as base.u64)
break
}
this.swizzler.swizzle_interleaved_transparent_black!(
dst: row,
dst_palette: dst_palette,
num_pixels: 0xFFFF_FFFF_FFFF_FFFF)
code -= 1
} endwhile
}
rle_state = RLE_STATE_NEUTRAL
continue.middle
} endwhile.inner
}} endwhile.goto_suspend
this.rle_state = rle_state
return "@internal note: short read"
} endwhile.middle
} endwhile.outer
while this.dst_y < this.height {
row = tab.row(y: this.dst_y)
if dst_bytes_per_row < row.length() {
row = row[.. dst_bytes_per_row]
}
this.swizzler.swizzle_interleaved_transparent_black!(
dst: row,
dst_palette: dst_palette,
num_pixels: 0xFFFF_FFFF_FFFF_FFFF)
this.dst_y ~mod+= this.dst_y_inc
} endwhile
return ok
}
pri func decoder.swizzle_bitfields!(dst: ptr base.pixel_buffer, src: base.io_reader) base.status {
var dst_pixfmt : base.pixel_format
var dst_bits_per_pixel : base.u32[..= 256]
var dst_bytes_per_pixel : base.u64[..= 32]
var dst_bytes_per_row : base.u64
var dst_palette : slice base.u8
var tab : table base.u8
var dst : slice base.u8
var i : base.u64
var n : base.u64
var p0 : base.u32[..= 256]
var p1 : base.u32[..= 256]
var p1_temp : base.u32
var num_bits : base.u32[..= 32]
var c : base.u32
var c32 : base.u32
var channel : base.u32[..= 4]
// TODO: the dst_pixfmt variable shouldn't be necessary. We should be able
// to chain the two calls: "args.dst.pixel_format().bits_per_pixel()".
dst_pixfmt = args.dst.pixel_format()
dst_bits_per_pixel = dst_pixfmt.bits_per_pixel()
if (dst_bits_per_pixel & 7) <> 0 {
return base."#unsupported option"
}
dst_bytes_per_pixel = (dst_bits_per_pixel / 8) as base.u64
dst_bytes_per_row = (this.width as base.u64) * dst_bytes_per_pixel
dst_palette = args.dst.palette_or_else(fallback: this.scratch[1024 ..])
tab = args.dst.plane(p: 0)
while.outer true {
while this.pending_pad > 0 {
if args.src.length() <= 0 {
return "@internal note: short read"
}
this.pending_pad -= 1
args.src.skip_u32_fast!(actual: 1, worst_case: 1)
} endwhile
while.inner true {
if this.dst_x == this.width {
this.dst_x = 0
this.dst_y ~mod+= this.dst_y_inc
if this.dst_y >= this.height {
if this.height > 0 {
this.pending_pad = this.pad_per_row
}
break.outer
} else if this.pad_per_row <> 0 {
this.pending_pad = this.pad_per_row
continue.outer
}
}
// -------- BEGIN convert to PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE.
p1_temp = this.width ~mod- this.dst_x
p1 = p1_temp.min(a: 256)
p0 = 0
while p0 < p1 {
assert p0 < 256 via "a < b: a < c; c <= b"(c: p1)
if this.bits_per_pixel == 16 {
if args.src.length() < 2 {
break
}
c32 = args.src.peek_u16le_as_u32()
args.src.skip_u32_fast!(actual: 2, worst_case: 2)
} else {
if args.src.length() < 4 {
break
}
c32 = args.src.peek_u32le()
args.src.skip_u32_fast!(actual: 4, worst_case: 4)
}
channel = 0
while channel < 4,
inv p0 < 256,
{
if this.channel_num_bits[channel] == 0 {
this.scratch[(8 * p0) + (2 * channel) + 0] = 0xFF
this.scratch[(8 * p0) + (2 * channel) + 1] = 0xFF
} else {
c = (c32 & this.channel_masks[channel]) >> this.channel_shifts[channel]
num_bits = this.channel_num_bits[channel] as base.u32
while num_bits < 16,
inv p0 < 256,
inv channel < 4,
post num_bits >= 16,
{
c |= c ~mod<< num_bits
num_bits *= 2
} endwhile
c >>= num_bits - 16
this.scratch[(8 * p0) + (2 * channel) + 0] = (0xFF & (c >> 0)) as base.u8
this.scratch[(8 * p0) + (2 * channel) + 1] = (0xFF & (c >> 8)) as base.u8
}
channel += 1
} endwhile
p0 += 1
} endwhile
// -------- END convert to PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE.
dst = tab.row(y: this.dst_y)
if dst_bytes_per_row < dst.length() {
dst = dst[.. dst_bytes_per_row]
}
i = (this.dst_x as base.u64) * dst_bytes_per_pixel
if i >= dst.length() {
// TODO: advance args.src if the dst pixel_buffer bounds is
// smaller than this BMP's image bounds?
continue.inner
}
n = this.swizzler.swizzle_interleaved_from_slice!(
dst: dst[i ..],
dst_palette: dst_palette,
src: this.scratch[.. 8 * p0])
if n == 0 {
return "@internal note: short read"
}
this.dst_x ~sat+= (n & 0xFFFF_FFFF) as base.u32
} endwhile.inner
} endwhile.outer
return ok
}
pri func decoder.swizzle_low_bit_depth!(dst: ptr base.pixel_buffer, src: base.io_reader) base.status {
var dst_pixfmt : base.pixel_format
var dst_bits_per_pixel : base.u32[..= 256]
var dst_bytes_per_pixel : base.u64[..= 32]
var dst_bytes_per_row : base.u64
var dst_palette : slice base.u8
var tab : table base.u8
var dst : slice base.u8
var i : base.u64
var n : base.u64
var p0 : base.u32[..= 543]
var chunk_bits : base.u32
var chunk_count : base.u32
// TODO: the dst_pixfmt variable shouldn't be necessary. We should be able
// to chain the two calls: "args.dst.pixel_format().bits_per_pixel()".
dst_pixfmt = args.dst.pixel_format()
dst_bits_per_pixel = dst_pixfmt.bits_per_pixel()
if (dst_bits_per_pixel & 7) <> 0 {
return base."#unsupported option"
}
dst_bytes_per_pixel = (dst_bits_per_pixel / 8) as base.u64
dst_bytes_per_row = (this.width as base.u64) * dst_bytes_per_pixel
dst_palette = args.dst.palette_or_else(fallback: this.scratch[1024 ..])
tab = args.dst.plane(p: 0)
while.loop true {
if this.dst_x == this.width {
this.dst_x = 0
this.dst_y ~mod+= this.dst_y_inc
if this.dst_y >= this.height {
break.loop
}
}
dst = tab.row(y: this.dst_y)
if dst_bytes_per_row < dst.length() {
dst = dst[.. dst_bytes_per_row]
}
i = (this.dst_x as base.u64) * dst_bytes_per_pixel
if i >= dst.length() {
// TODO: advance args.src if the dst pixel_buffer bounds is
// smaller than this BMP's image bounds?
continue.loop
}
dst = dst[i ..]
p0 = 0
if this.bits_per_pixel == 1 {
// Calculate the remaining number of 32-bit chunks. At 1 bit per
// pixel there are 32 pixels per chunk. Division rounds up.
chunk_count = ((this.width ~sat- this.dst_x) + 31) / 32
chunk_count = chunk_count.min(a: 16) // Keep p0 <= 512.
while (chunk_count > 0) and (args.src.length() >= 4) {
chunk_bits = args.src.peek_u32be()
args.src.skip_u32_fast!(actual: 4, worst_case: 4)
this.scratch[p0 + 0x00] = (0x01 & (chunk_bits >> 0x1F)) as base.u8
this.scratch[p0 + 0x01] = (0x01 & (chunk_bits >> 0x1E)) as base.u8
this.scratch[p0 + 0x02] = (0x01 & (chunk_bits >> 0x1D)) as base.u8
this.scratch[p0 + 0x03] = (0x01 & (chunk_bits >> 0x1C)) as base.u8
this.scratch[p0 + 0x04] = (0x01 & (chunk_bits >> 0x1B)) as base.u8
this.scratch[p0 + 0x05] = (0x01 & (chunk_bits >> 0x1A)) as base.u8
this.scratch[p0 + 0x06] = (0x01 & (chunk_bits >> 0x19)) as base.u8
this.scratch[p0 + 0x07] = (0x01 & (chunk_bits >> 0x18)) as base.u8
this.scratch[p0 + 0x08] = (0x01 & (chunk_bits >> 0x17)) as base.u8
this.scratch[p0 + 0x09] = (0x01 & (chunk_bits >> 0x16)) as base.u8
this.scratch[p0 + 0x0A] = (0x01 & (chunk_bits >> 0x15)) as base.u8
this.scratch[p0 + 0x0B] = (0x01 & (chunk_bits >> 0x14)) as base.u8
this.scratch[p0 + 0x0C] = (0x01 & (chunk_bits >> 0x13)) as base.u8
this.scratch[p0 + 0x0D] = (0x01 & (chunk_bits >> 0x12)) as base.u8
this.scratch[p0 + 0x0E] = (0x01 & (chunk_bits >> 0x11)) as base.u8
this.scratch[p0 + 0x0F] = (0x01 & (chunk_bits >> 0x10)) as base.u8
this.scratch[p0 + 0x10] = (0x01 & (chunk_bits >> 0x0F)) as base.u8
this.scratch[p0 + 0x11] = (0x01 & (chunk_bits >> 0x0E)) as base.u8
this.scratch[p0 + 0x12] = (0x01 & (chunk_bits >> 0x0D)) as base.u8
this.scratch[p0 + 0x13] = (0x01 & (chunk_bits >> 0x0C)) as base.u8
this.scratch[p0 + 0x14] = (0x01 & (chunk_bits >> 0x0B)) as base.u8
this.scratch[p0 + 0x15] = (0x01 & (chunk_bits >> 0x0A)) as base.u8
this.scratch[p0 + 0x16] = (0x01 & (chunk_bits >> 0x09)) as base.u8
this.scratch[p0 + 0x17] = (0x01 & (chunk_bits >> 0x08)) as base.u8
this.scratch[p0 + 0x18] = (0x01 & (chunk_bits >> 0x07)) as base.u8
this.scratch[p0 + 0x19] = (0x01 & (chunk_bits >> 0x06)) as base.u8
this.scratch[p0 + 0x1A] = (0x01 & (chunk_bits >> 0x05)) as base.u8
this.scratch[p0 + 0x1B] = (0x01 & (chunk_bits >> 0x04)) as base.u8
this.scratch[p0 + 0x1C] = (0x01 & (chunk_bits >> 0x03)) as base.u8
this.scratch[p0 + 0x1D] = (0x01 & (chunk_bits >> 0x02)) as base.u8
this.scratch[p0 + 0x1E] = (0x01 & (chunk_bits >> 0x01)) as base.u8
this.scratch[p0 + 0x1F] = (0x01 & (chunk_bits >> 0x00)) as base.u8
p0 = (p0 & 511) + 0x20
chunk_count -= 1
} endwhile
} else if this.bits_per_pixel == 2 {
// Calculate the remaining number of 32-bit chunks. At 2 bits per
// pixel there are 16 pixels per chunk. Division rounds up.
chunk_count = ((this.width ~sat- this.dst_x) + 15) / 16
chunk_count = chunk_count.min(a: 32) // Keep p0 <= 512.
while (chunk_count > 0) and (args.src.length() >= 4) {
chunk_bits = args.src.peek_u32be()
args.src.skip_u32_fast!(actual: 4, worst_case: 4)
this.scratch[p0 + 0x00] = (0x03 & (chunk_bits >> 0x1E)) as base.u8
this.scratch[p0 + 0x01] = (0x03 & (chunk_bits >> 0x1C)) as base.u8
this.scratch[p0 + 0x02] = (0x03 & (chunk_bits >> 0x1A)) as base.u8
this.scratch[p0 + 0x03] = (0x03 & (chunk_bits >> 0x18)) as base.u8
this.scratch[p0 + 0x04] = (0x03 & (chunk_bits >> 0x16)) as base.u8
this.scratch[p0 + 0x05] = (0x03 & (chunk_bits >> 0x14)) as base.u8
this.scratch[p0 + 0x06] = (0x03 & (chunk_bits >> 0x12)) as base.u8
this.scratch[p0 + 0x07] = (0x03 & (chunk_bits >> 0x10)) as base.u8
this.scratch[p0 + 0x08] = (0x03 & (chunk_bits >> 0x0E)) as base.u8
this.scratch[p0 + 0x09] = (0x03 & (chunk_bits >> 0x0C)) as base.u8
this.scratch[p0 + 0x0A] = (0x03 & (chunk_bits >> 0x0A)) as base.u8
this.scratch[p0 + 0x0B] = (0x03 & (chunk_bits >> 0x08)) as base.u8
this.scratch[p0 + 0x0C] = (0x03 & (chunk_bits >> 0x06)) as base.u8
this.scratch[p0 + 0x0D] = (0x03 & (chunk_bits >> 0x04)) as base.u8
this.scratch[p0 + 0x0E] = (0x03 & (chunk_bits >> 0x02)) as base.u8
this.scratch[p0 + 0x0F] = (0x03 & (chunk_bits >> 0x00)) as base.u8
p0 = (p0 & 511) + 0x10
chunk_count -= 1
} endwhile
} else if this.bits_per_pixel == 4 {
// Calculate the remaining number of 32-bit chunks. At 4 bits per
// pixel there are 8 pixels per chunk. Division rounds up.
chunk_count = ((this.width ~sat- this.dst_x) + 7) / 8
chunk_count = chunk_count.min(a: 64) // Keep p0 <= 512.
while (chunk_count > 0) and (args.src.length() >= 4) {
chunk_bits = args.src.peek_u32be()
args.src.skip_u32_fast!(actual: 4, worst_case: 4)
this.scratch[p0 + 0x00] = (0x0F & (chunk_bits >> 0x1C)) as base.u8
this.scratch[p0 + 0x01] = (0x0F & (chunk_bits >> 0x18)) as base.u8
this.scratch[p0 + 0x02] = (0x0F & (chunk_bits >> 0x14)) as base.u8
this.scratch[p0 + 0x03] = (0x0F & (chunk_bits >> 0x10)) as base.u8
this.scratch[p0 + 0x04] = (0x0F & (chunk_bits >> 0x0C)) as base.u8
this.scratch[p0 + 0x05] = (0x0F & (chunk_bits >> 0x08)) as base.u8
this.scratch[p0 + 0x06] = (0x0F & (chunk_bits >> 0x04)) as base.u8
this.scratch[p0 + 0x07] = (0x0F & (chunk_bits >> 0x00)) as base.u8
p0 = (p0 & 511) + 0x08
chunk_count -= 1
} endwhile
}
p0 = p0.min(a: this.width ~sat- this.dst_x)
n = this.swizzler.swizzle_interleaved_from_slice!(
dst: dst,
dst_palette: dst_palette,
src: this.scratch[.. p0])
if n == 0 {
return "@internal note: short read"
}
this.dst_x ~sat+= (n & 0xFFFF_FFFF) as base.u32
} endwhile.loop
return ok
}
pub func decoder.frame_dirty_rect() base.rect_ie_u32 {
return this.util.make_rect_ie_u32(
min_incl_x: 0,
min_incl_y: 0,
max_excl_x: this.width,
max_excl_y: this.height)
}
pub func decoder.num_animation_loops() base.u32 {
return 0
}
pub func decoder.num_decoded_frame_configs() base.u64 {
if this.call_sequence > 3 {
return 1
}
return 0
}
pub func decoder.num_decoded_frames() base.u64 {
if this.call_sequence > 4 {
return 1
}
return 0
}
pub func decoder.restart_frame!(index: base.u64, io_position: base.u64) base.status {
if this.call_sequence < 3 {
return base."#bad call sequence"
}
if args.index <> 0 {
return base."#bad argument"
}
this.call_sequence = 3
this.frame_config_io_position = args.io_position
return ok
}
pub func decoder.set_report_metadata!(fourcc: base.u32, report: base.bool) {
// No-op. BMP doesn't support metadata.
}
pub func decoder.tell_me_more?(dst: base.io_writer, minfo: nptr base.more_information, src: base.io_reader) {
if this.io_redirect_fourcc <= 1 {
return base."#no more information"
}
if args.minfo <> nullptr {
args.minfo.set!(
flavor: 1, // WUFFS_BASE__MORE_INFORMATION__FLAVOR__IO_REDIRECT
w: this.io_redirect_fourcc,
x: 0,
y: this.io_redirect_pos,
z: 0xFFFF_FFFF_FFFF_FFFF)
}
// Setting io_redirect_fourcc to a placeholder value of 1 will cause future
// calls to return an error.
this.io_redirect_fourcc = 1
}
pub func decoder.workbuf_len() base.range_ii_u64 {
return this.util.make_range_ii_u64(min_incl: 0, max_incl: 0)
}
pri func decoder.read_palette?(src: base.io_reader) {
var i : base.u32
var argb : base.u32
if this.bitmap_info_len == 12 {
while (i < 256) and (this.padding >= 3) {
this.padding -= 3
argb = args.src.read_u24le_as_u32?()
argb |= 0xFF00_0000
this.src_palette[(4 * i) + 0] = ((argb >> 0) & 0xFF) as base.u8
this.src_palette[(4 * i) + 1] = ((argb >> 8) & 0xFF) as base.u8
this.src_palette[(4 * i) + 2] = ((argb >> 16) & 0xFF) as base.u8
this.src_palette[(4 * i) + 3] = ((argb >> 24) & 0xFF) as base.u8
i += 1
} endwhile
} else {
while (i < 256) and (this.padding >= 4) {
this.padding -= 4
argb = args.src.read_u32le?()
argb |= 0xFF00_0000
this.src_palette[(4 * i) + 0] = ((argb >> 0) & 0xFF) as base.u8
this.src_palette[(4 * i) + 1] = ((argb >> 8) & 0xFF) as base.u8
this.src_palette[(4 * i) + 2] = ((argb >> 16) & 0xFF) as base.u8
this.src_palette[(4 * i) + 3] = ((argb >> 24) & 0xFF) as base.u8
i += 1
} endwhile
}
while i < 256 {
this.src_palette[(4 * i) + 0] = 0x00
this.src_palette[(4 * i) + 1] = 0x00
this.src_palette[(4 * i) + 2] = 0x00
this.src_palette[(4 * i) + 3] = 0xFF
i += 1
} endwhile
}
pri func decoder.process_masks?() {
var i : base.u32
var mask : base.u32
var n : base.u32
while i < 4 {
mask = this.channel_masks[i]
if mask <> 0 {
n = 0
while (mask & 1) == 0,
inv i < 4,
{
n ~mod+= 1
mask >>= 1
} endwhile
this.channel_shifts[i] = (n & 31) as base.u8
n = 0
while (mask & 1) == 1,
inv i < 4,
{
n ~mod+= 1
mask >>= 1
} endwhile
if (mask <> 0) or (n > 32) {
return "#bad header"
}
this.channel_num_bits[i] = n as base.u8
} else if i <> 3 {
return "#bad header"
}
i += 1
} endwhile
}