Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 72 additions & 126 deletions YYImage/YYImageCoder.m
Original file line number Diff line number Diff line change
Expand Up @@ -318,82 +318,6 @@ static double yy_png_delay_to_seconds(uint16_t num, uint16_t den) {
}
}

static bool yy_png_validate_animation_chunk_order(yy_png_chunk_info *chunks, /* input */
uint32_t chunk_num, /* input */
uint32_t *first_idat_index, /* output */
bool *first_frame_is_cover /* output */) {
/*
PNG at least contains 3 chunks: IHDR, IDAT, IEND.
`IHDR` must appear first.
`IDAT` must appear consecutively.
`IEND` must appear end.

APNG must contains one `acTL` and at least one 'fcTL' and `fdAT`.
`fdAT` must appear consecutively.
`fcTL` must appear before `IDAT` or `fdAT`.
*/
if (chunk_num <= 2) return false;
if (chunks->fourcc != YY_FOUR_CC('I', 'H', 'D', 'R')) return false;
if ((chunks + chunk_num - 1)->fourcc != YY_FOUR_CC('I', 'E', 'N', 'D')) return false;

uint32_t prev_fourcc = 0;
uint32_t IHDR_num = 0;
uint32_t IDAT_num = 0;
uint32_t acTL_num = 0;
uint32_t fcTL_num = 0;
uint32_t first_IDAT = 0;
bool first_frame_cover = false;
for (uint32_t i = 0; i < chunk_num; i++) {
yy_png_chunk_info *chunk = chunks + i;
switch (chunk->fourcc) {
case YY_FOUR_CC('I', 'H', 'D', 'R'): { // png header
if (i != 0) return false;
if (IHDR_num > 0) return false;
IHDR_num++;
} break;
case YY_FOUR_CC('I', 'D', 'A', 'T'): { // png data
if (prev_fourcc != YY_FOUR_CC('I', 'D', 'A', 'T')) {
if (IDAT_num == 0)
first_IDAT = i;
else
return false;
}
IDAT_num++;
} break;
case YY_FOUR_CC('a', 'c', 'T', 'L'): { // apng control
if (acTL_num > 0) return false;
acTL_num++;
} break;
case YY_FOUR_CC('f', 'c', 'T', 'L'): { // apng frame control
if (i + 1 == chunk_num) return false;
if ((chunk + 1)->fourcc != YY_FOUR_CC('f', 'd', 'A', 'T') &&
(chunk + 1)->fourcc != YY_FOUR_CC('I', 'D', 'A', 'T')) {
return false;
}
if (fcTL_num == 0) {
if ((chunk + 1)->fourcc == YY_FOUR_CC('I', 'D', 'A', 'T')) {
first_frame_cover = true;
}
}
fcTL_num++;
} break;
case YY_FOUR_CC('f', 'd', 'A', 'T'): { // apng data
if (prev_fourcc != YY_FOUR_CC('f', 'd', 'A', 'T') && prev_fourcc != YY_FOUR_CC('f', 'c', 'T', 'L')) {
return false;
}
} break;
}
prev_fourcc = chunk->fourcc;
}
if (IHDR_num != 1) return false;
if (IDAT_num == 0) return false;
if (acTL_num != 1) return false;
if (fcTL_num < acTL_num) return false;
*first_idat_index = first_IDAT;
*first_frame_is_cover = first_frame_cover;
return true;
}

