| // Copyright 2017 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 "go run preprocess-wuffs.go" TO MAKE decode_config.wuffs | 
 | //#WHEN PREPROC000 | 
 | // | 
 | // This file defines "struct decoder". It is also preprocessed (copied with | 
 | // automatic edits) to generate the decode_config.wuffs file that defines | 
 | // "struct config_decoder". See preprocess-wuffs.go for more detail about the | 
 | // preprocessor language (the //#FOOBAR comments). | 
 | // | 
 | // config_decoder does not implement decode_frame, only decode_frame_config and | 
 | // decode_image_config (and other miscellaneous methods). The sizeof the | 
 | // config_decoder can be substantially smaller than the sizeof the full | 
 | // decoder, as it does not need any internal buffers for understanding the | 
 | // underlying compression format. | 
 | // | 
 | //#WHEN PREPROC000 decode_config.wuffs | 
 | //#REPLACE   "func decoder" WITH   "func config_decoder" | 
 | //#REPLACE "struct decoder" WITH "struct config_decoder" | 
 | //#DONE PREPROC000 | 
 |  | 
 | // -------- | 
 |  | 
 | pub struct decoder? implements base.image_decoder( | 
 | 	width  : base.u32, | 
 | 	height : base.u32, | 
 |  | 
 | 	// Call sequence states: | 
 | 	//  - 0: initial state. | 
 | 	//  - 1: metadata reported; image config decode is in progress. | 
 | 	//  - 2: metadata finished; image config decode is in progress. | 
 | 	//  - 3: image config decoded, including the first frame's bounds, but not | 
 | 	//       the first frame's pixels. | 
 | 	//  - 4: frame config decoded. | 
 | 	//  - 5: frame decoded. | 
 | 	// | 
 | 	// State transitions: | 
 | 	// | 
 | 	//  - 0 -> 1: via IC (metadata reported) | 
 | 	//  - 0 -> 3: via IC (metadata not reported) | 
 | 	//  - 0 -> 4: via FC with implicit IC | 
 | 	//  - 0 -> 5: via F  with implicit IC and FC | 
 | 	// | 
 | 	//  - 1 -> 2: via AMC | 
 | 	// | 
 | 	//  - 2 -> 1: via IC (metadata reported) | 
 | 	//  - 2 -> 3: via IC (metadata not reported) | 
 | 	// | 
 | 	//  - 3 -> 4: via FC | 
 | 	//  - 3 -> 5: via F  with implicit FC | 
 | 	// | 
 | 	//  - 4 -> 4: via FC with implicit F | 
 | 	//  - 4 -> 5: via F | 
 | 	// | 
 | 	//  - 5 -> 4: via FC | 
 | 	//  - 5 -> 5: via F  with implicit FC | 
 | 	// | 
 | 	// Where: | 
 | 	//  - AMC is ack_metadata_chunk | 
 | 	//  - F   is decode_frame,        implicit means skip_frame | 
 | 	//  - FC  is decode_frame_config, implicit means nullptr args.dst | 
 | 	//  - IC  is decode_image_config, implicit means nullptr args.dst | 
 | 	call_sequence : base.u8, | 
 |  | 
 | 	ignore_metadata      : base.bool, | 
 | 	report_metadata_iccp : base.bool, | 
 | 	report_metadata_xmp  : base.bool, | 
 | 	metadata_fourcc      : base.u32, | 
 | 	metadata_io_position : base.u64, | 
 |  | 
 | 	quirks : array[QUIRKS_COUNT] base.bool, | 
 |  | 
 | 	delayed_num_decoded_frames         : base.bool, | 
 | 	end_of_data                        : base.bool, | 
 | 	restarted                          : base.bool, | 
 | 	previous_lzw_decode_ended_abruptly : base.bool, | 
 |  | 
 | 	has_global_palette : base.bool, | 
 |  | 
 | 	// interlace indexes the INTERLACE_START and INTERLACE_DELTA arrays. | 
 | 	interlace : base.u8[..= 4], | 
 |  | 
 | 	// Absent an ANIMEXTS1.0 or NETSCAPE2.0 extension, the implicit number of | 
 | 	// animation loops is 1. | 
 | 	seen_num_loops : base.bool, | 
 | 	num_loops      : base.u32, | 
 |  | 
 | 	background_color_u32_argb_premul : base.u32, | 
 | 	black_color_u32_argb_premul      : base.u32, | 
 |  | 
 | 	gc_has_transparent_index : base.bool, | 
 | 	gc_transparent_index     : base.u8, | 
 | 	gc_disposal              : base.u8, | 
 | 	// There are 7_056000 flicks per centisecond. | 
 | 	gc_duration : base.u64[..= 0xFFFF * 7_056000], | 
 |  | 
 | 	frame_config_io_position        : base.u64, | 
 | 	num_decoded_frame_configs_value : base.u64, | 
 | 	num_decoded_frames_value        : base.u64, | 
 |  | 
 | 	frame_rect_x0 : base.u32, | 
 | 	frame_rect_y0 : base.u32, | 
 | 	frame_rect_x1 : base.u32, | 
 | 	frame_rect_y1 : base.u32, | 
 |  | 
 | 	//#WHEN PREPROC100 | 
 | 	// The dst_etc fields are the output cursor during copy_to_image_buffer. | 
 | 	dst_x            : base.u32, | 
 | 	dst_y            : base.u32, | 
 | 	dirty_max_excl_y : base.u32, | 
 |  | 
 | 	// Indexes into the compressed array, defined below. | 
 | 	compressed_ri : base.u64, | 
 | 	compressed_wi : base.u64, | 
 |  | 
 | 	swizzler : base.pixel_swizzler, | 
 | 	//#DONE PREPROC100 | 
 |  | 
 | 	util : base.utility, | 
 | )( | 
 | 	//#WHEN PREPROC101 | 
 | 	compressed : array[4096] base.u8, | 
 |  | 
 | 	// palettes[0] and palettes[1] are the Global and Local Color Table. | 
 | 	palettes : array[2] array[4 * 256] base.u8, | 
 | 	// dst_palette is the swizzled color table. | 
 | 	dst_palette : array[4 * 256] base.u8, | 
 |  | 
 | 	lzw : lzw.decoder, | 
 | 	//#WHEN PREPROC101 decode_config.wuffs | 
 | 	//## palettes : array[1] array[4 * 256] base.u8, | 
 | 	//#DONE PREPROC101 | 
 | ) | 
 |  | 
 | pub func decoder.set_quirk_enabled!(quirk: base.u32, enabled: base.bool) { | 
 | 	if (this.call_sequence == 0) and (args.quirk >= QUIRKS_BASE) { | 
 | 		args.quirk -= QUIRKS_BASE | 
 | 		if args.quirk < QUIRKS_COUNT { | 
 | 			this.quirks[args.quirk] = args.enabled | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | pub func decoder.decode_image_config?(dst: nptr base.image_config, src: base.io_reader) { | 
 | 	var ffio : base.bool | 
 |  | 
 | 	if this.call_sequence == 0 { | 
 | 		this.decode_header?(src: args.src) | 
 | 		this.decode_lsd?(src: args.src) | 
 | 	} else if this.call_sequence <> 2 { | 
 | 		return base."#bad call sequence" | 
 | 	} | 
 |  | 
 | 	this.decode_up_to_id_part1?(src: args.src) | 
 |  | 
 | 	// TODO: if this.end_of_data, return an error and/or set dst to zero? | 
 |  | 
 | 	ffio = not this.gc_has_transparent_index | 
 | 	if not this.quirks[QUIRK_HONOR_BACKGROUND_COLOR - QUIRKS_BASE] { | 
 | 		ffio = ffio and | 
 | 			(this.frame_rect_x0 == 0) and | 
 | 			(this.frame_rect_y0 == 0) and | 
 | 			(this.frame_rect_x1 == this.width) and | 
 | 			(this.frame_rect_y1 == this.height) | 
 | 	} else if ffio { | 
 | 		// Use opaque black, not transparent black. | 
 | 		this.black_color_u32_argb_premul = 0xFF00_0000 | 
 | 	} | 
 |  | 
 | 	if this.background_color_u32_argb_premul == 77 { | 
 | 		this.background_color_u32_argb_premul = this.black_color_u32_argb_premul | 
 | 	} | 
 |  | 
 | 	if args.dst <> nullptr { | 
 | 		// TODO: a Wuffs (not just C) name for the | 
 | 		// WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY magic pixfmt constant. | 
 | 		args.dst.set!( | 
 | 			pixfmt: 0x4704_0008, | 
 | 			pixsub: 0, | 
 | 			width: this.width, | 
 | 			height: this.height, | 
 | 			first_frame_io_position: this.frame_config_io_position, | 
 | 			first_frame_is_opaque: ffio) | 
 | 	} | 
 |  | 
 | 	this.call_sequence = 3 | 
 | } | 
 |  | 
 | pub func decoder.set_report_metadata!(fourcc: base.u32, report: base.bool) { | 
 | 	if args.fourcc == 'ICCP'be { | 
 | 		this.report_metadata_iccp = args.report | 
 | 	} else if args.fourcc == 'XMP 'be { | 
 | 		this.report_metadata_xmp = args.report | 
 | 	} | 
 | } | 
 |  | 
 | pub func decoder.tell_me_more?(dst: base.io_writer, minfo: nptr base.more_information, src: base.io_reader) { | 
 | 	var chunk_length : base.u64 | 
 |  | 
 | 	if this.call_sequence <> 1 { | 
 | 		return base."#bad call sequence" | 
 | 	} | 
 | 	if this.metadata_fourcc == 0 { | 
 | 		return base."#no more information" | 
 | 	} | 
 |  | 
 | 	while true { | 
 | 		while true, | 
 | 			post args.src.available() > 0, | 
 | 		{ | 
 | 			if args.src.position() <> this.metadata_io_position { | 
 | 				if args.minfo <> nullptr { | 
 | 					args.minfo.set!( | 
 | 						flavor: 2,  // WUFFS_BASE__MORE_INFORMATION__FLAVOR__IO_SEEK | 
 | 						w: 0, | 
 | 						x: this.metadata_io_position, | 
 | 						y: 0, | 
 | 						z: 0) | 
 | 				} | 
 | 				yield? base."$mispositioned read" | 
 | 				continue | 
 | 			} | 
 |  | 
 | 			if args.src.available() <= 0 { | 
 | 				if args.minfo <> nullptr { | 
 | 					args.minfo.set!( | 
 | 						flavor: 0, | 
 | 						w: 0, | 
 | 						x: 0, | 
 | 						y: 0, | 
 | 						z: 0) | 
 | 				} | 
 | 				yield? base."$short read" | 
 | 				continue | 
 | 			} | 
 |  | 
 | 			break | 
 | 		} endwhile | 
 |  | 
 | 		chunk_length = args.src.peek_u8_as_u64() | 
 | 		if chunk_length <= 0 { | 
 | 			// Consume the '\x00' that means a zero-length block. | 
 | 			args.src.skip32_fast!(actual: 1, worst_case: 1) | 
 | 			break | 
 | 		} | 
 |  | 
 | 		if this.metadata_fourcc == 'XMP 'be { | 
 | 			// The +1 is because XMP metadata's encoding includes each | 
 | 			// block's leading byte (the block size) as part of the | 
 | 			// metadata passed to the caller. | 
 | 			chunk_length += 1 | 
 | 		} else { | 
 | 			args.src.skip32_fast!(actual: 1, worst_case: 1) | 
 | 		} | 
 | 		this.metadata_io_position = args.src.position() ~sat+ chunk_length | 
 |  | 
 | 		if args.minfo <> nullptr { | 
 | 			args.minfo.set!( | 
 | 				flavor: 3,  // WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA | 
 | 				w: this.metadata_fourcc, | 
 | 				x: 0, | 
 | 				y: args.src.position(), | 
 | 				z: this.metadata_io_position) | 
 | 		} | 
 |  | 
 | 		yield? base."$even more information" | 
 | 	} endwhile | 
 |  | 
 | 	if args.minfo <> nullptr { | 
 | 		args.minfo.set!( | 
 | 			flavor: 3,  // WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA | 
 | 			w: this.metadata_fourcc, | 
 | 			x: 0, | 
 | 			y: this.metadata_io_position, | 
 | 			z: this.metadata_io_position) | 
 | 	} | 
 | 	this.call_sequence = 2 | 
 | 	this.metadata_fourcc = 0 | 
 | 	this.metadata_io_position = 0 | 
 | 	return ok | 
 | } | 
 |  | 
 | pub func decoder.num_animation_loops() base.u32 { | 
 | 	if this.seen_num_loops { | 
 | 		return this.num_loops | 
 | 	} | 
 | 	return 1 | 
 | } | 
 |  | 
 | pub func decoder.num_decoded_frame_configs() base.u64 { | 
 | 	return this.num_decoded_frame_configs_value | 
 | } | 
 |  | 
 | pub func decoder.num_decoded_frames() base.u64 { | 
 | 	return this.num_decoded_frames_value | 
 | } | 
 |  | 
 | pub func decoder.frame_dirty_rect() base.rect_ie_u32 { | 
 | 	//#WHEN PREPROC200 | 
 | 	// The "foo.min(a:this.width_or_height)" calls clip the nominal frame_rect | 
 | 	// to the image_rect. | 
 | 	return this.util.make_rect_ie_u32( | 
 | 		min_incl_x: this.frame_rect_x0.min(a: this.width), | 
 | 		min_incl_y: this.frame_rect_y0.min(a: this.height), | 
 | 		max_excl_x: this.frame_rect_x1.min(a: this.width), | 
 | 		max_excl_y: this.dirty_max_excl_y.min(a: this.height)) | 
 | 	//#WHEN PREPROC200 decode_config.wuffs | 
 | 	//## return this.util.empty_rect_ie_u32() | 
 | 	//#DONE PREPROC200 | 
 | } | 
 |  | 
 | pub func decoder.workbuf_len() base.range_ii_u64 { | 
 | 	//#WHEN PREPROC201 | 
 | 	return this.util.make_range_ii_u64( | 
 | 		min_incl: DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE, | 
 | 		max_incl: DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE) | 
 | 	//#WHEN PREPROC201 decode_config.wuffs | 
 | 	//## return this.util.empty_range_ii_u64() | 
 | 	//#DONE PREPROC201 | 
 | } | 
 |  | 
 | pub func decoder.restart_frame!(index: base.u64, io_position: base.u64) base.status { | 
 | 	if this.call_sequence == 0 { | 
 | 		return base."#bad call sequence" | 
 | 	} | 
 | 	this.delayed_num_decoded_frames = false | 
 | 	this.end_of_data = false | 
 | 	this.restarted = true | 
 | 	this.frame_config_io_position = args.io_position | 
 | 	this.num_decoded_frame_configs_value = args.index | 
 | 	this.num_decoded_frames_value = args.index | 
 | 	this.reset_gc!() | 
 | 	return ok | 
 | } | 
 |  | 
 | pub func decoder.decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) { | 
 | 	var background_color : base.u32 | 
 | 	var flags            : base.u8 | 
 |  | 
 | 	this.ignore_metadata = true | 
 |  | 
 | 	//#WHEN PREPROC202 | 
 | 	this.dirty_max_excl_y = 0 | 
 | 	//#DONE PREPROC202 | 
 |  | 
 | 	if not this.end_of_data { | 
 | 		if this.call_sequence == 0 { | 
 | 			this.decode_image_config?(dst: nullptr, src: args.src) | 
 | 		} else if this.call_sequence <> 3 { | 
 | 			if this.call_sequence == 4 { | 
 | 				this.skip_frame?(src: args.src) | 
 | 			} | 
 | 			this.decode_up_to_id_part1?(src: args.src) | 
 | 		} | 
 | 	} | 
 |  | 
 | 	// This is a new "if", not an "else", because the calls above can modify | 
 | 	// this.end_of_data. | 
 | 	if this.end_of_data { | 
 | 		return base."@end of data" | 
 | 	} | 
 |  | 
 | 	background_color = this.black_color_u32_argb_premul | 
 | 	if not this.gc_has_transparent_index { | 
 | 		background_color = this.background_color_u32_argb_premul | 
 |  | 
 | 		// If the quirk is enabled and the first frame has a local color | 
 | 		// palette, its background color is black. | 
 | 		if this.quirks[QUIRK_FIRST_FRAME_LOCAL_PALETTE_MEANS_BLACK_BACKGROUND - QUIRKS_BASE] and | 
 | 			(this.num_decoded_frame_configs_value == 0) { | 
 |  | 
 | 			while args.src.available() <= 0, | 
 | 				post args.src.available() > 0, | 
 | 			{ | 
 | 				yield? base."$short read" | 
 | 			} endwhile | 
 | 			flags = args.src.peek_u8() | 
 | 			if (flags & 0x80) <> 0 { | 
 | 				background_color = this.black_color_u32_argb_premul | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if args.dst <> nullptr { | 
 | 		// The "foo.min(a:this.width_or_height)" calls clip the nominal | 
 | 		// frame_rect to the image_rect. | 
 | 		args.dst.update!(bounds: this.util.make_rect_ie_u32( | 
 | 			min_incl_x: this.frame_rect_x0.min(a: this.width), | 
 | 			min_incl_y: this.frame_rect_y0.min(a: this.height), | 
 | 			max_excl_x: this.frame_rect_x1.min(a: this.width), | 
 | 			max_excl_y: this.frame_rect_y1.min(a: this.height)), | 
 | 			duration: this.gc_duration, | 
 | 			index: this.num_decoded_frame_configs_value, | 
 | 			io_position: this.frame_config_io_position, | 
 | 			disposal: this.gc_disposal, | 
 | 			opaque_within_bounds: not this.gc_has_transparent_index, | 
 | 			overwrite_instead_of_blend: false, | 
 | 			background_color: background_color) | 
 | 	} | 
 |  | 
 | 	this.num_decoded_frame_configs_value ~sat+= 1 | 
 | 	this.call_sequence = 4 | 
 | } | 
 |  | 
 | pri func decoder.skip_frame?(src: base.io_reader) { | 
 | 	var flags : base.u8 | 
 | 	var lw    : base.u8 | 
 |  | 
 | 	// Skip the optional Local Color Table, 3 bytes (RGB) per entry. | 
 | 	flags = args.src.read_u8?() | 
 | 	if (flags & 0x80) <> 0 { | 
 | 		args.src.skip32?(n: (3 as base.u32) << (1 + (flags & 0x07))) | 
 | 	} | 
 |  | 
 | 	// Process the LZW literal width. | 
 | 	lw = args.src.read_u8?() | 
 | 	if lw > 8 { | 
 | 		return "#bad literal width" | 
 | 	} | 
 |  | 
 | 	// Skip the blocks of LZW-compressed data. | 
 | 	this.skip_blocks?(src: args.src) | 
 |  | 
 | 	if this.quirks[QUIRK_DELAY_NUM_DECODED_FRAMES - QUIRKS_BASE] { | 
 | 		this.delayed_num_decoded_frames = true | 
 | 	} else { | 
 | 		this.num_decoded_frames_value ~sat+= 1 | 
 | 	} | 
 | 	this.reset_gc!() | 
 | } | 
 |  | 
 | // TODO: honor args.opts. | 
 | 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) { | 
 | 	//#WHEN PREPROC300 | 
 | 	this.ignore_metadata = true | 
 | 	if this.call_sequence <> 4 { | 
 | 		this.decode_frame_config?(dst: nullptr, src: args.src) | 
 | 	} | 
 | 	if this.quirks[QUIRK_REJECT_EMPTY_FRAME - QUIRKS_BASE] and | 
 | 		((this.frame_rect_x0 == this.frame_rect_x1) or (this.frame_rect_y0 == this.frame_rect_y1)) { | 
 | 		return "#bad frame size" | 
 | 	} | 
 | 	this.decode_id_part1?(dst: args.dst, src: args.src, blend: args.blend) | 
 | 	this.decode_id_part2?(dst: args.dst, src: args.src, workbuf: args.workbuf) | 
 |  | 
 | 	this.num_decoded_frames_value ~sat+= 1 | 
 | 	this.reset_gc!() | 
 | 	//#WHEN PREPROC300 decode_config.wuffs | 
 | 	//## return base."#unsupported method" | 
 | 	//#DONE PREPROC300 | 
 | } | 
 |  | 
 | pri func decoder.reset_gc!() { | 
 | 	this.call_sequence = 5 | 
 | 	// The Image Descriptor is mandatory, but the Graphic Control extension is | 
 | 	// optional. Reset the GC related fields for the next decode_frame call. | 
 | 	this.gc_has_transparent_index = false | 
 | 	this.gc_transparent_index = 0 | 
 | 	this.gc_disposal = 0 | 
 | 	this.gc_duration = 0 | 
 | } | 
 |  | 
 | pri func decoder.decode_up_to_id_part1?(src: base.io_reader) { | 
 | 	var block_type : base.u8 | 
 |  | 
 | 	if not this.restarted { | 
 | 		if this.call_sequence <> 2 { | 
 | 			this.frame_config_io_position = args.src.position() | 
 | 		} | 
 | 	} else if this.frame_config_io_position <> args.src.position() { | 
 | 		return base."#bad restart" | 
 | 	} else { | 
 | 		this.restarted = false | 
 | 	} | 
 |  | 
 | 	while true { | 
 | 		block_type = args.src.read_u8?() | 
 | 		if block_type == 0x21 {  // The spec calls 0x21 the "Extension Introducer". | 
 | 			this.decode_extension?(src: args.src) | 
 | 		} else if block_type == 0x2C {  // The spec calls 0x2C the "Image Separator". | 
 | 			if this.delayed_num_decoded_frames { | 
 | 				this.delayed_num_decoded_frames = false | 
 | 				this.num_decoded_frames_value ~sat+= 1 | 
 | 			} | 
 | 			this.decode_id_part0?(src: args.src) | 
 | 			break | 
 | 		} else if block_type == 0x3B {  // The spec calls 0x3B the "Trailer". | 
 | 			if this.delayed_num_decoded_frames { | 
 | 				this.delayed_num_decoded_frames = false | 
 | 				this.num_decoded_frames_value ~sat+= 1 | 
 | 			} | 
 | 			this.end_of_data = true | 
 | 			break | 
 | 		} else { | 
 | 			return "#bad block" | 
 | 		} | 
 | 	} endwhile | 
 | } | 
 |  | 
 | // decode_header reads either "GIF87a" or "GIF89a". | 
 | // | 
 | // See the spec section 17 "Header" on page 7. | 
 | pri func decoder.decode_header?(src: base.io_reader) { | 
 | 	var c : array[6] base.u8 | 
 | 	var i : base.u32 | 
 |  | 
 | 	while i < 6 { | 
 | 		c[i] = args.src.read_u8?() | 
 | 		i += 1 | 
 | 	} endwhile | 
 | 	if (c[0] <> 'G') or (c[1] <> 'I') or (c[2] <> 'F') or (c[3] <> '8') or | 
 | 		((c[4] <> '7') and (c[4] <> '9')) or (c[5] <> 'a') { | 
 | 		return "#bad header" | 
 | 	} | 
 | } | 
 |  | 
 | // decode_lsd reads the Logical Screen Descriptor. | 
 | // | 
 | // See the spec section 18 "Logical Screen Descriptor" on page 8. | 
 | pri func decoder.decode_lsd?(src: base.io_reader) { | 
 | 	var flags                  : base.u8 | 
 | 	var background_color_index : base.u8 | 
 | 	var num_palette_entries    : base.u32[..= 256] | 
 | 	var i                      : base.u32 | 
 | 	var j                      : base.u32[..= 1020] | 
 | 	var argb                   : base.u32 | 
 |  | 
 | 	this.width = args.src.read_u16le_as_u32?() | 
 | 	this.height = args.src.read_u16le_as_u32?() | 
 | 	flags = args.src.read_u8?() | 
 | 	background_color_index = args.src.read_u8?() | 
 | 	// Ignore the Pixel Aspect Ratio byte. | 
 | 	args.src.skip32?(n: 1) | 
 |  | 
 | 	// Read the optional Global Color Table. | 
 | 	i = 0 | 
 | 	this.has_global_palette = (flags & 0x80) <> 0 | 
 | 	if this.has_global_palette { | 
 | 		num_palette_entries = (1 as base.u32) << (1 + (flags & 0x07)) | 
 | 		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.palettes[0][(4 * i) + 0] = ((argb >> 0) & 0xFF) as base.u8 | 
 | 			this.palettes[0][(4 * i) + 1] = ((argb >> 8) & 0xFF) as base.u8 | 
 | 			this.palettes[0][(4 * i) + 2] = ((argb >> 16) & 0xFF) as base.u8 | 
 | 			this.palettes[0][(4 * i) + 3] = ((argb >> 24) & 0xFF) as base.u8 | 
 | 			i += 1 | 
 | 		} endwhile | 
 |  | 
 | 		if this.quirks[QUIRK_HONOR_BACKGROUND_COLOR - QUIRKS_BASE] { | 
 | 			if (background_color_index <> 0) and | 
 | 				((background_color_index as base.u32) < num_palette_entries) { | 
 |  | 
 | 				j = 4 * (background_color_index as base.u32) | 
 | 				this.background_color_u32_argb_premul = | 
 | 					((this.palettes[0][j + 0] as base.u32) << 0) | | 
 | 					((this.palettes[0][j + 1] as base.u32) << 8) | | 
 | 					((this.palettes[0][j + 2] as base.u32) << 16) | | 
 | 					((this.palettes[0][j + 3] as base.u32) << 24) | 
 | 			} else { | 
 | 				// The background color is either opaque black or transparent | 
 | 				// black. We set it to an arbitrary nonsense value (77) for | 
 | 				// now, and set it to its real value later, once we know | 
 | 				// whether the first frame is opaque (the ffio value). | 
 | 				this.background_color_u32_argb_premul = 77 | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	// Set the remaining palette entries to opaque black. | 
 | 	while i < 256 { | 
 | 		this.palettes[0][(4 * i) + 0] = 0x00 | 
 | 		this.palettes[0][(4 * i) + 1] = 0x00 | 
 | 		this.palettes[0][(4 * i) + 2] = 0x00 | 
 | 		this.palettes[0][(4 * i) + 3] = 0xFF | 
 | 		i += 1 | 
 | 	} endwhile | 
 | } | 
 |  | 
 | // decode_extension reads an extension. The Extension Introducer byte has | 
 | // already been read. | 
 | // | 
 | // See the spec: | 
 | //  - section 23 "Graphic Control Extension" on page 15. | 
 | //  - section 24 "Comment Extension" on page 17. | 
 | //  - section 25 "Plain Text Extension" on page 18. | 
 | //  - section 26 "Application Extension" on page 21. | 
 | pri func decoder.decode_extension?(src: base.io_reader) { | 
 | 	var label : base.u8 | 
 |  | 
 | 	label = args.src.read_u8?() | 
 | 	if label == 0xF9 {  // The spec calls 0xF9 the "Graphic Control Label". | 
 | 		this.decode_gc?(src: args.src) | 
 | 		return ok | 
 | 	} else if label == 0xFF {  // The spec calls 0xFF the "Application Extension Label". | 
 | 		this.decode_ae?(src: args.src) | 
 | 		return ok | 
 | 	} | 
 | 	// We skip over all other extensions, including 0x01 "Plain Text Label" and | 
 | 	// 0xFE "Comment Label". | 
 | 	this.skip_blocks?(src: args.src) | 
 | } | 
 |  | 
 | pri func decoder.skip_blocks?(src: base.io_reader) { | 
 | 	var block_size : base.u8 | 
 |  | 
 | 	while true { | 
 | 		block_size = args.src.read_u8?() | 
 | 		if block_size == 0 { | 
 | 			return ok | 
 | 		} | 
 | 		args.src.skip32?(n: block_size as base.u32) | 
 | 	} endwhile | 
 | } | 
 |  | 
 | // decode_ae reads an Application Extension. | 
 | pri func decoder.decode_ae?(src: base.io_reader) { | 
 | 	var c           : base.u8 | 
 | 	var block_size  : base.u8 | 
 | 	var is_animexts : base.bool | 
 | 	var is_netscape : base.bool | 
 | 	var is_iccp     : base.bool | 
 | 	var is_xmp      : base.bool | 
 |  | 
 | 	while.goto_done true {{ | 
 | 	if this.metadata_fourcc <> 0 { | 
 | 		return base."@metadata reported" | 
 | 	} | 
 |  | 
 | 	block_size = args.src.read_u8?() | 
 | 	if block_size == 0 { | 
 | 		return ok | 
 | 	} | 
 |  | 
 | 	// Look only for an 11 byte "ANIMEXTS1.0", "NETSCAPE2.0" or other | 
 | 	// extension, as per: | 
 | 	//  - http://www.vurdalakov.net/misc/gif/animexts-looping-application-extension | 
 | 	//  - http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension | 
 | 	// | 
 | 	// Other extensions include XMP metadata. | 
 | 	if block_size <> 11 { | 
 | 		args.src.skip32?(n: block_size as base.u32) | 
 | 		break.goto_done | 
 | 	} | 
 | 	is_animexts = true | 
 | 	is_netscape = true | 
 | 	is_iccp = true | 
 | 	is_xmp = true | 
 | 	block_size = 0  // Re-purpose the block_size variable as a counter. | 
 | 	while block_size < 11 { | 
 | 		c = args.src.read_u8?() | 
 | 		is_animexts = is_animexts and (c == ANIMEXTS1DOT0[block_size]) | 
 | 		is_netscape = is_netscape and (c == NETSCAPE2DOT0[block_size]) | 
 | 		is_iccp = is_iccp and (c == ICCRGBG1012[block_size]) | 
 | 		is_xmp = is_xmp and (c == XMPDATAXMP[block_size]) | 
 | 		block_size += 1 | 
 | 	} endwhile | 
 |  | 
 | 	if is_animexts or is_netscape { | 
 | 		// Those 11 bytes should be followed by 0x03, 0x01 and then the loop | 
 | 		// count. | 
 | 		block_size = args.src.read_u8?() | 
 | 		if block_size <> 3 { | 
 | 			args.src.skip32?(n: block_size as base.u32) | 
 | 			break.goto_done | 
 | 		} | 
 | 		c = args.src.read_u8?() | 
 | 		if c <> 0x01 { | 
 | 			args.src.skip32?(n: 2) | 
 | 			break.goto_done | 
 | 		} | 
 | 		this.num_loops = args.src.read_u16le_as_u32?() | 
 | 		this.seen_num_loops = true | 
 |  | 
 | 		// A loop count of N, in the wire format, actually means "repeat N | 
 | 		// times after the first play", if N is positive. A zero N means to | 
 | 		// loop forever. Playing the frames exactly once is denoted by the | 
 | 		// *absence* of this NETSCAPE2.0 application extension. | 
 | 		// | 
 | 		// For example, if there are four frames: A, B, C, D, and N is 2, then | 
 | 		// each frame is actually played N+1 or 3 times: ABCDABCDABCD. | 
 | 		// | 
 | 		// Thus, we increment N if it is positive. The comparison against | 
 | 		// 0xFFFF will never fail, but is necessary for the overflow checker. | 
 | 		if (0 < this.num_loops) and (this.num_loops <= 0xFFFF) { | 
 | 			this.num_loops += 1 | 
 | 		} | 
 |  | 
 | 	} else if this.ignore_metadata { | 
 | 		// No-op. | 
 |  | 
 | 	} else if is_iccp and this.report_metadata_iccp { | 
 | 		this.metadata_fourcc = 'ICCP'be | 
 | 		this.metadata_io_position = args.src.position() | 
 | 		this.call_sequence = 1 | 
 | 		return base."@metadata reported" | 
 |  | 
 | 	} else if is_xmp and this.report_metadata_xmp { | 
 | 		this.metadata_fourcc = 'XMP 'be | 
 | 		this.metadata_io_position = args.src.position() | 
 | 		this.call_sequence = 1 | 
 | 		return base."@metadata reported" | 
 | 	} | 
 |  | 
 | 	break.goto_done | 
 | 	}} endwhile.goto_done | 
 |  | 
 | 	this.skip_blocks?(src: args.src) | 
 | } | 
 |  | 
 | // decode_gc reads a Graphic Control. | 
 | pri func decoder.decode_gc?(src: base.io_reader) { | 
 | 	var c                        : base.u8 | 
 | 	var flags                    : base.u8 | 
 | 	var gc_duration_centiseconds : base.u16 | 
 |  | 
 | 	c = args.src.read_u8?() | 
 | 	if c <> 4 { | 
 | 		return "#bad graphic control" | 
 | 	} | 
 |  | 
 | 	flags = args.src.read_u8?() | 
 | 	this.gc_has_transparent_index = (flags & 0x01) <> 0 | 
 |  | 
 | 	// Convert the disposal method from GIF's wire format to Wuffs constants. | 
 | 	// | 
 | 	// The GIF spec discusses the 3-bit flag value being 0, 1, 2 or 3. Values | 
 | 	// in the range [4 ..= 7] are "to be defined". In practice, some encoders also | 
 | 	// use 4 for "restore previous". See | 
 | 	// https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.cc?rcl=5161173c43324da2b13e1aa45bbe69901daa1279&l=625 | 
 | 	// | 
 | 	// TODO: named constants instead of assigning 1 for | 
 | 	// WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND, etc. | 
 | 	flags = (flags >> 2) & 0x07 | 
 | 	if flags == 2 { | 
 | 		this.gc_disposal = 1  // 1 is WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND | 
 | 	} else if (flags == 3) or (flags == 4) { | 
 | 		this.gc_disposal = 2  // 2 is WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_PREVIOUS | 
 | 	} else { | 
 | 		this.gc_disposal = 0 | 
 | 	} | 
 |  | 
 | 	// There are 7_056000 flicks per centisecond. | 
 | 	gc_duration_centiseconds = args.src.read_u16le?() | 
 | 	this.gc_duration = (gc_duration_centiseconds as base.u64) * 7_056000 | 
 | 	this.gc_transparent_index = args.src.read_u8?() | 
 |  | 
 | 	c = args.src.read_u8?() | 
 | 	if c <> 0 { | 
 | 		return "#bad graphic control" | 
 | 	} | 
 | } | 
 |  | 
 | // decode_id_partX reads an Image Descriptor. The Image Separator byte has | 
 | // already been read. | 
 | // | 
 | // See the spec section 20 "Image Descriptor" on page 11. | 
 | // | 
 | // The code is split into three parts (part0, part 1 and part12 because | 
 | // determining the overall image's width and height also requires decoding the | 
 | // first frame's bounds (part0), but doesn't require decoding the first frame's | 
 | // pixels (the other two parts). Decoding the actual pixels is split into two | 
 | // (part1 and part2) not out of necessity, just for the general programming | 
 | // principle that smaller functions are easier to understand. | 
 |  | 
 | pri func decoder.decode_id_part0?(src: base.io_reader) { | 
 | 	this.frame_rect_x0 = args.src.read_u16le_as_u32?() | 
 | 	this.frame_rect_y0 = args.src.read_u16le_as_u32?() | 
 | 	this.frame_rect_x1 = args.src.read_u16le_as_u32?() | 
 | 	this.frame_rect_x1 ~mod+= this.frame_rect_x0 | 
 | 	this.frame_rect_y1 = args.src.read_u16le_as_u32?() | 
 | 	this.frame_rect_y1 ~mod+= this.frame_rect_y0 | 
 |  | 
 | 	//#WHEN PREPROC400 | 
 | 	this.dst_x = this.frame_rect_x0 | 
 | 	this.dst_y = this.frame_rect_y0 | 
 | 	//#DONE PREPROC400 | 
 |  | 
 | 	// Set the image's overall width and height to be the maximum of the | 
 | 	// nominal image width and height (given in the Logical Screen Descriptor) | 
 | 	// and the bottom right extent of the first frame. See | 
 | 	// test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt for | 
 | 	// more discussion. | 
 | 	if (this.call_sequence == 0) and (not this.quirks[QUIRK_IMAGE_BOUNDS_ARE_STRICT - QUIRKS_BASE]) { | 
 | 		this.width = this.width.max(a: this.frame_rect_x1) | 
 | 		this.height = this.height.max(a: this.frame_rect_y1) | 
 | 	} | 
 | } | 
 |  | 
 | //#WHEN PREPROC900 | 
 | pri func decoder.decode_id_part1?(dst: ptr base.pixel_buffer, src: base.io_reader, blend: base.pixel_blend) { | 
 | 	var flags               : base.u8 | 
 | 	var which_palette       : base.u8[..= 1] | 
 | 	var num_palette_entries : base.u32[..= 256] | 
 | 	var i                   : base.u32 | 
 | 	var argb                : base.u32 | 
 | 	var dst_palette         : slice base.u8 | 
 | 	var status              : base.status | 
 | 	var lw                  : base.u8 | 
 |  | 
 | 	flags = args.src.read_u8?() | 
 | 	if (flags & 0x40) <> 0 { | 
 | 		this.interlace = 4 | 
 | 	} else { | 
 | 		this.interlace = 0 | 
 | 	} | 
 |  | 
 | 	// Read the optional Local Color Table. | 
 | 	which_palette = 1 | 
 | 	if (flags & 0x80) <> 0 { | 
 | 		num_palette_entries = (1 as base.u32) << (1 + (flags & 0x07)) | 
 | 		i = 0 | 
 | 		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.palettes[1][(4 * i) + 0] = ((argb >> 0) & 0xFF) as base.u8 | 
 | 			this.palettes[1][(4 * i) + 1] = ((argb >> 8) & 0xFF) as base.u8 | 
 | 			this.palettes[1][(4 * i) + 2] = ((argb >> 16) & 0xFF) as base.u8 | 
 | 			this.palettes[1][(4 * i) + 3] = ((argb >> 24) & 0xFF) as base.u8 | 
 | 			i += 1 | 
 | 		} endwhile | 
 | 		// Set the remaining palette entries to opaque black. | 
 | 		while i < 256 { | 
 | 			this.palettes[1][(4 * i) + 0] = 0x00 | 
 | 			this.palettes[1][(4 * i) + 1] = 0x00 | 
 | 			this.palettes[1][(4 * i) + 2] = 0x00 | 
 | 			this.palettes[1][(4 * i) + 3] = 0xFF | 
 | 			i += 1 | 
 | 		} endwhile | 
 | 	} else if this.quirks[QUIRK_REJECT_EMPTY_PALETTE - QUIRKS_BASE] and (not this.has_global_palette) { | 
 | 		return "#bad palette" | 
 | 	} else if this.gc_has_transparent_index { | 
 | 		this.palettes[1][..].copy_from_slice!(s: this.palettes[0][..]) | 
 | 	} else { | 
 | 		which_palette = 0 | 
 | 	} | 
 |  | 
 | 	// Set the gc_transparent_index palette entry to transparent black. | 
 | 	if this.gc_has_transparent_index { | 
 | 		this.palettes[1][(4 * (this.gc_transparent_index as base.u32)) + 0] = 0x00 | 
 | 		this.palettes[1][(4 * (this.gc_transparent_index as base.u32)) + 1] = 0x00 | 
 | 		this.palettes[1][(4 * (this.gc_transparent_index as base.u32)) + 2] = 0x00 | 
 | 		this.palettes[1][(4 * (this.gc_transparent_index as base.u32)) + 3] = 0x00 | 
 | 	} | 
 |  | 
 | 	dst_palette = args.dst.palette() | 
 | 	if dst_palette.length() == 0 { | 
 | 		dst_palette = this.dst_palette[..] | 
 | 	} | 
 |  | 
 | 	// TODO: a Wuffs (not just C) name for the | 
 | 	// WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY magic pixfmt constant. | 
 | 	status = this.swizzler.prepare!( | 
 | 		dst_pixfmt: args.dst.pixel_format(), | 
 | 		dst_palette: dst_palette, | 
 | 		src_pixfmt: this.util.make_pixel_format(repr: 0x4704_0008), | 
 | 		src_palette: this.palettes[which_palette][..], | 
 | 		blend: args.blend) | 
 | 	if not status.is_ok() { | 
 | 		return status | 
 | 	} | 
 |  | 
 | 	// Other GIF implementations accept GIF files that aren't completely spec | 
 | 	// compliant. For example, the test/data/gifplayer-muybridge.gif file | 
 | 	// (created by the gifsicle program) is accepted by other GIF decoders. | 
 | 	// However, in that file, frame #61's embedded LZW data is truncated, | 
 | 	// finishing with only 8 of the 9 bits required of the LZW end code. The | 
 | 	// end code itself, 0x81, is representable in only 8 bits, but the number | 
 | 	// of bits for the decoder to read has ticked over from 8 to 9 just before | 
 | 	// that end code is encountered. | 
 | 	// | 
 | 	// To accommodate such malformed GIFs, we detect when the previous frame's | 
 | 	// LZW decoding ended abruptly. The previous LZW decode 'works', in that it | 
 | 	// decodes as much pixel data as is available, but without seeing that end | 
 | 	// code (i.e. returning the "ok" status code), the LZW decoder is stuck in | 
 | 	// a coroutine-in-progress lzw_decoder.decode call, still waiting for that | 
 | 	// end code. To cancel that coroutine, we reset the LZW decoder. | 
 | 	if this.previous_lzw_decode_ended_abruptly { | 
 | 		this.lzw.reset!() | 
 | 	} | 
 |  | 
 | 	// Process the LZW literal width. The spec says that "images which have one | 
 | 	// color bit must be indicated as having a code size [i.e. literal width] | 
 | 	// of 2", but in practice, some encoders use a literal width of 1 or 0. | 
 | 	lw = args.src.read_u8?() | 
 | 	if lw > 8 { | 
 | 		return "#bad literal width" | 
 | 	} | 
 | 	this.lzw.set_literal_width!(lw: lw as base.u32) | 
 |  | 
 | 	this.previous_lzw_decode_ended_abruptly = true | 
 | } | 
 |  | 
 | pri func decoder.decode_id_part2?(dst: ptr base.pixel_buffer, src: base.io_reader, workbuf: slice base.u8) { | 
 | 	var block_size      : base.u64[..= 255] | 
 | 	var need_block_size : base.bool | 
 | 	var n_compressed    : base.u64 | 
 | 	var compressed      : slice base.u8 | 
 | 	var r               : base.io_reader | 
 | 	var mark            : base.u64 | 
 | 	var lzw_status      : base.status | 
 | 	var copy_status     : base.status | 
 | 	var uncompressed    : slice base.u8 | 
 |  | 
 | 	need_block_size = true | 
 | 	while.outer true { | 
 | 		if need_block_size { | 
 | 			need_block_size = false | 
 | 			block_size = args.src.read_u8_as_u64?() | 
 | 		} | 
 | 		if block_size == 0 { | 
 | 			break.outer | 
 | 		} | 
 | 		while args.src.available() == 0 { | 
 | 			yield? base."$short read" | 
 | 		} endwhile | 
 |  | 
 | 		if this.compressed_ri == this.compressed_wi { | 
 | 			this.compressed_ri = 0 | 
 | 			this.compressed_wi = 0 | 
 | 		} | 
 | 		while this.compressed_wi <= (4096 - 255) { | 
 | 			n_compressed = block_size.min(a: args.src.available()) | 
 | 			if n_compressed <= 0 { | 
 | 				break | 
 | 			} | 
 | 			compressed = args.src.take!(n: n_compressed) | 
 | 			this.compressed[this.compressed_wi ..].copy_from_slice!(s: compressed) | 
 | 			this.compressed_wi ~sat+= n_compressed | 
 | 			block_size ~sat-= n_compressed | 
 | 			if block_size > 0 { | 
 | 				break | 
 | 			} | 
 | 			if args.src.available() <= 0 { | 
 | 				need_block_size = true | 
 | 				break | 
 | 			} | 
 | 			block_size = args.src.peek_u8_as_u64() | 
 | 			args.src.skip32_fast!(actual: 1, worst_case: 1) | 
 | 		} endwhile | 
 |  | 
 | 		while.inner true { | 
 | 			if (this.compressed_ri > this.compressed_wi) or (this.compressed_wi > 4096) { | 
 | 				return "#internal error: inconsistent ri/wi" | 
 | 			} | 
 | 			io_bind (io: r, data: this.compressed[this.compressed_ri .. this.compressed_wi]) { | 
 | 				mark = r.mark() | 
 | 				lzw_status =? this.lzw.transform_io?( | 
 | 					dst: this.util.empty_io_writer(), src: r, workbuf: this.util.empty_slice_u8()) | 
 | 				this.compressed_ri ~sat+= r.count_since(mark: mark) | 
 | 			} | 
 |  | 
 | 			uncompressed = this.lzw.flush!() | 
 | 			if uncompressed.length() > 0 { | 
 | 				copy_status = this.copy_to_image_buffer!(pb: args.dst, src: uncompressed) | 
 | 				if copy_status.is_error() { | 
 | 					return copy_status | 
 | 				} | 
 | 			} | 
 |  | 
 | 			if lzw_status.is_ok() { | 
 | 				this.previous_lzw_decode_ended_abruptly = false | 
 |  | 
 | 				// Skip any trailing blocks. | 
 | 				if need_block_size or (block_size > 0) { | 
 | 					args.src.skip32?(n: block_size as base.u32) | 
 | 					this.skip_blocks?(src: args.src) | 
 | 				} | 
 |  | 
 | 				break.outer | 
 | 			} else if lzw_status == base."$short read" { | 
 | 				continue.outer | 
 | 			} else if lzw_status == base."$short write" { | 
 | 				continue.inner | 
 | 			} | 
 | 			return lzw_status | 
 | 		} endwhile.inner | 
 | 	} endwhile.outer | 
 |  | 
 | 	this.compressed_ri = 0 | 
 | 	this.compressed_wi = 0 | 
 |  | 
 | 	if (this.dst_y < this.frame_rect_y1) and | 
 | 		(this.frame_rect_x0 <> this.frame_rect_x1) and | 
 | 		(this.frame_rect_y0 <> this.frame_rect_y1) { | 
 | 		return base."#not enough data" | 
 | 	} | 
 | } | 
 |  | 
 | pri func decoder.copy_to_image_buffer!(pb: ptr base.pixel_buffer, src: slice base.u8) base.status { | 
 | 	// TODO: don't assume an interleaved pixel format. | 
 | 	var dst             : slice base.u8 | 
 | 	var src             : slice base.u8 | 
 | 	var width_in_bytes  : base.u64 | 
 | 	var n               : base.u64 | 
 | 	var src_ri          : base.u64 | 
 | 	var pixfmt          : base.pixel_format | 
 | 	var bytes_per_pixel : base.u32[..= 32] | 
 | 	var bits_per_pixel  : base.u32[..= 256] | 
 | 	var tab             : table base.u8 | 
 | 	var i               : base.u64 | 
 | 	var j               : base.u64 | 
 | 	var replicate_y0    : base.u32 | 
 | 	var replicate_y1    : base.u32 | 
 | 	var replicate_dst   : slice base.u8 | 
 | 	var replicate_src   : slice base.u8 | 
 |  | 
 | 	// TODO: the pixfmt variable shouldn't be necessary. We should be able to | 
 | 	// chain the two calls: "args.pb.pixel_format().bits_per_pixel()". | 
 | 	pixfmt = args.pb.pixel_format() | 
 | 	bits_per_pixel = pixfmt.bits_per_pixel() | 
 | 	if (bits_per_pixel & 7) <> 0 { | 
 | 		return base."#unsupported option" | 
 | 	} | 
 | 	bytes_per_pixel = bits_per_pixel >> 3 | 
 |  | 
 | 	width_in_bytes = (this.width as base.u64) * (bytes_per_pixel as base.u64) | 
 | 	tab = args.pb.plane(p: 0) | 
 | 	while src_ri < args.src.length() { | 
 | 		src = args.src[src_ri ..] | 
 |  | 
 | 		if this.dst_y >= this.frame_rect_y1 { | 
 | 			if this.quirks[QUIRK_IGNORE_TOO_MUCH_PIXEL_DATA - QUIRKS_BASE] { | 
 | 				return ok | 
 | 			} | 
 | 			return base."#too much data" | 
 | 		} | 
 |  | 
 | 		// First, copy from src to that part of the frame rect that is inside | 
 | 		// args.pb's bounds (clipped to the image bounds). | 
 |  | 
 | 		dst = tab.row(y: this.dst_y) | 
 | 		if this.dst_y >= this.height { | 
 | 			dst = dst[.. 0] | 
 | 		} else if width_in_bytes < dst.length() { | 
 | 			dst = dst[.. width_in_bytes] | 
 | 		} | 
 |  | 
 | 		i = (this.dst_x as base.u64) * (bytes_per_pixel as base.u64) | 
 | 		if i < dst.length() { | 
 | 			j = (this.frame_rect_x1 as base.u64) * (bytes_per_pixel as base.u64) | 
 | 			if (i <= j) and (j <= dst.length()) { | 
 | 				dst = dst[i .. j] | 
 | 			} else { | 
 | 				dst = dst[i ..] | 
 | 			} | 
 | 			n = this.swizzler.swizzle_interleaved!( | 
 | 				dst: dst, dst_palette: this.dst_palette[..], src: src) | 
 |  | 
 | 			src_ri ~sat+= n | 
 | 			this.dst_x ~sat+= (n & 0xFFFF_FFFF) as base.u32 | 
 |  | 
 | 			this.dirty_max_excl_y = this.dirty_max_excl_y.max(a: this.dst_y ~sat+ 1) | 
 | 		} | 
 |  | 
 | 		if this.frame_rect_x1 <= this.dst_x { | 
 | 			this.dst_x = this.frame_rect_x0 | 
 | 			if this.interlace == 0 { | 
 | 				this.dst_y ~sat+= 1 | 
 | 				continue | 
 | 			} | 
 |  | 
 | 			// For the first frame of an interlaced, non-transparent GIF, | 
 | 			// replicate the early passes' rows. For example, when such an | 
 | 			// image is downloaded over a slow network, this produces a richer | 
 | 			// intermediate image while waiting for the complete image. | 
 | 			// | 
 | 			// Some other GIF implementations call this progressive display or | 
 | 			// a "Haeberli inspired" technique. | 
 | 			if (this.num_decoded_frames_value == 0) and | 
 | 				(not this.gc_has_transparent_index) and | 
 | 				(this.interlace > 1) { | 
 |  | 
 | 				replicate_src = tab.row(y: this.dst_y) | 
 | 				replicate_y0 = this.dst_y ~sat+ 1 | 
 | 				replicate_y1 = this.dst_y ~sat+ (INTERLACE_COUNT[this.interlace] as base.u32) | 
 | 				replicate_y1 = replicate_y1.min(a: this.frame_rect_y1) | 
 | 				while replicate_y0 < replicate_y1 { | 
 | 					assert replicate_y0 < 0xFFFF_FFFF via "a < b: a < c; c <= b"(c: replicate_y1) | 
 | 					replicate_dst = tab.row(y: replicate_y0) | 
 | 					replicate_dst.copy_from_slice!(s: replicate_src) | 
 | 					replicate_y0 += 1 | 
 | 				} endwhile | 
 | 				this.dirty_max_excl_y = this.dirty_max_excl_y.max(a: replicate_y1) | 
 | 			} | 
 |  | 
 | 			this.dst_y ~sat+= INTERLACE_DELTA[this.interlace] as base.u32 | 
 | 			while (this.interlace > 0) and (this.dst_y >= this.frame_rect_y1) { | 
 | 				this.interlace -= 1 | 
 | 				this.dst_y = this.frame_rect_y0 ~sat+ INTERLACE_START[this.interlace] | 
 | 			} endwhile | 
 | 			continue | 
 | 		} | 
 |  | 
 | 		if args.src.length() == src_ri { | 
 | 			break | 
 | 		} else if args.src.length() < src_ri { | 
 | 			return "#internal error: inconsistent ri/wi" | 
 | 		} | 
 |  | 
 | 		// Second, skip over src for that part of the frame rect that is | 
 | 		// outside args.pb's bounds. This second step should be infrequent. | 
 |  | 
 | 		// Set n to the number of pixels (i.e. the number of bytes) to skip. | 
 | 		n = (this.frame_rect_x1 - this.dst_x) as base.u64 | 
 | 		n = n.min(a: args.src.length() - src_ri) | 
 |  | 
 | 		src_ri ~sat+= n | 
 | 		this.dst_x ~sat+= (n & 0xFFFF_FFFF) as base.u32 | 
 |  | 
 | 		if this.frame_rect_x1 <= this.dst_x { | 
 | 			this.dst_x = this.frame_rect_x0 | 
 | 			this.dst_y ~sat+= INTERLACE_DELTA[this.interlace] as base.u32 | 
 | 			while (this.interlace > 0) and (this.dst_y >= this.frame_rect_y1) { | 
 | 				this.interlace -= 1 | 
 | 				this.dst_y = this.frame_rect_y0 ~sat+ INTERLACE_START[this.interlace] | 
 | 			} endwhile | 
 | 			continue | 
 | 		} | 
 |  | 
 | 		if src_ri <> args.src.length() { | 
 | 			return "#internal error: inconsistent ri/wi" | 
 | 		} | 
 | 		break | 
 | 	} endwhile | 
 | 	return ok | 
 | } | 
 | //#DONE PREPROC900 |