// Copyright 2014 PDFium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com #include "core/fxcodec/lgif/fx_gif.h" #include #include #include "core/fxcodec/lbmp/fx_bmp.h" #include "core/fxcodec/lgif/cgifcontext.h" #include "third_party/base/ptr_util.h" #include "third_party/base/stl_util.h" static_assert(sizeof(GifImageInfo) == 9, "GifImageInfo should have a size of 9"); static_assert(sizeof(GifPalette) == 3, "GifPalette should have a size of 3"); static_assert(sizeof(GifPTE) == 13, "GifPTE should have a size of 13"); static_assert(sizeof(GifGCE) == 5, "GifGCE should have a size of 5"); static_assert(sizeof(GifHeader) == 6, "GifHeader should have a size of 6"); static_assert(sizeof(GifLSD) == 7, "GifLSD should have a size of 7"); namespace { uint8_t* gif_read_data(CGifContext* context, uint8_t** des_buf_pp, uint32_t data_size) { if (!context || context->avail_in < context->skip_size + data_size) return nullptr; *des_buf_pp = context->next_in + context->skip_size; context->skip_size += data_size; return *des_buf_pp; } void gif_save_decoding_status(CGifContext* context, int32_t status) { context->decode_status = status; context->next_in += context->skip_size; context->avail_in -= context->skip_size; context->skip_size = 0; } GifDecodeStatus gif_decode_extension(CGifContext* context) { uint8_t* data_size_ptr = nullptr; uint8_t* data_ptr = nullptr; uint32_t skip_size_org = context->skip_size; switch (context->decode_status) { case GIF_D_STATUS_EXT_CE: { if (!gif_read_data(context, &data_size_ptr, 1)) { context->skip_size = skip_size_org; return GifDecodeStatus::Unfinished; } context->cmt_data.clear(); while (*data_size_ptr != GIF_BLOCK_TERMINAL) { uint8_t data_size = *data_size_ptr; if (!gif_read_data(context, &data_ptr, *data_size_ptr) || !gif_read_data(context, &data_size_ptr, 1)) { context->skip_size = skip_size_org; return GifDecodeStatus::Unfinished; } context->cmt_data += CFX_ByteString(data_ptr, data_size); } break; } case GIF_D_STATUS_EXT_PTE: { GifPTE* gif_pte = nullptr; if (!gif_read_data(context, reinterpret_cast(&gif_pte), 13)) return GifDecodeStatus::Unfinished; context->m_GifGCE = nullptr; if (!gif_read_data(context, &data_size_ptr, 1)) { context->skip_size = skip_size_org; return GifDecodeStatus::Unfinished; } while (*data_size_ptr != GIF_BLOCK_TERMINAL) { if (!gif_read_data(context, &data_ptr, *data_size_ptr) || !gif_read_data(context, &data_size_ptr, 1)) { context->skip_size = skip_size_org; return GifDecodeStatus::Unfinished; } } break; } case GIF_D_STATUS_EXT_GCE: { GifGCE* gif_gce_ptr = nullptr; if (!gif_read_data(context, reinterpret_cast(&gif_gce_ptr), 6)) return GifDecodeStatus::Unfinished; if (!context->m_GifGCE.get()) context->m_GifGCE = pdfium::MakeUnique(); context->m_GifGCE->block_size = gif_gce_ptr->block_size; context->m_GifGCE->gce_flag = gif_gce_ptr->gce_flag; context->m_GifGCE->delay_time = GetWord_LSBFirst( reinterpret_cast(&gif_gce_ptr->delay_time)); context->m_GifGCE->trans_index = gif_gce_ptr->trans_index; break; } default: { if (context->decode_status == GIF_D_STATUS_EXT_PTE) context->m_GifGCE = nullptr; if (!gif_read_data(context, &data_size_ptr, 1)) return GifDecodeStatus::Unfinished; while (*data_size_ptr != GIF_BLOCK_TERMINAL) { if (!gif_read_data(context, &data_ptr, *data_size_ptr) || !gif_read_data(context, &data_size_ptr, 1)) { context->skip_size = skip_size_org; return GifDecodeStatus::Unfinished; } } } } gif_save_decoding_status(context, GIF_D_STATUS_SIG); return GifDecodeStatus::Success; } GifDecodeStatus gif_decode_image_info(CGifContext* context) { if (context->width == 0 || context->height == 0) { context->ErrorData("No Image Header Info"); return GifDecodeStatus::Error; } uint32_t skip_size_org = context->skip_size; GifImageInfo* gif_img_info_ptr = nullptr; if (!gif_read_data(context, reinterpret_cast(&gif_img_info_ptr), 9)) return GifDecodeStatus::Unfinished; auto gif_image = pdfium::MakeUnique(); gif_image->m_ImageInfo.left = GetWord_LSBFirst(reinterpret_cast(&gif_img_info_ptr->left)); gif_image->m_ImageInfo.top = GetWord_LSBFirst(reinterpret_cast(&gif_img_info_ptr->top)); gif_image->m_ImageInfo.width = GetWord_LSBFirst(reinterpret_cast(&gif_img_info_ptr->width)); gif_image->m_ImageInfo.height = GetWord_LSBFirst(reinterpret_cast(&gif_img_info_ptr->height)); gif_image->m_ImageInfo.local_flag = gif_img_info_ptr->local_flag; if (gif_image->m_ImageInfo.left + gif_image->m_ImageInfo.width > context->width || gif_image->m_ImageInfo.top + gif_image->m_ImageInfo.height > context->height) { context->ErrorData("Image Data Out Of LSD, The File May Be Corrupt"); return GifDecodeStatus::Error; } GifLF* gif_img_info_lf_ptr = (GifLF*)&gif_img_info_ptr->local_flag; if (gif_img_info_lf_ptr->local_pal) { int32_t loc_pal_size = (2 << gif_img_info_lf_ptr->pal_bits) * 3; uint8_t* loc_pal_ptr = nullptr; if (!gif_read_data(context, &loc_pal_ptr, loc_pal_size)) { context->skip_size = skip_size_org; return GifDecodeStatus::Unfinished; } gif_image->m_LocalPalettes = std::vector(loc_pal_size / 3); std::copy(loc_pal_ptr, loc_pal_ptr + loc_pal_size, reinterpret_cast(gif_image->m_LocalPalettes.data())); } uint8_t* code_size_ptr = nullptr; if (!gif_read_data(context, &code_size_ptr, 1)) { context->skip_size = skip_size_org; return GifDecodeStatus::Unfinished; } gif_image->image_code_size = *code_size_ptr; context->RecordCurrentPosition(&gif_image->image_data_pos); gif_image->image_data_pos += context->skip_size; gif_image->m_ImageGCE = nullptr; if (context->m_GifGCE.get()) { gif_image->m_ImageGCE = std::move(context->m_GifGCE); context->m_GifGCE = nullptr; } context->m_Images.push_back(std::move(gif_image)); gif_save_decoding_status(context, GIF_D_STATUS_IMG_DATA); return GifDecodeStatus::Success; } void gif_decoding_failure_at_tail_cleanup(CGifContext* context, GifImage* gif_image_ptr) { gif_image_ptr->m_ImageRowBuf.clear(); gif_save_decoding_status(context, GIF_D_STATUS_TAIL); context->ErrorData("Decode Image Data Error"); } } // namespace GifImage::GifImage() {} GifImage::~GifImage() {} void CGifLZWDecoder::Input(uint8_t* src_buf, uint32_t src_size) { next_in = src_buf; avail_in = src_size; } uint32_t CGifLZWDecoder::GetAvailInput() { return avail_in; } CGifLZWDecoder::CGifLZWDecoder(char* error_ptr) : code_size(0), code_size_cur(0), code_clear(0), code_end(0), code_next(0), code_first(0), stack_size(0), code_old(0), next_in(nullptr), avail_in(0), bits_left(0), code_store(0), err_msg_ptr(error_ptr) {} CGifLZWDecoder::~CGifLZWDecoder() {} void CGifLZWDecoder::InitTable(uint8_t code_len) { code_size = code_len; ASSERT(code_size < 32); code_clear = 1 << code_size; code_end = code_clear + 1; bits_left = 0; code_store = 0; next_in = nullptr; avail_in = 0; stack_size = 0; code_first = 0; ClearTable(); } void CGifLZWDecoder::ClearTable() { code_size_cur = code_size + 1; code_next = code_end + 1; code_old = static_cast(-1); memset(code_table, 0, sizeof(tag_Table) * GIF_MAX_LZW_CODE); memset(stack, 0, GIF_MAX_LZW_CODE); for (uint16_t i = 0; i < code_clear; i++) code_table[i].suffix = static_cast(i); } void CGifLZWDecoder::DecodeString(uint16_t code) { stack_size = 0; while (true) { if (code < code_clear || code > code_next) break; stack[GIF_MAX_LZW_CODE - 1 - stack_size++] = code_table[code].suffix; code = code_table[code].prefix; } stack[GIF_MAX_LZW_CODE - 1 - stack_size++] = static_cast(code); code_first = static_cast(code); } void CGifLZWDecoder::AddCode(uint16_t prefix_code, uint8_t append_char) { if (code_next == GIF_MAX_LZW_CODE) return; code_table[code_next].prefix = prefix_code; code_table[code_next].suffix = append_char; if (++code_next < GIF_MAX_LZW_CODE) { if (code_next >> code_size_cur) code_size_cur++; } } GifDecodeStatus CGifLZWDecoder::Decode(uint8_t* des_buf, uint32_t* des_size) { if (*des_size == 0) return GifDecodeStatus::InsufficientDestSize; uint32_t i = 0; if (stack_size != 0) { if (*des_size < stack_size) { memcpy(des_buf, &stack[GIF_MAX_LZW_CODE - stack_size], *des_size); stack_size -= static_cast(*des_size); return GifDecodeStatus::InsufficientDestSize; } memcpy(des_buf, &stack[GIF_MAX_LZW_CODE - stack_size], stack_size); des_buf += stack_size; i += stack_size; stack_size = 0; } ASSERT(err_msg_ptr); while (i <= *des_size && (avail_in > 0 || bits_left >= code_size_cur)) { if (code_size_cur > 12) { strncpy(err_msg_ptr, "Code Length Out Of Range", GIF_MAX_ERROR_SIZE - 1); return GifDecodeStatus::Error; } if (avail_in > 0) { if (bits_left > 31) { strncpy(err_msg_ptr, "Decode Error", GIF_MAX_ERROR_SIZE - 1); return GifDecodeStatus::Error; } pdfium::base::CheckedNumeric safe_code = *next_in++; safe_code <<= bits_left; safe_code |= code_store; if (!safe_code.IsValid()) { strncpy(err_msg_ptr, "Code Store Out Of Range", GIF_MAX_ERROR_SIZE - 1); return GifDecodeStatus::Error; } code_store = safe_code.ValueOrDie(); --avail_in; bits_left += 8; } while (bits_left >= code_size_cur) { uint16_t code = static_cast(code_store) & ((1 << code_size_cur) - 1); code_store >>= code_size_cur; bits_left -= code_size_cur; if (code == code_clear) { ClearTable(); continue; } if (code == code_end) { *des_size = i; return GifDecodeStatus::Success; } if (code_old != static_cast(-1)) { if (code_next < GIF_MAX_LZW_CODE) { if (code == code_next) { AddCode(code_old, code_first); DecodeString(code); } else if (code > code_next) { strncpy(err_msg_ptr, "Decode Error, Out Of Range", GIF_MAX_ERROR_SIZE - 1); return GifDecodeStatus::Error; } else { DecodeString(code); uint8_t append_char = stack[GIF_MAX_LZW_CODE - stack_size]; AddCode(code_old, append_char); } } } else { DecodeString(code); } code_old = code; if (i + stack_size > *des_size) { memcpy(des_buf, &stack[GIF_MAX_LZW_CODE - stack_size], *des_size - i); stack_size -= static_cast(*des_size - i); return GifDecodeStatus::InsufficientDestSize; } memcpy(des_buf, &stack[GIF_MAX_LZW_CODE - stack_size], stack_size); des_buf += stack_size; i += stack_size; stack_size = 0; } } if (avail_in == 0) { *des_size = i; return GifDecodeStatus::Unfinished; } return GifDecodeStatus::Error; } GifDecodeStatus gif_read_header(CGifContext* context) { if (!context) return GifDecodeStatus::Error; uint32_t skip_size_org = context->skip_size; GifHeader* gif_header_ptr = nullptr; if (!gif_read_data(context, reinterpret_cast(&gif_header_ptr), 6)) return GifDecodeStatus::Unfinished; if (strncmp(gif_header_ptr->signature, GIF_SIGNATURE, 3) != 0 || gif_header_ptr->version[0] != '8' || gif_header_ptr->version[2] != 'a') { context->ErrorData("Not A Gif Image"); return GifDecodeStatus::Error; } GifLSD* gif_lsd_ptr = nullptr; if (!gif_read_data(context, reinterpret_cast(&gif_lsd_ptr), 7)) { context->skip_size = skip_size_org; return GifDecodeStatus::Unfinished; } if (reinterpret_cast(&gif_lsd_ptr->global_flag)->global_pal) { context->global_pal_num = 2 << reinterpret_cast(&gif_lsd_ptr->global_flag)->pal_bits; int32_t global_pal_size = context->global_pal_num * 3; uint8_t* global_pal_ptr = nullptr; if (!gif_read_data(context, &global_pal_ptr, global_pal_size)) { context->skip_size = skip_size_org; return GifDecodeStatus::Unfinished; } context->global_sort_flag = ((GifGF*)&gif_lsd_ptr->global_flag)->sort_flag; context->global_color_resolution = ((GifGF*)&gif_lsd_ptr->global_flag)->color_resolution; context->m_GlobalPalette.resize(global_pal_size / 3); memcpy(context->m_GlobalPalette.data(), global_pal_ptr, global_pal_size); } context->width = (int)GetWord_LSBFirst(reinterpret_cast(&gif_lsd_ptr->width)); context->height = (int)GetWord_LSBFirst(reinterpret_cast(&gif_lsd_ptr->height)); context->bc_index = gif_lsd_ptr->bc_index; context->pixel_aspect = gif_lsd_ptr->pixel_aspect; return GifDecodeStatus::Success; } GifDecodeStatus gif_get_frame(CGifContext* context) { if (!context) return GifDecodeStatus::Error; GifDecodeStatus ret = GifDecodeStatus::Success; while (true) { switch (context->decode_status) { case GIF_D_STATUS_TAIL: return GifDecodeStatus::Success; case GIF_D_STATUS_SIG: { uint8_t* sig_ptr = nullptr; if (!gif_read_data(context, &sig_ptr, 1)) return GifDecodeStatus::Unfinished; switch (*sig_ptr) { case GIF_SIG_EXTENSION: gif_save_decoding_status(context, GIF_D_STATUS_EXT); continue; case GIF_SIG_IMAGE: gif_save_decoding_status(context, GIF_D_STATUS_IMG_INFO); continue; case GIF_SIG_TRAILER: gif_save_decoding_status(context, GIF_D_STATUS_TAIL); return GifDecodeStatus::Success; default: if (context->avail_in) { // The Gif File has non_standard Tag! gif_save_decoding_status(context, GIF_D_STATUS_SIG); continue; } // The Gif File Doesn't have Trailer Tag! return GifDecodeStatus::Success; } } case GIF_D_STATUS_EXT: { uint8_t* ext_ptr = nullptr; if (!gif_read_data(context, &ext_ptr, 1)) return GifDecodeStatus::Unfinished; switch (*ext_ptr) { case GIF_BLOCK_CE: gif_save_decoding_status(context, GIF_D_STATUS_EXT_CE); continue; case GIF_BLOCK_GCE: gif_save_decoding_status(context, GIF_D_STATUS_EXT_GCE); continue; case GIF_BLOCK_PTE: gif_save_decoding_status(context, GIF_D_STATUS_EXT_PTE); continue; default: { int32_t status = GIF_D_STATUS_EXT_UNE; if (*ext_ptr == GIF_BLOCK_PTE) { status = GIF_D_STATUS_EXT_PTE; } gif_save_decoding_status(context, status); continue; } } } case GIF_D_STATUS_IMG_INFO: { ret = gif_decode_image_info(context); if (ret != GifDecodeStatus::Success) return ret; continue; } case GIF_D_STATUS_IMG_DATA: { uint8_t* data_size_ptr = nullptr; uint8_t* data_ptr = nullptr; uint32_t skip_size_org = context->skip_size; if (!gif_read_data(context, &data_size_ptr, 1)) return GifDecodeStatus::Unfinished; while (*data_size_ptr != GIF_BLOCK_TERMINAL) { if (!gif_read_data(context, &data_ptr, *data_size_ptr)) { context->skip_size = skip_size_org; return GifDecodeStatus::Unfinished; } gif_save_decoding_status(context, GIF_D_STATUS_IMG_DATA); skip_size_org = context->skip_size; if (!gif_read_data(context, &data_size_ptr, 1)) return GifDecodeStatus::Unfinished; } gif_save_decoding_status(context, GIF_D_STATUS_SIG); continue; } default: { ret = gif_decode_extension(context); if (ret != GifDecodeStatus::Success) return ret; break; } } } return GifDecodeStatus::Success; } GifDecodeStatus gif_load_frame(CGifContext* context, int32_t frame_num) { if (!context || !pdfium::IndexInBounds(context->m_Images, frame_num)) return GifDecodeStatus::Error; uint8_t* data_size_ptr = nullptr; uint8_t* data_ptr = nullptr; uint32_t skip_size_org = context->skip_size; GifImage* gif_image_ptr = context->m_Images[frame_num].get(); uint32_t gif_img_row_bytes = gif_image_ptr->m_ImageInfo.width; if (gif_img_row_bytes == 0) { context->ErrorData("Error Invalid Number of Row Bytes"); return GifDecodeStatus::Error; } if (context->decode_status == GIF_D_STATUS_TAIL) { gif_image_ptr->m_ImageRowBuf.resize(gif_img_row_bytes); GifGCE* gif_img_gce_ptr = gif_image_ptr->m_ImageGCE.get(); int32_t loc_pal_num = ((GifLF*)&gif_image_ptr->m_ImageInfo.local_flag)->local_pal ? (2 << ((GifLF*)&gif_image_ptr->m_ImageInfo.local_flag)->pal_bits) : 0; context->avail_in = 0; GifPalette* pLocalPalette = gif_image_ptr->m_LocalPalettes.empty() ? nullptr : gif_image_ptr->m_LocalPalettes.data(); if (!gif_img_gce_ptr) { bool bRes = context->GetRecordPosition( gif_image_ptr->image_data_pos, gif_image_ptr->m_ImageInfo.left, gif_image_ptr->m_ImageInfo.top, gif_image_ptr->m_ImageInfo.width, gif_image_ptr->m_ImageInfo.height, loc_pal_num, pLocalPalette, 0, 0, -1, 0, (bool)((GifLF*)&gif_image_ptr->m_ImageInfo.local_flag)->interlace); if (!bRes) { gif_image_ptr->m_ImageRowBuf.clear(); context->ErrorData("Error Read Record Position Data"); return GifDecodeStatus::Error; } } else { bool bRes = context->GetRecordPosition( gif_image_ptr->image_data_pos, gif_image_ptr->m_ImageInfo.left, gif_image_ptr->m_ImageInfo.top, gif_image_ptr->m_ImageInfo.width, gif_image_ptr->m_ImageInfo.height, loc_pal_num, pLocalPalette, (int32_t)gif_image_ptr->m_ImageGCE->delay_time, (bool)((GifCEF*)&gif_image_ptr->m_ImageGCE->gce_flag)->user_input, ((GifCEF*)&gif_image_ptr->m_ImageGCE->gce_flag)->transparency ? (int32_t)gif_image_ptr->m_ImageGCE->trans_index : -1, (int32_t)((GifCEF*)&gif_image_ptr->m_ImageGCE->gce_flag) ->disposal_method, (bool)((GifLF*)&gif_image_ptr->m_ImageInfo.local_flag)->interlace); if (!bRes) { gif_image_ptr->m_ImageRowBuf.clear(); context->ErrorData("Error Read Record Position Data"); return GifDecodeStatus::Error; } } if (gif_image_ptr->image_code_size >= 32) { gif_image_ptr->m_ImageRowBuf.clear(); context->ErrorData("Error Invalid Code Size"); return GifDecodeStatus::Error; } if (!context->m_ImgDecoder.get()) context->m_ImgDecoder = pdfium::MakeUnique(context->err_ptr); context->m_ImgDecoder->InitTable(gif_image_ptr->image_code_size); context->img_row_offset = 0; context->img_row_avail_size = 0; context->img_pass_num = 0; gif_image_ptr->image_row_num = 0; gif_save_decoding_status(context, GIF_D_STATUS_IMG_DATA); } CGifLZWDecoder* img_decoder_ptr = context->m_ImgDecoder.get(); if (context->decode_status == GIF_D_STATUS_IMG_DATA) { if (!gif_read_data(context, &data_size_ptr, 1)) return GifDecodeStatus::Unfinished; if (*data_size_ptr != GIF_BLOCK_TERMINAL) { if (!gif_read_data(context, &data_ptr, *data_size_ptr)) { context->skip_size = skip_size_org; return GifDecodeStatus::Unfinished; } img_decoder_ptr->Input(data_ptr, *data_size_ptr); gif_save_decoding_status(context, GIF_D_STATUS_IMG_DATA); context->img_row_offset += context->img_row_avail_size; context->img_row_avail_size = gif_img_row_bytes - context->img_row_offset; GifDecodeStatus ret = img_decoder_ptr->Decode( gif_image_ptr->m_ImageRowBuf.data() + context->img_row_offset, &context->img_row_avail_size); if (ret == GifDecodeStatus::Error) { gif_decoding_failure_at_tail_cleanup(context, gif_image_ptr); return GifDecodeStatus::Error; } while (ret != GifDecodeStatus::Error) { if (ret == GifDecodeStatus::Success) { context->ReadScanline(gif_image_ptr->image_row_num, gif_image_ptr->m_ImageRowBuf.data()); gif_image_ptr->m_ImageRowBuf.clear(); gif_save_decoding_status(context, GIF_D_STATUS_TAIL); return GifDecodeStatus::Success; } if (ret == GifDecodeStatus::Unfinished) { ASSERT(img_decoder_ptr->GetAvailInput() == 0); skip_size_org = context->skip_size; if (!gif_read_data(context, &data_size_ptr, 1)) return GifDecodeStatus::Unfinished; if (*data_size_ptr != GIF_BLOCK_TERMINAL) { if (!gif_read_data(context, &data_ptr, *data_size_ptr)) { context->skip_size = skip_size_org; return GifDecodeStatus::Unfinished; } img_decoder_ptr->Input(data_ptr, *data_size_ptr); gif_save_decoding_status(context, GIF_D_STATUS_IMG_DATA); context->img_row_offset += context->img_row_avail_size; context->img_row_avail_size = gif_img_row_bytes - context->img_row_offset; ret = img_decoder_ptr->Decode( gif_image_ptr->m_ImageRowBuf.data() + context->img_row_offset, &context->img_row_avail_size); } } if (ret == GifDecodeStatus::InsufficientDestSize) { if (((GifLF*)&gif_image_ptr->m_ImageInfo.local_flag)->interlace) { context->ReadScanline(gif_image_ptr->image_row_num, gif_image_ptr->m_ImageRowBuf.data()); gif_image_ptr->image_row_num += s_gif_interlace_step[context->img_pass_num]; if (gif_image_ptr->image_row_num >= (int32_t)gif_image_ptr->m_ImageInfo.height) { context->img_pass_num++; if (context->img_pass_num == FX_ArraySize(s_gif_interlace_step)) { gif_decoding_failure_at_tail_cleanup(context, gif_image_ptr); return GifDecodeStatus::Error; } gif_image_ptr->image_row_num = s_gif_interlace_step[context->img_pass_num] / 2; } } else { context->ReadScanline(gif_image_ptr->image_row_num++, gif_image_ptr->m_ImageRowBuf.data()); } context->img_row_offset = 0; context->img_row_avail_size = gif_img_row_bytes; ret = img_decoder_ptr->Decode( gif_image_ptr->m_ImageRowBuf.data() + context->img_row_offset, &context->img_row_avail_size); } if (ret == GifDecodeStatus::Error) { gif_decoding_failure_at_tail_cleanup(context, gif_image_ptr); return GifDecodeStatus::Error; } } } gif_save_decoding_status(context, GIF_D_STATUS_TAIL); } context->ErrorData("Decode Image Data Error"); return GifDecodeStatus::Error; } void gif_input_buffer(CGifContext* context, uint8_t* src_buf, uint32_t src_size) { context->next_in = src_buf; context->avail_in = src_size; context->skip_size = 0; } uint32_t gif_get_avail_input(CGifContext* context, uint8_t** avail_buf_ptr) { if (avail_buf_ptr) { *avail_buf_ptr = nullptr; if (context->avail_in > 0) *avail_buf_ptr = context->next_in; } return context->avail_in; } int32_t gif_get_frame_num(CGifContext* context) { return pdfium::CollectionSize(context->m_Images); }