blob: c43493008df142cf85c131208199c74f708621d2 [file] [log] [blame]
// Copyright 2019 Google LLC
// 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
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
// -----------------------------------------------------------------------------
// Main decoding functions for WP2 images.
// Author: Skal (
#ifndef WP2_WP2_DECODE_H_
#define WP2_WP2_DECODE_H_
#include <string>
#include "./base.h"
#include "./debug.h"
#include "./format_constants.h"
namespace WP2 {
// Features gathered from the bitstream
struct BitstreamFeatures {
uint32_t raw_width; // Width in pixels, as read from the bitstream.
uint32_t raw_height; // Height in pixels, as read from the bitstream.
Orientation orientation; // Rotation or mirroring applied during decoding.
uint32_t width; // Width after the orientation is applied.
uint32_t height; // Height after the orientation is applied.
uint32_t tile_width; // Width of tiles in pixels.
uint32_t tile_height; // Height of tiles in pixels.
uint32_t rgb_bit_depth; // RGB depth in bits. 8 or 10. Alpha is always 8.
bool is_opaque; // True if no alpha. False has no guarantee.
bool is_animation; // True if the bitstream is an animation.
bool has_preview; // True if the bitstream has a preview image
// (irrespective of the preview color).
RGB12b preview_color; // Average color in RGB444 format.
bool has_icc; // Indicates the presence of ICC information.
bool has_trailing_data; // Trailing data is present (XMP, EXIF, ...).
TransferFunction transfer_function;
bool loop_forever; // Play once if false, forever if true (anim only).
Argb38b background_color; // Color to dispose the canvas with (anim only).
size_t header_size; // Byte size of header without preview and ICC chunks.
// Extracts features from the bitstream. Returns:
// - WP2_STATUS_OK when the features are successfully retrieved.
// - WP2_STATUS_NOT_ENOUGH_DATA when more data is needed to retrieve the
// features from the header.
// - Any other error status in case of parsing failure.
WP2_NO_DISCARD WP2Status Read(const uint8_t* data, size_t data_size) {
return InitInternal(WP2_ABI_VERSION, data, data_size);
// Internal, version-checked, entry point.
WP2Status InitInternal(int, const uint8_t*, size_t);
// For animations.
struct FrameFeatures {
uint32_t duration_ms = 0; // Milliseconds during which this frame is visible.
Rectangle window; // The canvas area modified by this frame
// (BitstreamFeatures::orientation included).
bool is_last = false; // If true, there is no frame after this one.
// Index of first frame needed to decode this frame. If the value is the same
// as this frame's index, it means this frame is independent of any other one.
uint32_t last_dispose_frame_index = 0;
// Main decoding call
struct DecoderConfig {
// 0 to disable multi-threading. Any other value represents the maximum number
// of simultaneous extra threads (can be as large as 2^31 - 1).
uint32_t thread_level = 0;
// If enabled, the filters smooth lossy compression artifacts out.
// Not applying them can reduce decoding duration for time-sensitive cases.
bool enable_deblocking_filter = true; // Reduces blocky artifacts.
bool enable_directional_filter = false; // Reduces noise and ringing.
bool enable_restoration_filter = false; // Denoises and enhances edges.
bool enable_alpha_filter = true; // Edge-preserving blur for alpha only.
// Amplitude factor for the grain generation [0(=off) .. 100]
uint8_t grain_amplitude = 0;
// Output pixels earlier or in bigger batches. Only used by Decoder classes.
enum class IncrementalMode {
// TODO(yguyon): Add a FULL_FRAME mode (no partial frame decoding)
FULL_TILE = 0, // Decode each frame tile only when fully available.
PARTIAL_TILE_CONTEXT, // Decode frame tiles even if partially available,
// with a context switch or fibers, if compiled.
// TODO(yguyon): Add a PARTIAL_TILE_THREAD mode (suspend a thread instead)
IncrementalMode incremental_mode = IncrementalMode::PARTIAL_TILE_CONTEXT;
// Inherit ProgressHook and reference the instance here to get notified of
// progress and/or to abort decoding.
ProgressHook* progress_hook = nullptr;
DecoderInfo* info = nullptr; // if not null, report internal stats and info
static const DecoderConfig kDefault;
// This function decodes the bitstream into 'output_buffer' as Argb samples.
// If it's an animation, it will only decode the first frame.
// TODO(maryla): add support for decoding first frame with preframes.
// Returns decoding status (which should be WP2_STATUS_OK if the decoding was
// successful). Note that 'output_buffer' cannot be null.
// If 'output_buffer' already fits the image exactly, it won't be reallocated.
// Otherwise, if it doesn't point to external memory, it will be (re)allocated
// to the exact width and height of the image.
Decode(const uint8_t* data, size_t data_size, ArgbBuffer* output_buffer,
const DecoderConfig& config = DecoderConfig::kDefault);
Decode(const std::string& data, ArgbBuffer* output_buffer,
const DecoderConfig& config = DecoderConfig::kDefault);
// Specialized call
// Decodes the image bitstream in '*input_data' directly into the pre-allocated
// buffer 'output_buffer'. The maximum storage available in this buffer is
// indicated by 'output_buffer_size'. The parameter 'output_stride' specifies
// the distance (in bytes) between scanlines. Hence, 'output_buffer_size' is
// expected to be at least 'output_stride x picture-height'.
// If an error occurred (insufficient storage, ...) the corresponding status is
// returned. Otherwise, the return value is WP2_STATUS_OK.
// The output format is Argb 32b (premultiplied). The ordering of samples in
// memory is: A, r, g, b, A, r, g, b, A, r, g, b...
// in scan order with 8b per channel (endian-independent).
DecodeArgb_32(const uint8_t* input_data, size_t input_data_size,
uint8_t* output_buffer, uint32_t output_stride,
size_t output_buffer_size,
const DecoderConfig& config = DecoderConfig::kDefault);
// Decodes to a custom color space defined by fixed-point 'rgb_to_ccsp_matrix'
// (row-ordered) and its 'rgb_to_ccsp_shift' (applied after). The output samples
// come from alpha-premultiplied RGB and maximum output precision is 10 bits.
// Examples can be found in ../extras/extras.h.
// The 'c0', 'c1' and 'c2' channels are output in buffers of signed 16 bits.
// The 'a' alpha buffer, if not null, is stored in 8 bits (unsigned) every 16
// bits (filled with 255 if the image does not contain any non-opaque alpha).
// The '_step' parameters specify the distances in samples between scanlines and
// must be multiples of two. '_buffer_size' is the byte length of a channel.
// TODO(yguyon): Specify endianness?
Decode(const uint8_t* input_data, size_t input_data_size,
const int16_t rgb_to_ccsp_matrix[9], uint32_t rgb_to_ccsp_shift,
int16_t* c0_buffer, uint32_t c0_step, size_t c0_buffer_size,
int16_t* c1_buffer, uint32_t c1_step, size_t c1_buffer_size,
int16_t* c2_buffer, uint32_t c2_step, size_t c2_buffer_size,
int16_t* a_buffer = nullptr, uint32_t a_step = 0,
size_t a_buffer_size = 0, Metadata* metadata = nullptr,
const DecoderConfig& config = DecoderConfig::kDefault);
// Preview
// If 'output_buffer' is not already allocated, allocates it to the size of the
// WP2 image stored in 'data'. Otherwise uses the pre-allocated 'output_buffer'
// dimensions. Then fills 'output_buffer' with the decoded preview image if any,
// or else with the preview color.
// Returns WP2_STATUS_OK or an error.
WP2_EXTERN WP2_NO_DISCARD WP2Status ExtractPreview(const uint8_t* data,
size_t data_size,
ArgbBuffer* output_buffer);
// WebP2 container parsing
// Types of byte chunk that can be found in a WebP2 bitstream, in order:
enum class ChunkType {kHeader, kPreview, kIcc, kFrame, kXmp, kExif};
// Retrieves the chunk of 'chunk_type' within the 'bitstream_data' and outputs
// it through the given 'writer'.
// 'frame_index' (0-based) is ignored unless 'chunk_type' is kFrame.
// Returns WP2_STATUS_OK, a parsing error or a Writer issue.
// If the chunk is absent, returns WP2_STATUS_OK without writing anything.
WP2_NO_DISCARD WP2Status GetChunk(const uint8_t bitstream_data[],
size_t bitstream_size, ChunkType chunk_type,
Writer* writer, uint32_t frame_index = 0);
// Parses the whole bitstream to count the number of frames.
// Returns WP2_STATUS_OK or an error.
// 'num_frames' is a lower bound in case of corrupted or truncated bitstream.
WP2_NO_DISCARD WP2Status GetNumFrames(const uint8_t bitstream_data[],
size_t bitstream_size,
size_t* const num_frames);
// Animation and incremental decoding
// This API allows streamlined decoding of animated and/or partial data.
// - Use an ArrayDecoder if input is a contiguous array either fully available
// at start or that gets bigger as data comes in.
// - Use a StreamDecoder if input data is streamed (available by
// non-overlapping, in-order chunks).
// - Inherit from CustomDecoder if data must be fetched (for example to read a
// file). Fetch(), Discard() and Reset() must be overridden.
// If supplied, 'output_buffer' MUST NOT be changed until decoding is complete.
// It can either be a view pointing to some external memory (if so, it must
// match the image dimensions), or it is automatically allocated by the Decoder.
// If present and when fully decoded, ICC, EXIF and/or XMP chunks will be stored
// in 'output_buffer.metadata'.
// Code example for an animation encoded into a bitstream DATA of SIZE bytes:
// WP2::ArrayDecoder decoder(DATA, SIZE);
// uint32_t duration;
// while (decoder.ReadFrame(&duration)) {
// // A frame is ready to be fully displayed for 'duration' milliseconds.
// // GetPixels() contains it until the next ReadFrame() call.
// }
// if (decoder.GetStatus() != WP2_STATUS_OK) { /* Handle error */ }
// Code example for a still image encoded into chunks retrieved by GET_CHUNK():
// WP2::StreamDecoder decoder;
// while (GET_CHUNK(&CHUNK)) {
// decoder.AppendInput(CHUNK.DATA, CHUNK.SIZE);
// if (decoder.ReadFrame()) {
// // decoder.GetPixels() can be fully displayed now.
// break; // Still image = only one expected frame.
// }
// if (decoder.Failed()) break;
// // decoder.GetPixels() within decoder.GetDecodedArea() can be shown now.
// }
// if (decoder.GetStatus() != WP2_STATUS_OK) { /* Handle error */ }
class Decoder {
// Deletes the Decoder object and associated memory. Also deletes the output
// unless the 'output_buffer' was specified by the user.
virtual ~Decoder();
// Decodes the given bitstream until a frame is ready, then halts.
// Returns true if the current frame is completely decoded without any error.
// If so, 'duration_ms' can be retrieved if not null (0 ms for a still image).
// Does not halt decoding nor return true for skipped frames.
bool ReadFrame(uint32_t* duration_ms = nullptr);
// Returns WP2_STATUS_OK if the decoding is complete, in other words if the
// end of the bitstream was reached (including the trailing metadata if any).
// Otherwise returns WP2_STATUS_NOT_ENOUGH_DATA or an error that occurred
// during ReadFrame().
WP2_NO_DISCARD WP2Status GetStatus() const;
// Returns true if an error happened.
bool Failed() const;
// Returns the image features or nullptr if not yet available.
const BitstreamFeatures* TryGetDecodedFeatures() const;
// Returns the frame features at 'frame_index' (0-based) or nullptr if not yet
// available.
const FrameFeatures* TryGetFrameDecodedFeatures(uint32_t frame_index) const;
// Returns true and the 'offset' and/or 'length' in bytes in the bitstream of
// the frame at 'frame_index' or false if not yet available.
bool TryGetFrameLocation(uint32_t frame_index, size_t* offset,
size_t* length = nullptr) const;
// Returns the 0-based index of the current frame, which is totally decoded if
// the last call to ReadFrame() returned true, or partially decoded otherwise.
// It is equal to or greater than the total number of skipped frames.
// Meaningless if all frames were decoded or skipped.
uint32_t GetCurrentFrameIndex() const;
// Returns the number of decoded FrameFeatures. Never decreases (even rewind).
uint32_t GetNumFrameDecodedFeatures() const;
// Returns the available rectangular area of the current frame so far.
// Pixels included in this area will not change until next frame.
// The area will not decrease until next frame.
Rectangle GetDecodedArea() const;
// Returns the buffer (or the view to the buffer) containing the pixels.
const ArgbBuffer& GetPixels() const { return *output_; }
// Disables pixel decoding for the next 'num_frames_to_skip' (starting at
// GetCurrentFrameIndex(), included), but still reads frame features.
// Useful for going directly to a timestamp or getting frames info without
// decoding everything ('kMaxNumFrames' to skip all remaining frames).
void SkipNumNextFrames(uint32_t num_frames_to_skip);
// Resets the anim decoder to the first frame but keeps known frame features.
// The decoder will expect the same data, from the beginning of the bitstream.
void Rewind();
// Pointer-to-implementation forward declaration.
struct State;
Decoder(State* const state, const DecoderConfig& config,
ArgbBuffer* output_buffer);
virtual void DecodeAvailableData();
State* const state_;
WP2Status DecodeAvailableTiles();
WP2Status DiscardAvailableTiles();
const DecoderConfig& config_;
ArgbBuffer* output_, internal_output_;
BitstreamFeatures features_;
class ArrayDecoder : public Decoder {
// Constructs a Decoder with the given 'config' and 'output_buffer'.
// The latter can be null or empty (it will be allocated by the Decoder) or
// a view to a user-allocated, correctly-sized buffer (use BitstreamFeatures
// to find the width/height in the bitstream beforehand).
explicit ArrayDecoder(const DecoderConfig& config = DecoderConfig::kDefault,
ArgbBuffer* output_buffer = nullptr);
// Same as the constructor above plus calling SetInput(data, size).
explicit ArrayDecoder(const uint8_t* data, size_t size,
const DecoderConfig& config = DecoderConfig::kDefault,
ArgbBuffer* output_buffer = nullptr);
// Updates the input data buffer.
// Note that the value of the 'data' pointer can change between calls to
// ReadFrame(), for instance when the data buffer is resized to fit larger
// data. 'size' must never decrease between calls.
void SetInput(const uint8_t* data, size_t size);
class StreamDecoder : public Decoder {
// Constructs a Decoder with the given 'config' and 'output_buffer'.
explicit StreamDecoder(const DecoderConfig& config = DecoderConfig::kDefault,
ArgbBuffer* output_buffer = nullptr);
// Registers the next chunk of data from the stream.
// If 'data_is_persistent', 'data' must outlive the next AppendInput() call.
// If not 'data_is_persistent' (default), 'data' will be copied internally.
// If Rewind() is called, AppendInput() must also be called with 'data'
// pointing to the beginning of the bitstream.
void AppendInput(const uint8_t* data, size_t size,
bool data_is_persistent = false);
class CustomDecoder : public Decoder {
// Constructs a Decoder with the given 'config' and 'output_buffer'.
explicit CustomDecoder(const DecoderConfig& config = DecoderConfig::kDefault,
ArgbBuffer* output_buffer = nullptr);
// This will get called during ReadFrame() to fetch more data.
// Override to set 'available_bytes' and 'num_available_bytes', which should
// be at least 'num_requested_bytes' if possible.
// No data will be copied and 'num_available_bytes' must not decrease between
// calls (meaning Fetch() must return at least the same bytes, possibly more)
// until Discard() is called. The buffer can change as long as the values
// stay the same, and the buffer does not need to outlive ReadFrame().
virtual void Fetch(size_t num_requested_bytes,
const uint8_t** available_bytes,
size_t* num_available_bytes) = 0;
// This will get called during ReadFrame() to signal that 'num_bytes' (which
// were previously fetched or not yet) will not be used anymore, and can be
// freed. Fetch() must not return them in the future, only the data that comes
// after. Fetch() is called right after Discard() to make sure the remaining
// available bytes (if any) are up-to-date.
virtual void Discard(size_t num_bytes) = 0;
// This will get called during Rewind() to signal that the bitstream will need
// to be read from the beginning, including previously discarded bytes.
virtual void Reset() = 0;
// Internal.
void DecodeAvailableData() override;
friend class CustomDataSource;
} // namespace WP2
#endif /* WP2_WP2_DECODE_H_ */