From 25a01c52b889d64e77fb3f8f7e519d183d7635b8 Mon Sep 17 00:00:00 2001 From: Martin Vignali Date: Sun, 3 Apr 2016 00:21:30 +0200 Subject: [PATCH] libavcodec/exr: add tile support Signed-off-by: Paul B Mahol --- libavcodec/exr.c | 264 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 210 insertions(+), 54 deletions(-) diff --git a/libavcodec/exr.c b/libavcodec/exr.c index aaac3be403..b0573d53e4 100644 --- a/libavcodec/exr.c +++ b/libavcodec/exr.c @@ -3,6 +3,8 @@ * Copyright (c) 2006 Industrial Light & Magic, a division of Lucas Digital Ltd. LLC * Copyright (c) 2009 Jimmy Christensen * + * B44/B44A, Tile added by Jokyo Images support by CNC - French National Center for Cinema + * * This file is part of FFmpeg. * * FFmpeg is free software; you can redistribute it and/or @@ -67,11 +69,29 @@ enum ExrPixelType { EXR_UNKNOWN, }; +enum ExrTileLevelMode { + EXR_TILE_LEVEL_ONE, + EXR_TILE_LEVEL_MIPMAP, + EXR_TILE_LEVEL_RIPMAP, +}; + +enum ExrTileLevelRound { + EXR_TILE_ROUND_UP, + EXR_TILE_ROUND_DOWN, +}; + typedef struct EXRChannel { int xsub, ysub; enum ExrPixelType pixel_type; } EXRChannel; +typedef struct EXRTileAttribute { + int32_t xSize; + int32_t ySize; + enum ExrTileLevelMode level_mode; + enum ExrTileLevelRound level_round; +} EXRTileAttribute; + typedef struct EXRThreadData { uint8_t *uncompressed_data; int uncompressed_size; @@ -97,17 +117,21 @@ typedef struct EXRContext { uint32_t xmax, xmin; uint32_t ymax, ymin; uint32_t xdelta, ydelta; - int ysize; + int ysize, xsize; uint64_t scan_line_size; int scan_lines_per_block; + EXRTileAttribute tile_attr; /* header data attribute of tile */ + int is_tile; /* 0 if scanline, 1 if tile */ + GetByteContext gb; const uint8_t *buf; int buf_size; EXRChannel *channels; int nb_channels; + int current_channel_offset; EXRThreadData *thread_data; @@ -792,7 +816,7 @@ static int pxr24_uncompress(EXRContext *s, const uint8_t *src, if (uncompress(td->tmp, &dest_len, src, compressed_size) != Z_OK) { return AVERROR_INVALIDDATA; - } else if (dest_len != expected_len){ + } else if (dest_len != expected_len) { return AVERROR_INVALIDDATA; } @@ -889,7 +913,7 @@ static void unpack_3(const uint8_t b[3], uint16_t s[16]) static int b44_uncompress(EXRContext *s, const uint8_t *src, int compressed_size, - int uncompressed_size, EXRThreadData *td){ + int uncompressed_size, EXRThreadData *td) { const int8_t *sr = src; int stayToUncompress = compressed_size; int nbB44BlockW, nbB44BlockH; @@ -909,7 +933,7 @@ static int b44_uncompress(EXRContext *s, const uint8_t *src, int compressed_size for (c = 0; c < s->nb_channels; c++) { for (iY = 0; iY < nbB44BlockH; iY++) { for (iX = 0; iX < nbB44BlockW; iX++) {/* For each B44 block */ - if (stayToUncompress < 3){ + if (stayToUncompress < 3) { av_log(s, AV_LOG_ERROR, "Not enough data for B44A block: %d", stayToUncompress); return AVERROR_INVALIDDATA; } @@ -959,43 +983,87 @@ static int decode_block(AVCodecContext *avctx, void *tdata, uint32_t xdelta = s->xdelta; uint16_t *ptr_x; uint8_t *ptr; - uint32_t data_size, line; + uint32_t data_size, line, col = 0; + uint32_t tileX, tileY, tileLevelX, tileLevelY; + int channelLineSize, indexSrc, tX, tY, tCh; const uint8_t *src; - int axmax = (avctx->width - (s->xmax + 1)) * 2 * s->desc->nb_components; - int bxmin = s->xmin * 2 * s->desc->nb_components; + int axmax = (avctx->width - (s->xmax + 1)) * 2 * s->desc->nb_components; /* nb pixel to add at the right of the datawindow */ + int bxmin = s->xmin * 2 * s->desc->nb_components; /* nb pixel to add at the left of the datawindow */ int i, x, buf_size = s->buf_size; float one_gamma = 1.0f / s->gamma; avpriv_trc_function trc_func = avpriv_get_trc_function_from_trc(s->apply_trc_type); int ret; line_offset = AV_RL64(s->gb.buffer + jobnr * 8); - // Check if the buffer has the required bytes needed from the offset - if (line_offset > buf_size - 8) - return AVERROR_INVALIDDATA; - src = buf + line_offset + 8; - line = AV_RL32(src - 8); - if (line < s->ymin || line > s->ymax) - return AVERROR_INVALIDDATA; + if (s->is_tile) { + if (line_offset > buf_size - 20) + return AVERROR_INVALIDDATA; - data_size = AV_RL32(src - 4); - if (data_size <= 0 || data_size > buf_size) - return AVERROR_INVALIDDATA; + src = buf + line_offset + 20; - s->ysize = FFMIN(s->scan_lines_per_block, s->ymax - line + 1); - uncompressed_size = s->scan_line_size * s->ysize; - if ((s->compression == EXR_RAW && (data_size != uncompressed_size || - line_offset > buf_size - uncompressed_size)) || - (s->compression != EXR_RAW && (data_size > uncompressed_size || - line_offset > buf_size - data_size))) { - return AVERROR_INVALIDDATA; + tileX = AV_RL32(src - 20); + tileY = AV_RL32(src - 16); + tileLevelX = AV_RL32(src - 12); + tileLevelY = AV_RL32(src - 8); + + data_size = AV_RL32(src - 4); + if (data_size <= 0 || data_size > buf_size) + return AVERROR_INVALIDDATA; + + if (tileLevelX || tileLevelY) { /* tile of low resolution (Mipmap, rimmap) */ + av_log(s->avctx, AV_LOG_ERROR, "Wrong Tile level %i / %i.\n", tileLevelX, tileLevelY); + return AVERROR_INVALIDDATA; + } + + line = s->tile_attr.ySize * tileY; + col = s->tile_attr.xSize * tileX; + + s->ysize = FFMIN(s->tile_attr.ySize, s->ydelta - tileY * s->tile_attr.ySize); + s->xsize = FFMIN(s->tile_attr.xSize, s->xdelta - tileX * s->tile_attr.xSize); + uncompressed_size = s->current_channel_offset * s->ysize * s->xsize; + + if (col) { /* not the first tile of the line */ + bxmin = 0; axmax = 0; /* doesn't add pixel at the left of the datawindow */ + } + + if ((col + s->xsize) != s->xdelta)/* not the last tile of the line */ + axmax = 0; /* doesn't add pixel at the right of the datawindow */ + } else { + if (line_offset > buf_size - 8) + return AVERROR_INVALIDDATA; + + src = buf + line_offset + 8; + line = AV_RL32(src - 8); + if (line < s->ymin || line > s->ymax) + return AVERROR_INVALIDDATA; + + data_size = AV_RL32(src - 4); + if (data_size <= 0 || data_size > buf_size) + return AVERROR_INVALIDDATA; + + s->ysize = FFMIN(s->scan_lines_per_block, s->ymax - line + 1); /* s->ydelta - line ?? */ + s->xsize = s->xdelta; + uncompressed_size = s->scan_line_size * s->ysize; + if ((s->compression == EXR_RAW && (data_size != uncompressed_size || + line_offset > buf_size - uncompressed_size)) || + (s->compression != EXR_RAW && (data_size > uncompressed_size || + line_offset > buf_size - data_size))) { + return AVERROR_INVALIDDATA; + } + } + + if (data_size < uncompressed_size || s->is_tile) { /* td->tmp is use for tile reorganization */ + av_fast_padded_malloc(&td->tmp, &td->tmp_size, uncompressed_size); + if (!td->tmp) + return AVERROR(ENOMEM); } if (data_size < uncompressed_size) { av_fast_padded_malloc(&td->uncompressed_data, &td->uncompressed_size, uncompressed_size); - av_fast_padded_malloc(&td->tmp, &td->tmp_size, uncompressed_size); - if (!td->uncompressed_data || !td->tmp) + + if (!td->uncompressed_data) return AVERROR(ENOMEM); ret = AVERROR_INVALIDDATA; @@ -1025,16 +1093,40 @@ static int decode_block(AVCodecContext *avctx, void *tdata, src = td->uncompressed_data; } - channel_buffer[0] = src + xdelta * s->channel_offsets[0]; - channel_buffer[1] = src + xdelta * s->channel_offsets[1]; - channel_buffer[2] = src + xdelta * s->channel_offsets[2]; - if (s->channel_offsets[3] >= 0) - channel_buffer[3] = src + xdelta * s->channel_offsets[3]; + if (s->is_tile) { + indexSrc = 0; + channelLineSize = s->xsize * 2; + if (s->pixel_type == EXR_FLOAT) + channelLineSize *= 2; + + /* reorganise tile data to have each channel one after the other instead of line by line */ + for (tY = 0; tY < s->ysize; tY ++) { + for (tCh = 0; tCh < s->nb_channels; tCh++) { + for (tX = 0; tX < channelLineSize; tX++) { + td->tmp[tCh * channelLineSize * s->ysize + tY * channelLineSize + tX] = src[indexSrc]; + indexSrc++; + } + } + } + + channel_buffer[0] = td->tmp + s->xsize * s->channel_offsets[0] * s->ysize; + channel_buffer[1] = td->tmp + s->xsize * s->channel_offsets[1] * s->ysize; + channel_buffer[2] = td->tmp + s->xsize * s->channel_offsets[2] * s->ysize; + if (s->channel_offsets[3] >= 0) + channel_buffer[3] = td->tmp + s->xsize * s->channel_offsets[3]; + } else { + channel_buffer[0] = src + xdelta * s->channel_offsets[0]; + channel_buffer[1] = src + xdelta * s->channel_offsets[1]; + channel_buffer[2] = src + xdelta * s->channel_offsets[2]; + if (s->channel_offsets[3] >= 0) + channel_buffer[3] = src + xdelta * s->channel_offsets[3]; + } + + ptr = p->data[0] + line * p->linesize[0] + (col * s->desc->nb_components * 2); - ptr = p->data[0] + line * p->linesize[0]; for (i = 0; - i < s->scan_lines_per_block && line + i <= s->ymax; - i++, ptr += p->linesize[0]) { + i < s->ysize; i++, ptr += p->linesize[0]) { + const uint8_t *r, *g, *b, *a; r = channel_buffer[0]; @@ -1048,10 +1140,11 @@ static int decode_block(AVCodecContext *avctx, void *tdata, // Zero out the start if xmin is not 0 memset(ptr_x, 0, bxmin); ptr_x += s->xmin * s->desc->nb_components; + if (s->pixel_type == EXR_FLOAT) { // 32-bit if (trc_func) { - for (x = 0; x < xdelta; x++) { + for (x = 0; x < s->xsize; x++) { union av_intfloat32 t; t.i = bytestream_get_le32(&r); t.f = trc_func(t.f); @@ -1068,7 +1161,7 @@ static int decode_block(AVCodecContext *avctx, void *tdata, *ptr_x++ = exr_flt2uint(bytestream_get_le32(&a)); } } else { - for (x = 0; x < xdelta; x++) { + for (x = 0; x < s->xsize; x++) { union av_intfloat32 t; t.i = bytestream_get_le32(&r); if (t.f > 0.0f) /* avoid negative values */ @@ -1090,7 +1183,7 @@ static int decode_block(AVCodecContext *avctx, void *tdata, } } else { // 16-bit - for (x = 0; x < xdelta; x++) { + for (x = 0; x < s->xsize; x++) { *ptr_x++ = s->gamma_table[bytestream_get_le16(&r)]; *ptr_x++ = s->gamma_table[bytestream_get_le16(&g)]; *ptr_x++ = s->gamma_table[bytestream_get_le16(&b)]; @@ -1102,11 +1195,20 @@ static int decode_block(AVCodecContext *avctx, void *tdata, // Zero out the end if xmax+1 is not w memset(ptr_x, 0, axmax); - channel_buffer[0] += s->scan_line_size; - channel_buffer[1] += s->scan_line_size; - channel_buffer[2] += s->scan_line_size; - if (channel_buffer[3]) - channel_buffer[3] += s->scan_line_size; + if (s->is_tile) { + channel_buffer[0] += channelLineSize; + channel_buffer[1] += channelLineSize; + channel_buffer[2] += channelLineSize; + if (channel_buffer[3]) + channel_buffer[3] += channelLineSize; + } + else{ + channel_buffer[0] += s->scan_line_size; + channel_buffer[1] += s->scan_line_size; + channel_buffer[2] += s->scan_line_size; + if (channel_buffer[3]) + channel_buffer[3] += s->scan_line_size; + } } return 0; @@ -1155,9 +1257,9 @@ static int check_header_variable(EXRContext *s, static int decode_header(EXRContext *s) { - int current_channel_offset = 0; - int magic_number, version, flags, i, sar = 0; + int magic_number, version, i, flags, sar = 0; + s->current_channel_offset = 0; s->xmin = ~0; s->xmax = ~0; s->ymin = ~0; @@ -1173,6 +1275,9 @@ static int decode_header(EXRContext *s) s->nb_channels = 0; s->w = 0; s->h = 0; + s->tile_attr.xSize = -1; + s->tile_attr.ySize = -1; + s->is_tile = 0; if (bytestream2_get_bytes_left(&s->gb) < 10) { av_log(s->avctx, AV_LOG_ERROR, "Header too short to parse.\n"); @@ -1194,8 +1299,13 @@ static int decode_header(EXRContext *s) } flags = bytestream2_get_le24(&s->gb); - if (flags & 0x02) { - avpriv_report_missing_feature(s->avctx, "Tile support"); + + if (flags == 0x00) + s->is_tile = 0; + else if (flags & 0x02) + s->is_tile = 1; + else{ + avpriv_report_missing_feature(s->avctx, "flags %d", flags); return AVERROR_PATCHWELCOME; } @@ -1279,7 +1389,7 @@ static int decode_header(EXRContext *s) return AVERROR_INVALIDDATA; } s->pixel_type = current_pixel_type; - s->channel_offsets[channel_index] = current_channel_offset; + s->channel_offsets[channel_index] = s->current_channel_offset; } s->channels = av_realloc(s->channels, @@ -1291,7 +1401,7 @@ static int decode_header(EXRContext *s) channel->xsub = xsub; channel->ysub = ysub; - current_channel_offset += 1 << current_pixel_type; + s->current_channel_offset += 1 << current_pixel_type; } /* Check if all channels are set with an offset or if the channels @@ -1367,6 +1477,32 @@ static int decode_header(EXRContext *s) av_log(s->avctx, AV_LOG_WARNING, "Found more than one compression attribute.\n"); + continue; + } else if ((var_size = check_header_variable(s, "tiles", + "tiledesc", 22)) >= 0) { + if (!s->is_tile) + av_log(s->avctx, AV_LOG_WARNING, + "Found tile attribute and scanline flags. Exr will be interpreted as scanline.\n"); + + s->tile_attr.xSize = bytestream2_get_le32(&s->gb); + s->tile_attr.ySize = bytestream2_get_le32(&s->gb); + + char tileLevel = bytestream2_get_byte(&s->gb); + s->tile_attr.level_mode = tileLevel & 0x0f; + s->tile_attr.level_round = (tileLevel >> 4) & 0x0f; + + if (s->tile_attr.level_mode != EXR_TILE_LEVEL_ONE) { + avpriv_report_missing_feature(s->avctx, "Tile level mode %d", + s->tile_attr.level_mode); + return AVERROR_PATCHWELCOME; + } + + if (s->tile_attr.level_round != EXR_TILE_ROUND_UP) { + avpriv_report_missing_feature(s->avctx, "Tile level round %d", + s->tile_attr.level_round); + return AVERROR_PATCHWELCOME; + } + continue; } @@ -1390,7 +1526,20 @@ static int decode_header(EXRContext *s) av_log(s->avctx, AV_LOG_ERROR, "Missing compression attribute.\n"); return AVERROR_INVALIDDATA; } - s->scan_line_size = s->xdelta * current_channel_offset; + + if (s->is_tile) { + if (s->tile_attr.xSize < 1 || s->tile_attr.ySize < 1) { + av_log(s->avctx, AV_LOG_ERROR, "Invalid tile attribute.\n"); + return AVERROR_INVALIDDATA; + } + + if (s->compression != EXR_RAW) { + avpriv_report_missing_feature(s->avctx, "Compression in tile %d", s->compression); + return AVERROR_PATCHWELCOME; + } + } + + s->scan_line_size = s->xdelta * s->current_channel_offset; if (bytestream2_get_bytes_left(&s->gb) <= 0) { av_log(s->avctx, AV_LOG_ERROR, "Incomplete frame.\n"); @@ -1412,7 +1561,7 @@ static int decode_frame(AVCodecContext *avctx, void *data, int y, ret; int out_line_size; - int scan_line_blocks; + int nb_blocks;/* nb scanline or nb tile */ bytestream2_init(&s->gb, avpkt->data, avpkt->size); @@ -1476,13 +1625,19 @@ static int decode_frame(AVCodecContext *avctx, void *data, if (!s->desc) return AVERROR_INVALIDDATA; out_line_size = avctx->width * 2 * s->desc->nb_components; - scan_line_blocks = (s->ydelta + s->scan_lines_per_block - 1) / - s->scan_lines_per_block; + + if (s->is_tile) { + nb_blocks = ((s->xdelta + s->tile_attr.xSize - 1) / s->tile_attr.xSize) * + ((s->ydelta + s->tile_attr.ySize - 1) / s->tile_attr.ySize); + } else { /* scanline */ + nb_blocks = (s->ydelta + s->scan_lines_per_block - 1) / + s->scan_lines_per_block; + } if ((ret = ff_thread_get_buffer(avctx, &frame, 0)) < 0) return ret; - if (bytestream2_get_bytes_left(&s->gb) < scan_line_blocks * 8) + if (bytestream2_get_bytes_left(&s->gb) < nb_blocks * 8) return AVERROR_INVALIDDATA; // save pointer we are going to use in decode_block @@ -1497,7 +1652,8 @@ static int decode_frame(AVCodecContext *avctx, void *data, } s->picture = picture; - avctx->execute2(avctx, decode_block, s->thread_data, NULL, scan_line_blocks); + + avctx->execute2(avctx, decode_block, s->thread_data, NULL, nb_blocks); // Zero out the end if ymax+1 is not h for (y = s->ymax + 1; y < avctx->height; y++) {