From: Massimo Eynard <massimo.eynard@edu.univ-eiffel.fr> To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] [PATCH] libavcodec/pgssubdec: Implement cropping Date: Tue, 4 Apr 2023 11:15:07 +0200 Message-ID: <c8f07db6-f096-61b3-e2d6-0c6f15dfb6a5@edu.univ-eiffel.fr> (raw) In-Reply-To: <307774> Implement missing cropping option of subtitle bitmap, enabling complex cropping effects. A test sample as been submitted to https://streams.videolan.org/upload/. Attempt to correct the previous patch. Signed-off-by: Massimo Eynard <massimo.eynard@edu.univ-eiffel.fr> --- libavcodec/pgssubdec.c | 194 ++++++++++++++++++++++++++++------------- 1 file changed, 134 insertions(+), 60 deletions(-) diff --git a/libavcodec/pgssubdec.c b/libavcodec/pgssubdec.c index 5f76f12615..050667dbf3 100644 --- a/libavcodec/pgssubdec.c +++ b/libavcodec/pgssubdec.c @@ -70,9 +70,13 @@ typedef struct PGSSubObject { int id; int w; int h; - uint8_t *rle; - unsigned int rle_buffer_size, rle_data_len; + uint8_t *rle; /**< Run Length Encoded bitmap. */ + unsigned int rle_buffer_size; + unsigned int rle_data_len; unsigned int rle_remaining_len; + uint8_t *bitmap; /**< Decoded bitmap. */ + unsigned int bitmap_buffer_size; + unsigned int bitmap_size; } PGSSubObject; typedef struct PGSSubObjects { @@ -105,8 +109,11 @@ static void flush_cache(AVCodecContext *avctx) for (i = 0; i < ctx->objects.count; i++) { av_freep(&ctx->objects.object[i].rle); - ctx->objects.object[i].rle_buffer_size = 0; + ctx->objects.object[i].rle_buffer_size = 0; ctx->objects.object[i].rle_remaining_len = 0; + av_freep(&ctx->objects.object[i].bitmap); + ctx->objects.object[i].bitmap_buffer_size = 0; + ctx->objects.object[i].bitmap_size = 0; } ctx->objects.count = 0; ctx->palettes.count = 0; @@ -149,57 +156,57 @@ static av_cold int close_decoder(AVCodecContext *avctx) } /** - * Decode the RLE data. + * Decode the RLE data of a subtitle object. * - * The subtitle is stored as a Run Length Encoded image. + * The subtitle is stored as a Run Length Encoded bitmap image. * - * @param avctx contains the current codec context - * @param sub pointer to the processed subtitle data - * @param buf pointer to the RLE data to process - * @param buf_size size of the RLE data to process + * @param avctx Contains the current codec context. + * @param object Pointer to the processed subtitle object. */ -static int decode_rle(AVCodecContext *avctx, AVSubtitleRect *rect, - const uint8_t *buf, unsigned int buf_size) +static int decode_object_rle(AVCodecContext *avctx, PGSSubObject *object) { - const uint8_t *rle_bitmap_end; + const uint8_t *rle_buf; + const uint8_t *rle_end; int pixel_count, line_count; - rle_bitmap_end = buf + buf_size; - - rect->data[0] = av_malloc_array(rect->w, rect->h); + rle_buf = object->rle; + rle_end = object->rle + object->rle_data_len; - if (!rect->data[0]) + object->bitmap_size = object->w * object->h; + av_fast_padded_malloc(&object->bitmap, &object->bitmap_buffer_size, object->bitmap_size); + if (!object->bitmap) return AVERROR(ENOMEM); pixel_count = 0; line_count = 0; - while (buf < rle_bitmap_end && line_count < rect->h) { + while (rle_buf < rle_end && line_count < object->h) { uint8_t flags, color; int run; - color = bytestream_get_byte(&buf); + color = bytestream_get_byte(&rle_buf); run = 1; if (color == 0x00) { - flags = bytestream_get_byte(&buf); + flags = bytestream_get_byte(&rle_buf); run = flags & 0x3f; if (flags & 0x40) - run = (run << 8) + bytestream_get_byte(&buf); - color = flags & 0x80 ? bytestream_get_byte(&buf) : 0; + run = (run << 8) + bytestream_get_byte(&rle_buf); + color = flags & 0x80 ? bytestream_get_byte(&rle_buf) : 0; } - if (run > 0 && pixel_count + run <= rect->w * rect->h) { - memset(rect->data[0] + pixel_count, color, run); + if (run > 0 && pixel_count + run <= object->w * object->h) { + memset(object->bitmap + pixel_count, color, run); pixel_count += run; } else if (!run) { /* * New Line. Check if correct pixels decoded, if not display warning * and adjust bitmap pointer to correct new line position. */ - if (pixel_count % rect->w > 0) { - av_log(avctx, AV_LOG_ERROR, "Decoded %d pixels, when line should be %d pixels\n", - pixel_count % rect->w, rect->w); + if (pixel_count % object->w > 0) { + av_log(avctx, AV_LOG_ERROR, + "Decoded %d pixels, when object line should be %d pixels\n", + pixel_count % object->w, object->w); if (avctx->err_recognition & AV_EF_EXPLODE) { return AVERROR_INVALIDDATA; } @@ -208,12 +215,12 @@ static int decode_rle(AVCodecContext *avctx, AVSubtitleRect *rect, } } - if (pixel_count < rect->w * rect->h) { - av_log(avctx, AV_LOG_ERROR, "Insufficient RLE data for subtitle\n"); + if (pixel_count < object->w * object->h) { + av_log(avctx, AV_LOG_ERROR, "Insufficient RLE data for object\n"); return AVERROR_INVALIDDATA; } - ff_dlog(avctx, "Pixel Count = %d, Area = %d\n", pixel_count, rect->w * rect->h); + ff_dlog(avctx, "Pixel Count = %d, Area = %d\n", pixel_count, object->w * object->h); return 0; } @@ -289,14 +296,16 @@ static int parse_object_segment(AVCodecContext *avctx, width = bytestream_get_be16(&buf); height = bytestream_get_be16(&buf); - /* Make sure the bitmap is not too large */ - if (avctx->width < width || avctx->height < height || !width || !height) { - av_log(avctx, AV_LOG_ERROR, "Bitmap dimensions (%dx%d) invalid.\n", width, height); + /* Make sure the bitmap size is not zero */ + if (!width || !height) { + av_log(avctx, AV_LOG_ERROR, + "Bitmap dimensions (%dx%d) invalid.\n", width, height); return AVERROR_INVALIDDATA; } object->w = width; object->h = height; + /* Dimensions against video are checked at decode after cropping. */ av_fast_padded_malloc(&object->rle, &object->rle_buffer_size, rle_bitmap_len); @@ -383,7 +392,6 @@ static int parse_palette_segment(AVCodecContext *avctx, * @param avctx contains the current codec context * @param buf pointer to the packet to process * @param buf_size size of packet to process - * @todo TODO: Implement cropping */ static int parse_presentation_segment(AVCodecContext *avctx, const uint8_t *buf, int buf_size, @@ -468,16 +476,7 @@ static int parse_presentation_segment(AVCodecContext *avctx, ff_dlog(avctx, "Subtitle Placement x=%d, y=%d\n", object->x, object->y); - - if (object->x > avctx->width || object->y > avctx->height) { - av_log(avctx, AV_LOG_ERROR, "Subtitle out of video bounds. x = %d, y = %d, video width = %d, video height = %d.\n", - object->x, object->y, - avctx->width, avctx->height); - object->y = object->x = 0; - if (avctx->err_recognition & AV_EF_EXPLODE) { - return AVERROR_INVALIDDATA; - } - } + /* Placement is checked at decode after cropping. */ } return 0; @@ -528,6 +527,7 @@ static int display_end_segment(AVCodecContext *avctx, AVSubtitle *sub, return AVERROR_INVALIDDATA; } for (i = 0; i < ctx->presentation.object_count; i++) { + const PGSSubObjectRef sub_object = ctx->presentation.objects[i]; AVSubtitleRect *const rect = av_mallocz(sizeof(*rect)); PGSSubObject *object; @@ -537,45 +537,119 @@ static int display_end_segment(AVCodecContext *avctx, AVSubtitle *sub, rect->type = SUBTITLE_BITMAP; /* Process bitmap */ - object = find_object(ctx->presentation.objects[i].id, &ctx->objects); + object = find_object(sub_object.id, &ctx->objects); if (!object) { // Missing object. Should only happen with damaged streams. av_log(avctx, AV_LOG_ERROR, "Invalid object id %d\n", - ctx->presentation.objects[i].id); + sub_object.id); if (avctx->err_recognition & AV_EF_EXPLODE) return AVERROR_INVALIDDATA; // Leaves rect empty with 0 width and height. continue; } - if (ctx->presentation.objects[i].composition_flag & 0x40) + if (sub_object.composition_flag & 0x40) rect->flags |= AV_SUBTITLE_FLAG_FORCED; - rect->x = ctx->presentation.objects[i].x; - rect->y = ctx->presentation.objects[i].y; + rect->x = sub_object.x; + rect->y = sub_object.y; if (object->rle) { - rect->w = object->w; - rect->h = object->h; + int out_of_picture = 0; + rect->w = object->w; + rect->h = object->h; rect->linesize[0] = object->w; - if (object->rle_remaining_len) { - av_log(avctx, AV_LOG_ERROR, "RLE data length %u is %u bytes shorter than expected\n", - object->rle_data_len, object->rle_remaining_len); + // Check for cropping. + if (sub_object.composition_flag & 0x80) { + int out_of_object = 0; + + if (object->w < sub_object.crop_x + sub_object.crop_w) + out_of_object = 1; + if (object->h < sub_object.crop_y + sub_object.crop_h) + out_of_object = 1; + + if (out_of_object) { + av_log(avctx, AV_LOG_ERROR, + "Subtitle cropping values are out of object. " + "obj_w = %d, obj_h = %d, crop_x = %d, crop_y = %d, " + "crop_w = %d, crop_h = %d.\n", + object->w, + object->h, + sub_object.crop_x, + sub_object.crop_y, + sub_object.crop_w, + sub_object.crop_h); + if (avctx->err_recognition & AV_EF_EXPLODE) + return AVERROR_INVALIDDATA; + } + else { + // Replace subtitle dimensions with cropping ones. + rect->w = sub_object.crop_w; + rect->h = sub_object.crop_h; + rect->linesize[0] = sub_object.crop_w; + } + } + + /* Make sure the subtitle is not out of picture. */ + if (avctx->width < rect->x + rect->w || !rect->w) + out_of_picture = 1; + if (avctx->height < rect->y + rect->h || !rect->h) + out_of_picture = 1; + if (out_of_picture) { + av_log(avctx, AV_LOG_ERROR, + "Subtitle out of video bounds. " + "x = %d, y = %d, width = %d, height = %d.\n", + rect->x, rect->y, rect->w, rect->h); if (avctx->err_recognition & AV_EF_EXPLODE) return AVERROR_INVALIDDATA; - } - ret = decode_rle(avctx, rect, object->rle, object->rle_data_len); - if (ret < 0) { - if ((avctx->err_recognition & AV_EF_EXPLODE) || - ret == AVERROR(ENOMEM)) { - return ret; - } rect->w = 0; rect->h = 0; continue; } + + if (!object->bitmap_size) { + /* Decode bitmap from RLE. */ + if (object->rle_remaining_len) { + av_log(avctx, AV_LOG_ERROR, + "RLE data length %u is %u bytes shorter than expected\n", + object->rle_data_len, object->rle_remaining_len); + if (avctx->err_recognition & AV_EF_EXPLODE) + return AVERROR_INVALIDDATA; + } + + ret = decode_object_rle(avctx, object); + if (ret < 0) { + if ((avctx->err_recognition & AV_EF_EXPLODE) || + ret == AVERROR(ENOMEM)) { + return ret; + } + rect->w = 0; + rect->h = 0; + continue; + } + } + + rect->data[0] = av_malloc_array(rect->w, rect->h); + if (!rect->data[0]) + return AVERROR(ENOMEM); + + if (sub_object.composition_flag & 0x80) { + /* Copy cropped bitmap. */ + int y; + + for (y = 0; y < sub_object.crop_h; y++) { + memcpy(&rect->data[0][y * sub_object.crop_w], + &object->bitmap[(sub_object.crop_y + y) * + object->w + sub_object.crop_x], + sub_object.crop_w); + } + } + else { + memcpy(rect->data[0], object->bitmap, object->bitmap_size); + } } + /* Allocate memory for colors */ rect->nb_colors = 256; rect->data[1] = av_mallocz(AVPALETTE_SIZE); -- 2.40.0 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
next parent reply other threads:[~2023-04-04 9:15 UTC|newest] Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top [not found] <307774> 2023-04-04 9:15 ` Massimo Eynard [this message] 2023-03-25 14:01 Massimo Eynard
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=c8f07db6-f096-61b3-e2d6-0c6f15dfb6a5@edu.univ-eiffel.fr \ --to=massimo.eynard@edu.univ-eiffel.fr \ --cc=ffmpeg-devel@ffmpeg.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel This inbox may be cloned and mirrored by anyone: git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git # If you have public-inbox 1.1+ installed, you may # initialize and index your mirror using the following commands: public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \ ffmpegdev@gitmailbox.com public-inbox-index ffmpegdev Example config snippet for mirrors. AGPL code for this site: git clone https://public-inbox.org/public-inbox.git