/* * Output handler */ static void opng_write_data(png_structp png_ptr, png_bytep data, size_t length) { struct opng_codec_context * context = (struct opng_codec_context *)png_get_io_ptr(png_ptr); struct opng_encoding_stats * stats = context->stats; FILE * stream = context->stream; unsigned io_state = png_get_io_state(png_ptr); unsigned io_state_loc = io_state & PNG_IO_MASK_LOC; OPNG_ASSERT((io_state & PNG_IO_WRITING) && (io_state_loc != 0), "Incorrect info in png_ptr->io_state"); /* Handle the optipng-specific events. */ if (io_state_loc == PNG_IO_CHUNK_HDR) { OPNG_ASSERT(length == 8, "Writing chunk header, expecting 8 bytes"); png_bytep chunk_sig = data + 4; context->crt_chunk_is_allowed = opng_allow_chunk(context, chunk_sig); if (memcmp(chunk_sig, opng_sig_IDAT, 4) == 0) { context->crt_chunk_is_idat = 1; stats->idat_size += png_get_uint_32(data); } else /* not IDAT */ { context->crt_chunk_is_idat = 0; } } if (context->no_write) { return; } /* Continue only if the current chunk type is allowed. */ if (io_state_loc != PNG_IO_SIGNATURE && !context->crt_chunk_is_allowed) return; /* Here comes an elaborate way of writing the data, in which all IDATs * are joined into a single chunk. * Normally, the user-supplied I/O routines are not so complicated. */ switch (io_state_loc) { case PNG_IO_CHUNK_HDR: if (context->crt_chunk_is_idat) { if (context->crt_idat_offset == 0) { /* This is the header of the first IDAT. */ context->crt_idat_offset = ftell(stream); context->crt_idat_size = length; png_save_uint_32(data, (png_uint_32)context->crt_idat_size); /* Start computing the CRC of the final IDAT. */ context->crt_idat_crc = crc32(0, opng_sig_IDAT, 4); } else { /* This is not the first IDAT. Do not write its header. */ return; } } else { if (context->crt_idat_offset != 0) { png_byte buf[4]; /* This is the header of the first chunk after IDAT. * Finalize IDAT before resuming the normal operation. */ png_save_uint_32(buf, context->crt_idat_crc); fwrite(buf, 1, 4, stream); if (stats->idat_size != context->crt_idat_size) { /* The IDAT size, unknown at the start of encoding, * has not been guessed correctly. * It must be updated in a non-streamable way. */ png_save_uint_32(buf, (png_uint_32)stats->idat_size); fpos_t pos; if (fgetpos(stream, &pos) != 0 || fflush(stream) != 0 || (fseek(stream, context->crt_idat_offset, SEEK_SET) != 0) || (fwrite(buf, 1, 4, stream)!=4) || (fflush(stream) != 0) || (fsetpos(stream, &pos) != 0)) { io_state = 0; } } if (io_state == 0) png_error(png_ptr, "Can't finalize IDAT"); context->crt_idat_offset = 0; } } break; case PNG_IO_CHUNK_DATA: if (context->crt_chunk_is_idat) context->crt_idat_crc = crc32(context->crt_idat_crc, data, length); break; case PNG_IO_CHUNK_CRC: if (context->crt_chunk_is_idat) return; /* defer writing until the first non-IDAT occurs */ break; } /* Write the data. */ if (fwrite(data, 1, length, stream) != length) png_error(png_ptr, "Can't write file"); }
/* * @brief manually process a single chunk of apng data */ void apng_ani::_process_chunk() { _id = _read_chunk(_chunk); if (_id == id_acTL && !_got_IDAT && !_got_acTL) { // animation control chunk if (!_reading) { return; } nframes = png_get_uint_32(&_chunk.data[8]); plays = png_get_uint_32(&_chunk.data[12]); if (!nframes || nframes > PNG_UINT_31_MAX || plays > PNG_UINT_31_MAX) { _apng_failed("invalid apng acTL data"); } _got_acTL = true; _frames.reserve(nframes); _frame_offsets.reserve(nframes+1); // extra 1 is for EOF offset } else if (_id == id_fcTL && (!_got_IDAT || _got_acTL)) { // frame control chunk if (_reading) { uint sequence_num = png_get_uint_32(&_chunk.data[8]); if (sequence_num != _sequence_num++) { _apng_failed("invalid apng fcTL sequence number"); } } // handle frame finish in next_frame _framew = png_get_uint_32(&_chunk.data[12]); _frameh = png_get_uint_32(&_chunk.data[16]); _x_offset = png_get_uint_32(&_chunk.data[20]); _y_offset = png_get_uint_32(&_chunk.data[24]); _delay_num = png_get_uint_16(&_chunk.data[28]); _delay_den = png_get_uint_16(&_chunk.data[30]); _dispose_op = _chunk.data[32]; _blend_op = _chunk.data[33]; if (_reading && (_framew > cMaxPNGSize || _frameh > cMaxPNGSize || _x_offset > cMaxPNGSize || _y_offset > cMaxPNGSize || _x_offset + _framew > w || _y_offset + _frameh > h || _dispose_op > 2 || _blend_op > 1)) { _apng_failed("invalid apng fcTL data"); } // according to spec... if (current_frame == 0 && _dispose_op == 2) { _dispose_op = 1; } if (_delay_den == 0) _delay_den = 100; // APNG spec if (_delay_num == 0) _delay_num = 1; // arbitrary lower bound float frame_delay = static_cast<float>(_delay_num)/static_cast<float>(_delay_den); frame.delay = frame_delay; if (_reading) { anim_time+= frame_delay; _frame_offsets.push_back((int)_offset); } else { if (_got_IDAT && _processing_start()) { _apng_failed("couldn't start fdat apng frame"); } } } else if (_id == id_IDAT) { _got_IDAT = true; _processing_data(&_chunk.data[0], _chunk.size); } else if (_id == id_fdAT && _got_acTL) { if (_reading) { uint sequence_num = png_get_uint_32(&_chunk.data[8]); if (sequence_num != _sequence_num++) { _apng_failed("invalid apng fdAT sequence number"); } } if (_reading) { return; } png_save_uint_32(&_chunk.data[4], _chunk.size - 16); memcpy(&_chunk.data[8], "IDAT", 4); _processing_data(&_chunk.data[4], _chunk.size - 4); } else if (_id == id_IEND) { return; } else if (not_chunk(_chunk.data[4]) || not_chunk(_chunk.data[5]) || not_chunk(_chunk.data[6]) || not_chunk(_chunk.data[7])) { _apng_failed("unknown chunk ID found"); } else if (!_got_IDAT) { _processing_data(&_chunk.data[0], _chunk.size); _info_chunks.push_back(_chunk); } }