static void yy_png_info_release(yy_png_info *info) {
if (info) {
if (info->chunks) free(info->chunks);
Expand All @@ -419,6 +343,7 @@ static void yy_png_info_release(yy_png_info *info) {
uint32_t chunk_realloc_num = 16;
yy_png_chunk_info *chunks = malloc(sizeof(yy_png_chunk_info) * chunk_realloc_num);
if (!chunks) return NULL;
yy_png_info *info = NULL;

// parse png chunks
uint32_t offset = 8;
Expand All @@ -429,34 +354,42 @@ static void yy_png_info_release(yy_png_info *info) {
int32_t apng_frame_index = 0;
int32_t apng_frame_number = -1;
bool apng_chunk_error = false;
bool first_frame_is_cover = false;
bool encountered_fdAT = false;
uint32_t first_IDAT_index = 0;
do {
if (chunk_num >= chunk_capacity) {
yy_png_chunk_info *new_chunks = realloc(chunks, sizeof(yy_png_chunk_info) * (chunk_capacity + chunk_realloc_num));
if (!new_chunks) {
free(chunks);
return NULL;
}
if (!new_chunks) goto fail;
chunks = new_chunks;
chunk_capacity += chunk_realloc_num;
}
yy_png_chunk_info *chunk = chunks + chunk_num;
const uint8_t *chunk_data = data + offset;
chunk->offset = offset;
chunk->length = yy_swap_endian_uint32(*((uint32_t *)chunk_data));
if ((uint64_t)chunk->offset + (uint64_t)chunk->length + 12 > length) {
free(chunks);
return NULL;
}
if ((uint64_t)chunk->offset + (uint64_t)chunk->length + 12 > length) goto fail;

chunk->fourcc = *((uint32_t *)(chunk_data + 4));
if ((uint64_t)chunk->offset + 4 + chunk->length + 4 > (uint64_t)length) break;
chunk->crc32 = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 8 + chunk->length)));
chunk_num++;
offset += 12 + chunk->length;

if (chunk_num == 0 && chunk->fourcc != YY_FOUR_CC('I', 'H', 'D', 'R') ||
offset == length && chunk->fourcc != YY_FOUR_CC('I', 'E', 'N', 'D')) {
goto fail;
}
if (chunk->fourcc == YY_FOUR_CC('t', 'E', 'X', 't') ||
chunk->fourcc == YY_FOUR_CC('z', 'T', 'X', 't') ||
chunk->fourcc == YY_FOUR_CC('i', 'T', 'X', 't') ||
chunk->fourcc == YY_FOUR_CC('t', 'I', 'M', 'E') ||
chunk->fourcc == YY_FOUR_CC('e', 'X', 'I', 'f')) {
continue;
}
chunk_num++;

switch (chunk->fourcc) {
case YY_FOUR_CC('a', 'c', 'T', 'L') : {
if (chunk->length == 8) {
if (apng_frame_number == -1 && first_IDAT_index == 0 && chunk->length == 8) {
apng_frame_number = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 8)));
apng_loop_num = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 12)));
} else {
Expand All @@ -466,11 +399,17 @@ case YY_FOUR_CC('a', 'c', 'T', 'L') : {
case YY_FOUR_CC('f', 'c', 'T', 'L') :
case YY_FOUR_CC('f', 'd', 'A', 'T') : {
if (chunk->fourcc == YY_FOUR_CC('f', 'c', 'T', 'L')) {
if (chunk->length != 26) {
apng_chunk_error = true;
} else {
if ((apng_frame_index > 0 ? encountered_fdAT : 1) && chunk->length == 26) {
apng_frame_index++;
encountered_fdAT = false;
} else {
apng_chunk_error = true;
}
} else if (apng_sequence_index == -1 || first_IDAT_index == 0 ||
(apng_sequence_index == 0 && first_frame_is_cover)) {
apng_chunk_error = true;
} else {
encountered_fdAT = true;
}
if (chunk->length > 4) {
uint32_t sequence = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 8)));
Expand All @@ -482,64 +421,60 @@ case YY_FOUR_CC('f', 'd', 'A', 'T') : {
} else {
apng_chunk_error = true;
}
if (apng_sequence_index == 0 && first_IDAT_index == 0) {
first_frame_is_cover = true;
}
} break;
case YY_FOUR_CC('I', 'D', 'A', 'T') : {
if (first_IDAT_index == 0) {
first_IDAT_index = chunk_num - 1;
} else if ((chunk - 1)->fourcc != YY_FOUR_CC('I', 'D', 'A', 'T')) {
goto fail;
}
encountered_fdAT = true;
} break;
case YY_FOUR_CC('I', 'H', 'D', 'R') : {
if (chunk->length != 13 || chunk_num != 1) goto fail;
} break;
case YY_FOUR_CC('I', 'E', 'N', 'D') : {
if (chunk->length != 0) goto fail;
offset = length; // end, break do-while loop
} break;
}
} while (offset + 12 <= length);

if (chunk_num < 3 ||
chunks->fourcc != YY_FOUR_CC('I', 'H', 'D', 'R') ||
chunks->length != 13) {
free(chunks);
return NULL;
}
if (first_IDAT_index == 0 || offset != length) goto fail;
if (!encountered_fdAT) apng_chunk_error = true;

// png info
yy_png_info *info = calloc(1, sizeof(yy_png_info));
if (!info) {
free(chunks);
return NULL;
}
info = calloc(1, sizeof(yy_png_info));
if (!info) goto fail;
info->chunks = chunks;
info->chunk_num = chunk_num;
yy_png_chunk_IHDR_read(&info->header, data + chunks->offset + 8);

