Image Decoders Call Sequence

As discussed in the Image Decoders document, here's the canonical sequence of API calls to decode an animated image (with N frames):

  1. DIC (decode_image_config)
  2. DFC (decode_frame_config) #0
  3. DF (decode_frame) #0
  4. DFC (decode_frame_config) #1
  5. DF (decode_frame) #1
  6. ...
  7. DFC (decode_frame_config) #N-1
  8. DF (decode_frame) #N-1
  9. DFC (decode_frame_config) #N returning an "@end of data" status value.

The final DFC call returns "@end of data" even if the animation loops.

Decoding a still (non-animated) image is equivalent to N being 1.

These three methods (DIC, DFC, DF) are all coroutines and may return I/O related suspension status values. Those coroutines should be resumed by calling the same method again, before moving to the next one in the sequence.

Encountering an error (such as the input not being valid in that image file format) is sticky (any further calls will return that same error).

Implicit Calls

These three methods (DIC, DFC, DF) are all also optional in some sense. Referring to the ordered list at the top of this page, any decode_etc call jumps forward to the next matching method in that canonical sequence, implicitly calling any jumped over method with the same io_reader but null values for other arguments.

For example, starting with a freshly initialized image decoder, if it's a first party image and you already know its width, height and pixel format (so you can prepare a perfectly sized pixel buffer without having to call DIC or DFC), it is valid to call DF, going straight to the DF #0 stage. This implicitly calls DIC and DFC #0.

Even if you didn't know the width, height and pixel format, it is still valid to call DF with a smaller-dimensioned pixel buffer (which just means the decoded image will be clipped) or with a different pixel format (the decoder will convert).

After that, it is valid to call DFC, going to the DFC #1 stage (or get “@end of data” for still images), which would not make any implicit calls.

After that, it is valid to call DFC, going to the DFC #2 stage, implicitly calling DF #1.

And so on.

For example, starting at the DF #N-1 stage, it is valid to call DF again, which will return “@end of data” because it first implicitly calls DF #N, which returns “@end of data”.

Implicitly calling DF (skipping over the pixel data) is typically much faster than explicitly calling DF (decompressing the pixel data). For example, starting with a freshly initialized image decoder, if you just want to count the number of animation frames, without needing each frame's decoded pixels, just call DFC (and not DF) in a loop until “#end of data”.

State Machine

It‘s not part of Wuffs’ public API, but Wuffs' standard library's image decoder implementations all have a private call_sequence field to track this state machine. Broadly speaking, an image decoder starts in an initial state (labeled 0x00). The DIC call moves to state 0x20. Subsequent DFC and DF calls bounce between states 0x40 and 0x20 until the last DFC moves the final state 0x60. These are the bold-labeled states in the left hand column of the diagram below:

Image Decoders Call SequenceDiagram

The original Google Docs document for that diagram is here.

The italic-labeled states (whose numeric labels all have the 0x10 bit set) in the right hand column are side-track states when metadata is encountered, and are discussed below. If the caller never opts in to seeing metadata (opting in by calling set_report_metadata) then the decoder never enters these (base_value | 0x10) states.

The 0x08 bit is used when calling restart_frame, discussed below. The low three bits are reserved but currently unused.

The primary purposes of the call_sequence field is track when we've reached “@end of data” and to reject method calls that occur out of sequence. Such method calls result in the "#bad call sequence" error status value. For example, a DIC call is illegal after a DIC, DFC or DF call. Similarly, a TMM (tell_me_more) call is illegal unless the decoder is in a right hand column state.

Restarts

Image decoding typically runs sequentially: the first frame (#0) is decoded, then the second (#1), then the third (#2), etc. However, decoding can sometimes happen in random access instead of sequential order. Playing a looping animation may require rewinding back to the first frame. Replaying an animation may be able to re-use previousy cached frames at times, so a decoder may be asked to jump ahead. While Wuffs is designed for sequential decoding (as it does not assume that its input byte stream is rewindable or seekable), using Wuffs to implement other graphics APIs can involve random access to animation frames.

The RF restart_frame method supports these usage patterns, setting up the decoder so that it‘s just about to decode the I’th frame configuration (when the io_reader is also placed at io_position previously returned by the I'th DFC).

The RF method can only be called when the image configuration has already been decoded. Equivalently, only when the call_sequence is at least 0x20.