blob: d2f0cb2df40eaf77a85551350865a0f0e7d59d3b [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.
use "std/crc32"
use "std/zlib"
pub status "#bad checksum"
pub status "#bad chunk"
pub status "#bad filter"
pub status "#bad header"
pub status "#missing palette"
pub status "#unsupported PNG file"
pri status "#internal error: inconsistent workbuf length"
pri status "#internal error: zlib decoder did not exhaust its input"
pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0
pub struct decoder? implements base.image_decoder(
// The 0x00FF_FFFF limit is arbitrary (the PNG spec says 0x7FFF_FFFF) but
// it means that (width * height * bytes_per_pixel) doesn't overflow.
width : base.u32[..= 0x00FF_FFFF],
height : base.u32[..= 0x00FF_FFFF],
// bytes_per_row doesn't include the 1 byte for the per-row filter.
bytes_per_row : base.u64[..= 0x07FF_FFF8],
workbuf_wi : base.u64,
workbuf_length : base.u64[..= 0x0007_FFFF_F100_0007],
// 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,
ignore_checksum : base.bool,
depth : base.u8[..= 16],
color_type : base.u8,
filter_distance : base.u8[..= 8],
seen_palette : base.bool,
dst_pixfmt : base.u32,
src_pixfmt : base.u32,
chunk_type : base.u32,
chunk_type_array : array[4] base.u8,
chunk_length : base.u64,
frame_config_io_position : base.u64,
swizzler : base.pixel_swizzler,
util : base.utility,
)(
crc32 : crc32.ieee_hasher,
zlib : zlib.decoder,
dst_palette : array[4 * 256] base.u8,
src_palette : array[4 * 256] base.u8,
)
pub func decoder.set_quirk_enabled!(quirk: base.u32, enabled: base.bool) {
if args.quirk == base.QUIRK_IGNORE_CHECKSUM {
this.ignore_checksum = args.enabled
this.zlib.set_quirk_enabled!(quirk: args.quirk, enabled: args.enabled)
}
}
pub func decoder.decode_image_config?(dst: nptr base.image_config, src: base.io_reader) {
var magic : base.u64
var mark : base.u64
var checksum_have : base.u32
var checksum_want : base.u32
var status : base.status
if this.call_sequence <> 0 {
return base."#bad call sequence"
}
magic = args.src.read_u64le?()
if magic <> '\x89PNG\x0D\x0A\x1A\x0A'le {
return "#bad header"
}
magic = args.src.read_u64le?()
if magic <> '\x00\x00\x00\x0DIHDR'le {
return "#bad header"
}
this.crc32.reset!()
this.chunk_type_array[0] = 'I'
this.chunk_type_array[1] = 'H'
this.chunk_type_array[2] = 'D'
this.chunk_type_array[3] = 'R'
this.crc32.update_u32!(x: this.chunk_type_array[..])
while true {
mark = args.src.mark()
status =? this.decode_ihdr?(src: args.src)
if not this.ignore_checksum {
checksum_have = this.crc32.update_u32!(x: args.src.since(mark: mark))
}
if status.is_ok() {
break
}
yield? status
} endwhile
// Verify CRC-32 checksum.
checksum_want = args.src.read_u32be?()
if (not this.ignore_checksum) and (checksum_have <> checksum_want) {
return "#bad checksum"
}
// Read up until an IDAT chunk.
//
// By default, libpng "warns and discards" when seeing ancillary chunk
// checksum failures (as opposed to critical chunk checksum failures) but
// it still continues to decode the image. Wuffs' decoder is similar,
// simply always ignoring ancillary chunks' CRC-32 checksums.
//
// https://github.com/glennrp/libpng/blob/dbe3e0c43e549a1602286144d94b0666549b18e6/png.h#L1436
//
// We've already seen the IHDR chunk. We're not expecting an IEND chunk. An
// IDAT chunk breaks the loop. The only other possible critical chunk is a
// PLTE chunk. We verify PLTE checksums here but ignore other checksums.
while true {
this.chunk_length = args.src.read_u32be_as_u64?()
this.chunk_type = args.src.read_u32le?()
if (not this.ignore_checksum) and
((this.chunk_type == 'IDAT'le) or (this.chunk_type == 'PLTE'le)) {
this.crc32.reset!()
this.chunk_type_array[0] = ((this.chunk_type >> 0) & 0xFF) as base.u8
this.chunk_type_array[1] = ((this.chunk_type >> 8) & 0xFF) as base.u8
this.chunk_type_array[2] = ((this.chunk_type >> 16) & 0xFF) as base.u8
this.chunk_type_array[3] = ((this.chunk_type >> 24) & 0xFF) as base.u8
this.crc32.update_u32!(x: this.chunk_type_array[..])
}
if this.chunk_type == 'IDAT'le {
break
}
while true {
mark = args.src.mark()
status =? this.decode_other_chunk?(src: args.src)
if (not this.ignore_checksum) and (this.chunk_type == 'PLTE'le) {
checksum_have = this.crc32.update_u32!(x: args.src.since(mark: mark))
}
if status.is_ok() {
break
}
yield? status
} endwhile
checksum_want = args.src.read_u32be?()
if (not this.ignore_checksum) and (this.chunk_type == 'PLTE'le) and
(checksum_have <> checksum_want) {
return "#bad checksum"
}
} endwhile
if (this.color_type == 3) and (not this.seen_palette) {
return "#missing palette"
}
this.frame_config_io_position = args.src.position()
if args.dst <> nullptr {
args.dst.set!(
pixfmt: this.dst_pixfmt,
pixsub: 0,
width: this.width,
height: this.height,
first_frame_io_position: this.frame_config_io_position,
first_frame_is_opaque: false)
}
this.call_sequence = 3
}
pri func decoder.decode_ihdr?(src: base.io_reader) {
var a32 : base.u32
var a8 : base.u8
a32 = args.src.read_u32be?()
if a32 >= 0x8000_0000 {
return "#bad header"
} else if a32 >= 0x0100_0000 {
return "#unsupported PNG file"
}
this.width = a32
a32 = args.src.read_u32be?()
if a32 >= 0x8000_0000 {
return "#bad header"
} else if a32 >= 0x0100_0000 {
return "#unsupported PNG file"
}
this.height = a32
// Depth.
a8 = args.src.read_u8?()
if a8 > 16 {
return "#bad header"
}
this.depth = a8
// Color.
this.color_type = args.src.read_u8?()
if this.color_type == 0 {
this.dst_pixfmt = base.PIXEL_FORMAT__Y
this.src_pixfmt = base.PIXEL_FORMAT__Y
if this.depth == 8 {
this.filter_distance = 1
this.bytes_per_row = (this.width as base.u64) * 1
} else {
return "#unsupported PNG file"
}
} else if this.color_type == 2 {
this.dst_pixfmt = base.PIXEL_FORMAT__BGR
this.src_pixfmt = base.PIXEL_FORMAT__RGB
if this.depth == 8 {
this.filter_distance = 3
this.bytes_per_row = (this.width as base.u64) * 3
} else {
return "#unsupported PNG file"
}
} else if this.color_type == 3 {
// TODO: s/BINARY/NONPREMUL/ and decode the tRNS chunk.
this.dst_pixfmt = base.PIXEL_FORMAT__INDEXED__BGRA_BINARY
this.src_pixfmt = base.PIXEL_FORMAT__INDEXED__BGRA_BINARY
if this.depth == 8 {
this.filter_distance = 1
this.bytes_per_row = (this.width as base.u64) * 1
} else {
return "#unsupported PNG file"
}
} else if this.color_type == 6 {
this.dst_pixfmt = base.PIXEL_FORMAT__BGRA_NONPREMUL
this.src_pixfmt = base.PIXEL_FORMAT__RGBA_NONPREMUL
if this.depth == 8 {
this.filter_distance = 4
this.bytes_per_row = (this.width as base.u64) * 4
} else {
return "#unsupported PNG file"
}
} else {
return "#unsupported PNG file"
}
this.workbuf_length = (this.height as base.u64) * (1 + this.bytes_per_row)
this.choose_filter_implementations!()
// Compression.
a8 = args.src.read_u8?()
if a8 <> 0 {
return "#bad header"
}
// Filter.
a8 = args.src.read_u8?()
if a8 <> 0 {
return "#bad header"
}
// Interlace.
a8 = args.src.read_u8?()
if a8 == 0 {
// No-op.
} else if a8 == 1 {
return "#unsupported PNG file"
} else {
return "#bad header"
}
}
pri func decoder.choose_filter_implementations!() {
// Filter 0 is a no-op. Filter 2, the up filter, should already vectorize
// easily by a good optimizing C compiler.
if this.filter_distance == 3 {
choose filter_1 = [filter_1_distance_3_fallback]
choose filter_3 = [filter_3_distance_3_fallback]
choose filter_4 = [
filter_4_distance_3_sse42,
filter_4_distance_3_fallback]
} else if this.filter_distance == 4 {
choose filter_1 = [
filter_1_distance_4_sse42,
filter_1_distance_4_fallback]
choose filter_3 = [
filter_3_distance_4_sse42,
filter_3_distance_4_fallback]
choose filter_4 = [
filter_4_distance_4_sse42,
filter_4_distance_4_fallback]
}
}
pri func decoder.decode_other_chunk?(src: base.io_reader) {
if this.chunk_type == 'PLTE'le {
if this.seen_palette {
return "#bad chunk"
}
this.decode_plte?(src: args.src)
this.seen_palette = true
} else if this.chunk_type == 'tRNS'le {
// TODO.
args.src.skip?(n: this.chunk_length)
} else {
args.src.skip?(n: this.chunk_length)
}
}
pri func decoder.decode_plte?(src: base.io_reader) {
var num_palette_entries : base.u32[..= 256]
var i : base.u32
var argb : base.u32
if (this.chunk_length > 768) or ((this.chunk_length % 3) <> 0) {
return "#bad header"
}
num_palette_entries = (this.chunk_length as base.u32) / 3
while i < num_palette_entries {
assert i < 256 via "a < b: a < c; c <= b"(c: num_palette_entries)
// Convert from RGB (in memory order) to ARGB (in native u32 order)
// to BGRA (in memory order).
argb = args.src.read_u24be_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
// Set the remaining palette entries to opaque black.
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
}
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: false,
overwrite_instead_of_blend: false,
background_color: 0x0000_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 w : base.io_writer
var w_mark : base.u64
var r_mark : base.u64
var swizzler_status : base.status
var zlib_status : base.status
var checksum_have : base.u32
var checksum_want : base.u32
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"
}
this.workbuf_wi = 0
while true {
if (this.workbuf_wi > this.workbuf_length) or (
this.workbuf_length > args.workbuf.length()) {
return base."#bad workbuf length"
}
io_bind (io: w, data: args.workbuf[this.workbuf_wi .. this.workbuf_length]) {
io_limit (io: args.src, limit: this.chunk_length) {
w_mark = w.mark()
r_mark = args.src.mark()
zlib_status =? this.zlib.transform_io?(
dst: w, src: args.src, workbuf: this.util.empty_slice_u8())
if not this.ignore_checksum {
this.crc32.update_u32!(x: args.src.since(mark: r_mark))
}
this.chunk_length ~sat-= args.src.count_since(mark: r_mark)
this.workbuf_wi ~sat+= w.count_since(mark: w_mark)
}
}
if zlib_status.is_ok() {
break
} else if zlib_status == base."$short write" {
return base."#too much data"
} else if zlib_status <> base."$short read" {
return zlib_status
} else if this.chunk_length == 0 {
// Verify the non-final IDAT chunk's CRC-32 checksum.
checksum_want = args.src.read_u32be?()
if not this.ignore_checksum {
checksum_have = this.crc32.update_u32!(x: this.util.empty_slice_u8())
if checksum_have <> checksum_want {
return "#bad checksum"
}
}
// The next chunk should be another IDAT.
this.chunk_length = args.src.read_u32be_as_u64?()
this.chunk_type = args.src.read_u32le?()
if this.chunk_type <> 'IDAT'le {
return "#bad chunk"
}
// The 'IDAT'be is part of the next CRC-32 checksum's input.
if not this.ignore_checksum {
this.crc32.reset!()
this.chunk_type_array[0] = 'I'
this.chunk_type_array[1] = 'D'
this.chunk_type_array[2] = 'A'
this.chunk_type_array[3] = 'T'
this.crc32.update_u32!(x: this.chunk_type_array[..])
}
continue
} else if args.src.length() > 0 {
return "#internal error: zlib decoder did not exhaust its input"
}
yield? base."$short read"
} endwhile
// Verify the final IDAT chunk's CRC-32 checksum.
if not this.ignore_checksum {
if this.chunk_length > 0 {
// TODO: should this be a fatal error?
return base."#too much data"
}
checksum_have = this.crc32.update_u32!(x: this.util.empty_slice_u8())
checksum_want = args.src.read_u32be?()
if checksum_have <> checksum_want {
return "#bad checksum"
}
}
if this.workbuf_wi <> this.workbuf_length {
return base."#not enough data"
} else if 0 < args.workbuf.length() {
// For the top row, the Paeth filter (4) is equivalent to the Sub
// filter (1), but the Paeth implementation is simpler if it can assume
// that there is a previous row.
if args.workbuf[0] == 4 {
args.workbuf[0] = 1
}
}
swizzler_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: this.src_pixfmt),
src_palette: this.src_palette[..],
blend: args.blend)
if not swizzler_status.is_ok() {
return swizzler_status
}
swizzler_status = this.filter_and_swizzle!(dst: args.dst, workbuf: args.workbuf)
if not swizzler_status.is_ok() {
return swizzler_status
}
this.call_sequence = 0xFF
}
pri func decoder.filter_and_swizzle!(dst: ptr base.pixel_buffer, workbuf: slice base.u8) 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 y : base.u32
var dst : slice base.u8
var filter : base.u8
var curr_row : slice base.u8
var prev_row : slice base.u8
// 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.dst_palette[..])
tab = args.dst.plane(p: 0)
while y < this.height {
assert y < 0xFFFF_FFFF via "a < b: a < c; c <= b"(c: this.height)
dst = tab.row(y: y)
if dst_bytes_per_row < dst.length() {
dst = dst[.. dst_bytes_per_row]
}
if 1 > args.workbuf.length() {
return "#internal error: inconsistent workbuf length"
}
filter = args.workbuf[0]
args.workbuf = args.workbuf[1 ..]
if this.bytes_per_row > args.workbuf.length() {
return "#internal error: inconsistent workbuf length"
}
curr_row = args.workbuf[.. this.bytes_per_row]
args.workbuf = args.workbuf[this.bytes_per_row ..]
if filter == 0 {
// No-op.
} else if filter == 1 {
this.filter_1!(curr: curr_row)
} else if filter == 2 {
this.filter_2!(curr: curr_row, prev: prev_row)
} else if filter == 3 {
this.filter_3!(curr: curr_row, prev: prev_row)
} else if filter == 4 {
this.filter_4!(curr: curr_row, prev: prev_row)
} else {
return "#bad filter"
}
this.swizzler.swizzle_interleaved_from_slice!(
dst: dst,
dst_palette: dst_palette,
src: curr_row)
prev_row = curr_row
y += 1
} endwhile
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) {
// TODO.
}
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: this.workbuf_length,
max_incl: this.workbuf_length)
}