blob: 6b49b6f91df7a79d114651a3f0f2e82bb754ba5d [file] [log] [blame]
// Copyright 2023 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 DHT marker"
pub status "#bad DQT marker"
pub status "#bad DRI marker"
pub status "#bad SOF marker"
pub status "#bad SOS marker"
pub status "#bad header"
pub status "#bad marker"
pub status "#truncated input"
pub status "#unsupported arithmetic coding"
pub status "#unsupported hierarchical coding"
pub status "#unsupported lossless coding"
pub status "#unsupported implicit height"
pub status "#unsupported marker"
pub status "#unsupported precision"
pub status "#unsupported precision (12 bits)"
pub status "#unsupported precision (16 bits)"
pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0
pub struct decoder? implements base.image_decoder(
width : base.u32[..= 0xFFFF],
height : base.u32[..= 0xFFFF],
// The call sequence state machine is discussed in
// (/doc/std/image-decoders-call-sequence.md).
call_sequence : base.u8,
sof_marker : base.u8,
num_components : base.u32[..= 4],
components_c : array[4] base.u8,
components_h : array[4] base.u8[..= 4],
components_v : array[4] base.u8[..= 4],
components_tq : array[4] base.u8[..= 4],
payload_length : base.u32[..= 0xFFFF],
restart_interval : base.u32[..= 0xFFFF],
frame_config_io_position : base.u64,
seen_dqt : array[4] base.bool,
quant_tables : array[4] array[64] base.u8,
swizzler : base.pixel_swizzler,
util : base.utility,
) + (
dst_palette : array[4 * 256] base.u8,
)
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
} endwhile
}
pri func decoder.do_decode_image_config?(dst: nptr base.image_config, src: base.io_reader) {
var c : base.u8
var marker : base.u8
if this.call_sequence <> 0x00 {
return base."#bad call sequence"
}
c = args.src.read_u8?()
if c <> 0xFF {
return "#bad header"
}
c = args.src.read_u8?()
if c <> 0xD8 { // SOI (Start Of Image).
return "#bad header"
}
// Process chunks (markers and their payloads).
while true {
// Read the marker (a two-byte 0xFF 0x?? sequence).
while true {
c = args.src.read_u8?()
if c == 0xFF {
break
}
// Getting here is invalid according to the JPEG spec, but libjpeg
// treats this as a warning (JWRN_EXTRANEOUS_DATA), not an error.
} endwhile
while true {
c = args.src.read_u8?()
if c <> 0xFF {
marker = c
break
}
// Section B.1.1.2: "Any marker may optionally be preceded by any
// number of [0xFF] fill bytes".
} endwhile
if marker == 0x00 {
// Ignore byte stuffing.
continue
} else if (0xD0 <= marker) and (marker <= 0xD7) {
// RSTn (Restart) markers have no payload.
continue
}
// Payload length includes the 2 bytes for the u16be payload length.
this.payload_length = args.src.read_u16be_as_u32?()
if this.payload_length < 2 {
return "#bad marker"
}
this.payload_length -= 2
// Switch on the marker.
if marker < 0xC0 {
return "#unsupported marker"
} else if marker < 0xD0 { // SOFn (Start of Frame) and friends.
if marker <= 0xC2 {
if this.sof_marker <> 0 {
return "#bad SOF marker"
}
this.sof_marker = marker
this.decode_sof?(src: args.src)
break
} else if marker == 0xC3 {
return "#unsupported lossless coding"
} else if marker == 0xC4 { // DHT (Define Huffman Table).
// We shouldn't see DHT before SOF.
return "#bad DHT marker"
} else if (0xC5 <= marker) and (marker <= 0xC7) {
return "#unsupported hierarchical coding"
} else if marker == 0xC8 { // JPG (JPEG extension).
return "#unsupported marker"
} else {
return "#unsupported arithmetic coding"
}
} else if marker < 0xE0 {
if marker < 0xDA {
// RSTn markers are already handled above. We either have 0xD8
// (SOI: Start Of Image) or 0xD9 (EOI: End Of Image), neither
// of which are valid here.
return "#bad marker"
} else if marker == 0xDA { // SOS (Start Of Scan).
// We shouldn't see SOS before SOF.
return "#bad SOS marker"
} else if marker == 0xDB { // DQT (Define Quantization Table).
this.decode_dqt?(src: args.src)
continue
} else if marker == 0xDD { // DRI (Define Restart Interval).
this.decode_dri?(src: args.src)
continue
} else {
// 0xDC (DNL: Define Number of Lines).
// 0xDE (DHP: Define Hierarchical Progression).
// 0xDF (EXP: Expand Reference Components).
return "#unsupported marker"
}
} else if marker < 0xF0 { // APPn (Application specific).
// No-op.
} else {
if marker == 0xFE { // COM (Comment).
// No-op.
} else {
return "#unsupported marker"
}
}
args.src.skip_u32?(n: this.payload_length)
this.payload_length = 0
} endwhile
this.frame_config_io_position = args.src.position()
if args.dst <> nullptr {
args.dst.set!(
// TODO: base.PIXEL_FORMAT__YCBCR is probably more correct,
// although possibly less convenient for the caller.
pixfmt: base.PIXEL_FORMAT__BGRX,
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 = 0x20
}
pri func decoder.decode_dqt?(src: base.io_reader) {
var c : base.u8
var q : base.u8[..= 3]
var i : base.u32
while this.payload_length > 0 {
this.payload_length -= 1
c = args.src.read_u8?()
if (c & 0x0F) > 3 {
return "#bad DQT marker"
}
q = c & 0x0F
if (c >> 4) == 1 {
return "#unsupported precision"
} else if ((c >> 4) > 1) or (this.payload_length < 64) {
return "#bad DQT marker"
}
this.payload_length -= 64
i = 0
while i < 64 {
this.quant_tables[q][UNZIG[i]] = args.src.read_u8?()
i += 1
} endwhile
this.seen_dqt[q] = true
} endwhile
}
pri func decoder.decode_dri?(src: base.io_reader) {
if this.payload_length <> 2 {
return "#bad DRI marker"
}
this.payload_length = 0
this.restart_interval = args.src.read_u16be_as_u32?()
}
pri func decoder.decode_sof?(src: base.io_reader) {
var c : base.u8
var comp_h : base.u8
var comp_v : base.u8
var i : base.u32
var j : base.u32
if this.payload_length < 6 {
return "#bad SOF marker"
}
this.payload_length -= 6
c = args.src.read_u8?()
if c == 8 {
// No-op.
} else if c == 12 {
return "#unsupported precision (12 bits)"
} else if c == 16 {
return "#unsupported precision (16 bits)"
} else {
return "#unsupported precision"
}
this.height = args.src.read_u16be_as_u32?()
if this.height == 0 {
return "#unsupported implicit height"
}
this.width = args.src.read_u16be_as_u32?()
if this.width == 0 {
return base."#unsupported image dimension"
}
c = args.src.read_u8?()
if (c == 0) or (c > 4) {
return "#bad SOF marker"
}
this.num_components = c as base.u32
if this.payload_length <> (3 * this.num_components) {
return "#bad SOF marker"
}
this.payload_length = 0
i = 0
while i < this.num_components {
assert i < 4 via "a < b: a < c; c <= b"(c: this.num_components)
this.components_c[i] = args.src.read_u8?()
c = args.src.read_u8?()
comp_h = c >> 4
comp_v = c & 0x0F
if (comp_h == 0) or (comp_h > 4) or (comp_v == 0) or (comp_v > 4) {
return "#bad SOF marker"
}
this.components_h[i] = comp_h
this.components_v[i] = comp_v
c = args.src.read_u8?()
if c > 4 {
return "#bad SOF marker"
}
this.components_tq[i] = c
// Section B.2.2: "the value of C_i shall be different from the values
// of C_1 through C_(i-1)".
j = 0
while j < i,
inv i < 4,
{
assert j < 4 via "a < b: a < c; c < b"(c: i)
if this.components_c[j] == this.components_c[i] {
return "#bad SOF marker"
}
j += 1
} endwhile
i += 1
} endwhile
}
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
} endwhile
}
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 this.frame_config_io_position <> 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: this.frame_config_io_position,
disposal: 0,
opaque_within_bounds: true,
overwrite_instead_of_blend: false,
background_color: 0xFF00_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
} endwhile
}
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 dst_pixfmt : base.pixel_format
var dst_bits_per_pixel : base.u32[..= 256]
var dst_bytes_per_pixel : base.u32[..= 32]
var tab : table base.u8
var dst : slice base.u8
var src : array[4] base.u8
var y : base.u32
var x : base.u32
var d : 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_or_else(fallback: this.dst_palette[..]),
src_pixfmt: this.util.make_pixel_format(repr: base.PIXEL_FORMAT__BGRX),
src_palette: this.util.empty_slice_u8(),
blend: args.blend)
if not status.is_ok() {
return status
}
// 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
// TODO: actually decode the pixels. Until then, fill with gradient tiles.
tab = args.dst.plane(p: 0)
y = 0
while y < this.height {
assert y < 0xFFFF via "a < b: a < c; c <= b"(c: this.height)
x = 0
while x < this.width,
inv y < 0xFFFF,
{
assert x < 0xFFFF via "a < b: a < c; c <= b"(c: this.width)
dst = tab.row_u32(y: y)
d = (x * dst_bytes_per_pixel) as base.u64
if d < dst.length() {
src[0] = (x & 0xFF) as base.u8
src[1] = 0x7F
src[2] = (y & 0xFF) as base.u8
src[3] = 0xFF
this.swizzler.swizzle_interleaved_from_slice!(
dst: dst[d ..],
dst_palette: args.dst.palette_or_else(fallback: this.dst_palette[..]),
src: src[.. 4])
}
x += 1
} endwhile
y += 1
} endwhile
this.call_sequence = 0x60
}
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 {
return base."#bad argument"
}
this.call_sequence = 0x28
this.frame_config_io_position = args.io_position
return ok
}
pub func decoder.set_report_metadata!(fourcc: base.u32, report: base.bool) {
// TODO: implement.
}
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)
}