// apng info
if (!apng_chunk_error && apng_frame_number == apng_frame_index && apng_frame_number >= 1) {
bool first_frame_is_cover = false;
uint32_t first_IDAT_index = 0;
if (!yy_png_validate_animation_chunk_order(info->chunks, info->chunk_num, &first_IDAT_index, &first_frame_is_cover)) {
return info; // ignore apng chunk
}

info->apng_loop_num = apng_loop_num;
info->apng_frame_num = apng_frame_number;
info->apng_first_frame_is_cover = first_frame_is_cover;
info->apng_shared_insert_index = first_IDAT_index;
info->apng_frames = calloc(apng_frame_number, sizeof(yy_png_frame_info));
if (!info->apng_frames) {
yy_png_info_release(info);
return NULL;
}
if (!info->apng_frames) goto fail;
info->apng_shared_chunk_indexs = calloc(info->chunk_num, sizeof(uint32_t));
if (!info->apng_shared_chunk_indexs) {
yy_png_info_release(info);
return NULL;
}
if (!info->apng_shared_chunk_indexs) goto fail;

int32_t frame_index = -1;
uint32_t *shared_chunk_index = info->apng_shared_chunk_indexs;
for (int32_t i = 0; i < info->chunk_num; i++) {
yy_png_chunk_info *chunk = info->chunks + i;
switch (chunk->fourcc) {
case YY_FOUR_CC('I', 'D', 'A', 'T'): {
if (info->apng_shared_insert_index == 0) {
info->apng_shared_insert_index = i;
}
if (first_frame_is_cover) {
yy_png_frame_info *frame = info->apng_frames + frame_index;
if (frame->chunk_index == 0) {
frame->chunk_index = i;
}
frame->chunk_num++;
frame->chunk_size += chunk->length + 12;
}
Expand All @@ -549,24 +484,33 @@ case YY_FOUR_CC('a', 'c', 'T', 'L'): {
case YY_FOUR_CC('f', 'c', 'T', 'L'): {
frame_index++;
yy_png_frame_info *frame = info->apng_frames + frame_index;
frame->chunk_index = i + 1;
yy_png_chunk_fcTL_read(&frame->frame_control, data + chunk->offset + 8);
} break;
case YY_FOUR_CC('f', 'd', 'A', 'T'): {
yy_png_frame_info *frame = info->apng_frames + frame_index;
if (frame->chunk_index == 0) {
frame->chunk_index = i;
}
frame->chunk_num++;
frame->chunk_size += chunk->length + 12;
} break;
default: {
*shared_chunk_index = i;
shared_chunk_index++;
info->apng_shared_chunk_size += chunk->length + 12;
info->apng_shared_chunk_num++;
if (i < first_IDAT_index || i == info->chunk_num - 1) {
*shared_chunk_index = i;
shared_chunk_index++;
info->apng_shared_chunk_size += chunk->length + 12;
info->apng_shared_chunk_num++;
}
} break;
}
}
}
return info;

fail:
if (info) yy_png_info_release(info);
else if (chunks) free(chunks);
return NULL;
}

/**
Expand Down Expand Up @@ -604,18 +548,20 @@ case YY_FOUR_CC('f', 'd', 'A', 'T'): {

if (shared_chunk_index >= info->apng_shared_insert_index && !inserted) { // replace IDAT with fdAT
inserted = true;
for (uint32_t c = 0; c < frame_info->chunk_num; c++) {
yy_png_chunk_info *insert_chunk_info = info->chunks + frame_info->chunk_index + c;
yy_png_chunk_info *insert_chunk_info = info->chunks + frame_info->chunk_index;
for (uint32_t c = frame_info->chunk_num; c > 0; insert_chunk_info++) {
if (insert_chunk_info->fourcc == YY_FOUR_CC('f', 'd', 'A', 'T')) {
*((uint32_t *)(frame_data + data_offset)) = yy_swap_endian_uint32(insert_chunk_info->length - 4);
*((uint32_t *)(frame_data + data_offset + 4)) = YY_FOUR_CC('I', 'D', 'A', 'T');
memcpy(frame_data + data_offset + 8, data + insert_chunk_info->offset + 12, insert_chunk_info->length - 4);
uint32_t crc = (uint32_t)crc32(0, frame_data + data_offset + 4, insert_chunk_info->length);
*((uint32_t *)(frame_data + data_offset + insert_chunk_info->length + 4)) = yy_swap_endian_uint32(crc);
data_offset += insert_chunk_info->length + 8;
} else { // IDAT
c--;
} else if (insert_chunk_info->fourcc == YY_FOUR_CC('I', 'D', 'A', 'T')) {
memcpy(frame_data + data_offset, data + insert_chunk_info->offset, insert_chunk_info->length + 12);
data_offset += insert_chunk_info->length + 12;
c--;
}
}
}
Expand Down