blob: 589679e59d5ba13b1d45283de077f4abf792b771 [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 "#unsupported BMP file"
pri status "@internal note: short read"
pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0
pub struct decoder? implements base.image_decoder(
width : base.u32[..= 0x7FFF_FFFF],
height : base.u32[..= 0x7FFF_FFFF],
call_sequence : base.u8,
top_down : base.bool,
pad_per_row : base.u32[..= 3],
bytes_per_row : base.u64[..= 0x0000_0001_FFFF_FFFC], // 4 * 0x7FFF_FFFF
pixfmt : base.pixel_format,
io_redirect_fourcc : base.u32,
io_redirect_pos : base.u64,
frame_config_io_position : base.u64,
padding : base.u32,
mask_r : base.u32,
mask_g : base.u32,
mask_b : base.u32,
mask_a : base.u32,
dst_x : base.u32,
dst_y : base.u32,
dst_y_end : base.u32,
dst_y_inc : base.u32,
pending_pad : base.u32[..= 3],
swizzler : base.pixel_swizzler,
util : base.utility,
)
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 bitmap_info_len : base.u32
var width : base.u32
var height : base.u32
var planes : base.u32
var bits_per_pixel : base.u32
var compression : 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 (version 3 / 4 / 5 is 40 / 108 / 124 bytes).
bitmap_info_len = args.src.read_u32le?()
if (bitmap_info_len <> 40) and (bitmap_info_len <> 108) and (bitmap_info_len <> 124) {
return "#unsupported BMP file"
}
if this.padding < bitmap_info_len {
return "#bad header"
}
this.padding -= bitmap_info_len
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"
}
bits_per_pixel = args.src.read_u16le_as_u32?()
compression = args.src.read_u32le?()
if bits_per_pixel == 0 {
if compression == 4 {
this.io_redirect_fourcc = 'JPEG'be
return base."@I/O redirect"
} else if compression == 5 {
this.io_redirect_fourcc = 'PNG 'be
return base."@I/O redirect"
}
return "#unsupported BMP file"
} else if bits_per_pixel == 24 {
// 3 bytes per pixel, but row lengths are rounded up to multiples of 4.
// The "((x + 3) >> 2) << 2" dance rounds x up.
this.bytes_per_row = ((((this.width as base.u64) * 3) + 3) >> 2) << 2
this.pad_per_row = this.width & 3
this.pixfmt = this.util.make_pixel_format(repr: base.PIXEL_FORMAT__BGR)
} else if bits_per_pixel == 32 {
this.bytes_per_row = (this.width as base.u64) * 4
this.pad_per_row = 0
this.pixfmt = this.util.make_pixel_format(repr: base.PIXEL_FORMAT__BGRA_NONPREMUL)
} else {
// TODO: support other bits_per_pixel's.
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 bitmap_info_len >= 108 {
this.mask_r = args.src.read_u32le?()
this.mask_g = args.src.read_u32le?()
this.mask_b = args.src.read_u32le?()
this.mask_a = args.src.read_u32le?()
// If compression is 3 (BITFIELDS) but the explicit masks are what the
// implicit masks are for no compression, treat it as no compression.
if (compression == 3) and
(this.mask_r == 0x00FF_0000) and
(this.mask_g == 0x0000_FF00) and
(this.mask_b == 0x0000_00FF) and
(this.mask_a == 0xFF00_0000) {
compression = 0
}
// Skip the rest of the BITMAPINFOHEADER. We've already read (40 + (4 *
// 4)) bytes.
args.src.skip_u32?(n: bitmap_info_len - 56)
}
if compression <> 0 {
// TODO: support compression.
return "#unsupported BMP file"
}
this.frame_config_io_position = args.src.position()
if args.dst <> nullptr {
args.dst.set!(
pixfmt: base.PIXEL_FORMAT__BGRA_NONPREMUL, // TODO: this.pixfmt instead?
pixsub: 0,
width: this.width,
height: this.height,
first_frame_io_position: this.frame_config_io_position,
first_frame_is_opaque: true)
}
this.call_sequence = 1
}
pub func decoder.decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) {
if this.call_sequence < 1 {
this.decode_image_config?(dst: nullptr, src: args.src)
} else if this.call_sequence == 1 {
if this.frame_config_io_position <> args.src.position() {
return base."#bad restart"
}
} else if this.call_sequence == 2 {
this.skip_frame?(src: args.src)
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 = 2
}
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 < 2 {
this.decode_frame_config?(dst: nullptr, src: args.src)
} else if this.call_sequence == 2 {
// 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_end = this.height
this.dst_y_inc = 1
} else {
this.dst_y = this.height ~mod- 1
this.dst_y_end = 0xFFFF_FFFF // -1 as a base.u32.
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(),
src_pixfmt: this.pixfmt,
src_palette: this.util.empty_slice_u8(),
blend: args.blend)
if not status.is_ok() {
return status
}
while true {
status = this.swizzle!(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
}
this.call_sequence = 3
}
pri func decoder.swizzle!(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 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
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.dst_y_end {
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: this.util.empty_slice_u8(),
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 func decoder.skip_frame?(src: base.io_reader) {
args.src.skip_u32?(n: this.padding)
args.src.skip?(n: this.bytes_per_row * (this.height as base.u64))
this.call_sequence = 3
}
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 > 1 {
return 1
}
return 0
}
pub func decoder.num_decoded_frames() base.u64 {
if this.call_sequence > 2 {
return 1
}
return 0
}
pub func decoder.restart_frame!(index: base.u64, io_position: base.u64) base.status {
if this.call_sequence == 0 {
return base."#bad call sequence"
}
if args.index <> 0 {
return base."#bad argument"
}
this.call_sequence = 1
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 dummy 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)
}