blob: 05d263bbc4e3705e85a92f3ed23e9d8a419b1d61 [file] [log] [blame]
// Copyright 2024 The Wuffs Authors.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// SPDX-License-Identifier: Apache-2.0 OR MIT
pub status "#bad footer"
pub status "#bad header"
pub status "#truncated input"
pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0
pub struct decoder? implements base.image_decoder(
pixfmt : base.u32,
width : base.u32[..= 0xFF_FFFF],
height : base.u32[..= 0xFF_FFFF],
remaining_pixels_times_4 : base.u64,
// The call sequence state machine is discussed in
// (/doc/std/image-decoders-call-sequence.md).
call_sequence : base.u8,
buffer_index : base.u32[..= 8192],
dst_x : base.u32,
dst_y : base.u32,
swizzler : base.pixel_swizzler,
util : base.utility,
) + (
pixel : array[4] base.u8,
cache : array[256] base.u8,
buffer : array[8192 + 4] base.u8,
)
pub func decoder.get_quirk(key: base.u32) base.u64 {
return 0
}
pub func decoder.set_quirk!(key: base.u32, value: base.u64) base.status {
return base."#unsupported option"
}
pub func decoder.decode_image_config?(dst: nptr base.image_config, src: base.io_reader) {
var status : base.status
while true {
status =? this.do_decode_image_config?(dst: args.dst, src: args.src)
if (status == base."$short read") and args.src.is_closed() {
return "#truncated input"
}
yield? status
}
}
pri func decoder.do_decode_image_config?(dst: nptr base.image_config, src: base.io_reader) {
var a : base.u32
if this.call_sequence <> 0x00 {
return base."#bad call sequence"
}
a = args.src.read_u32le?()
if a <> 'qoif'le {
return "#bad header"
}
a = args.src.read_u32be?()
if a > 0xFF_FFFF {
return base."#unsupported image dimension"
}
this.width = a
a = args.src.read_u32be?()
if a > 0xFF_FFFF {
return base."#unsupported image dimension"
}
this.height = a
a = args.src.read_u8_as_u32?()
if a == 3 {
this.pixfmt = base.PIXEL_FORMAT__BGRX
} else if a == 4 {
this.pixfmt = base.PIXEL_FORMAT__BGRA_NONPREMUL
} else {
return "#bad header"
}
args.src.skip?(n: 1)
if args.dst <> nullptr {
args.dst.set!(
pixfmt: this.pixfmt,
pixsub: 0,
width: this.width,
height: this.height,
first_frame_io_position: 14,
first_frame_is_opaque: this.pixfmt == base.PIXEL_FORMAT__BGRX)
}
this.call_sequence = 0x20
}
pub func decoder.decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) {
var status : base.status
while true {
status =? this.do_decode_frame_config?(dst: args.dst, src: args.src)
if (status == base."$short read") and args.src.is_closed() {
return "#truncated input"
}
yield? status
}
}
pri func decoder.do_decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) {
if this.call_sequence == 0x20 {
// No-op.
} else if this.call_sequence < 0x20 {
this.do_decode_image_config?(dst: nullptr, src: args.src)
} else if this.call_sequence == 0x28 {
if 14 <> args.src.position() {
return base."#bad restart"
}
} else if this.call_sequence == 0x40 {
this.call_sequence = 0x60
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: 14,
disposal: 0,
opaque_within_bounds: this.pixfmt == base.PIXEL_FORMAT__BGRX,
overwrite_instead_of_blend: false,
background_color: 0x0000_0000)
}
this.call_sequence = 0x40
}
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
while true {
status =? this.do_decode_frame?(dst: args.dst, src: args.src, blend: args.blend, workbuf: args.workbuf, opts: args.opts)
if (status == base."$short read") and args.src.is_closed() {
return "#truncated input"
}
yield? status
}
}
pri func decoder.do_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
var c64 : base.u64
if this.call_sequence == 0x40 {
// No-op.
} else if this.call_sequence < 0x40 {
this.do_decode_frame_config?(dst: nullptr, src: args.src)
} else {
return base."@end of data"
}
status = this.swizzler.prepare!(
dst_pixfmt: args.dst.pixel_format(),
dst_palette: args.dst.palette(),
src_pixfmt: this.util.make_pixel_format(repr: this.pixfmt),
src_palette: this.util.empty_slice_u8(),
blend: args.blend)
if not status.is_ok() {
return status
}
this.dst_x = 0
this.dst_y = 0
this.pixel[0] = 0x00
this.pixel[1] = 0x00
this.pixel[2] = 0x00
this.pixel[3] = 0xFF
this.cache[.. 256].bulk_memset!(byte_value: 0)
this.remaining_pixels_times_4 = (this.width as base.u64) * (this.height as base.u64) * 4
while this.remaining_pixels_times_4 > 0 {
this.from_src_to_buffer?(src: args.src)
if this.remaining_pixels_times_4 < (this.buffer_index as base.u64) {
return base."#too much data"
}
this.remaining_pixels_times_4 -= this.buffer_index as base.u64
status = this.from_buffer_to_dst!(dst: args.dst)
if not status.is_ok() {
return status
}
}
c64 = args.src.read_u64be?()
if c64 <> 1 {
return "#bad footer"
}
this.call_sequence = 0x60
}
pri func decoder.from_src_to_buffer?(src: base.io_reader) {
var c8 : base.u8
var dg : base.u8
var bi : base.u32[..= 8192]
var bj : base.u32[..= 8188]
var bk : base.u32[..= 7936]
var ci : base.u32[..= 252]
var hash4 : base.u32[..= 252]
bk = 7936 // 4 * (2048 - 64), so we always have room for a QOI_OP_RUN.
if this.remaining_pixels_times_4 < 7936 {
bk = this.remaining_pixels_times_4 as base.u32
}
while bi < bk {
assert bi < 7936 via "a < b: a < c; c <= b"(c: bk)
c8 = args.src.read_u8?()
if c8 == 0xFE { // QOI_OP_RGB.
this.pixel[2] = args.src.read_u8?()
this.pixel[1] = args.src.read_u8?()
this.pixel[0] = args.src.read_u8?()
} else if c8 == 0xFF { // QOI_OP_RGBA.
this.pixel[2] = args.src.read_u8?()
this.pixel[1] = args.src.read_u8?()
this.pixel[0] = args.src.read_u8?()
this.pixel[3] = args.src.read_u8?()
} else if (c8 >> 6) == 0 { // QOI_OP_INDEX.
ci = 4 * ((c8 & 0x3F) as base.u32)
this.pixel[0] = this.cache[ci + 0]
this.pixel[1] = this.cache[ci + 1]
this.pixel[2] = this.cache[ci + 2]
this.pixel[3] = this.cache[ci + 3]
this.buffer[bi + 0] = this.pixel[0]
this.buffer[bi + 1] = this.pixel[1]
this.buffer[bi + 2] = this.pixel[2]
this.buffer[bi + 3] = this.pixel[3]
bi += 4
continue
} else if (c8 >> 6) == 1 { // QOI_OP_DIFF.
this.pixel[2] ~mod+= ((c8 >> 4) & 0x03) ~mod+ 0xFE
this.pixel[1] ~mod+= ((c8 >> 2) & 0x03) ~mod+ 0xFE
this.pixel[0] ~mod+= ((c8 >> 0) & 0x03) ~mod+ 0xFE
} else if (c8 >> 6) == 2 { // QOI_OP_LUMA.
dg = (c8 & 0x3F) ~mod+ 0xE0
c8 = args.src.read_u8?()
this.pixel[2] ~mod+= (dg ~mod+ 0xF8) ~mod+ (0x0F & (c8 >> 4))
this.pixel[1] ~mod+= dg
this.pixel[0] ~mod+= (dg ~mod+ 0xF8) ~mod+ (0x0F & (c8 >> 0))
} else { // QOI_OP_RUN.
bj = bi + (4 * (0x3F & (1 + (c8 as base.u32))))
while bi < bj {
assert bi < 8188 via "a < b: a < c; c <= b"(c: bj)
this.buffer[bi + 0] = this.pixel[0]
this.buffer[bi + 1] = this.pixel[1]
this.buffer[bi + 2] = this.pixel[2]
this.buffer[bi + 3] = this.pixel[3]
bi += 4
}
continue
}
hash4 = 4 * (63 & (
((this.pixel[2] as base.u32) * 3) +
((this.pixel[1] as base.u32) * 5) +
((this.pixel[0] as base.u32) * 7) +
((this.pixel[3] as base.u32) * 11)))
this.cache[hash4 + 0] = this.pixel[0]
this.cache[hash4 + 1] = this.pixel[1]
this.cache[hash4 + 2] = this.pixel[2]
this.cache[hash4 + 3] = this.pixel[3]
this.buffer[bi + 0] = this.pixel[0]
this.buffer[bi + 1] = this.pixel[1]
this.buffer[bi + 2] = this.pixel[2]
this.buffer[bi + 3] = this.pixel[3]
bi += 4
}
this.buffer_index = bi
}
pri func decoder.from_buffer_to_dst!(dst: ptr base.pixel_buffer) base.status {
var dst_pixfmt : base.pixel_format
var dst_bits_per_pixel : base.u32[..= 256]
var dst_bytes_per_pixel : base.u32[..= 32]
var dst_bytes_per_row : base.u64
var tab : table base.u8
var bi : base.u32
var rem_x : base.u32[..= 0xFF_FFFF]
var dst : slice base.u8
var src : slice base.u8
var src_length : base.u32
var i : 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
dst_bytes_per_row = (this.width * dst_bytes_per_pixel) as base.u64
tab = args.dst.plane(p: 0)
while bi < this.buffer_index {
assert bi < 8192 via "a < b: a < c; c <= b"(c: this.buffer_index)
if this.width <= this.dst_x {
this.dst_x = 0
this.dst_y ~mod+= 1
if this.dst_y >= this.height {
break
}
rem_x = this.width
} else {
rem_x = this.width - this.dst_x
}
src = this.buffer[bi .. this.buffer_index]
if ((4 * rem_x) as base.u64) < src.length() {
src = src[.. (4 * rem_x) as base.u64]
}
src_length = (src.length() & 0xFFFF_FFFF) as base.u32
bi ~mod+= src_length
dst = tab.row_u32(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 as base.u64)
this.dst_x ~mod+= src_length / 4
if i < dst.length() {
this.swizzler.swizzle_interleaved_from_slice!(
dst: dst[i ..],
dst_palette: args.dst.palette(),
src: src)
}
}
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 > 0x20 {
return 1
}
return 0
}
pub func decoder.num_decoded_frames() base.u64 {
if this.call_sequence > 0x40 {
return 1
}
return 0
}
pub func decoder.restart_frame!(index: base.u64, io_position: base.u64) base.status {
if this.call_sequence < 0x20 {
return base."#bad call sequence"
}
if (args.index <> 0) or (args.io_position <> 14) {
return base."#bad argument"
}
this.call_sequence = 0x28
return ok
}
pub func decoder.set_report_metadata!(fourcc: base.u32, report: base.bool) {
// No-op. QOI doesn't support metadata.
}
pub func decoder.tell_me_more?(dst: base.io_writer, minfo: nptr base.more_information, src: base.io_reader) {
return base."#no more information"
}
pub func decoder.workbuf_len() base.range_ii_u64 {
return this.util.make_range_ii_u64(min_incl: 0, max_incl: 0)
}