Commit a7ef46558e for ffmpeg
commit a7ef46558ef05ff8725dcbb0bf7743f4d16465d1
Author: Ruk Doe <info@doany.io>
Date: Sat Oct 10 15:50:10 2026 +0900
avcodec/libaribcaption: iterate bitmap rects over images, not regions
The bitmap path allocated one rect per rendered image but filled them,
freed them and placed them by looping over the caption regions. The
renderer does not draw every region: with ignore_ruby it drops ruby
regions, and it always drops regions that are too small to draw. A
caption with one ordinary and one ruby region then wrote past the end
of the rects array.
Loop over the images instead. The renderer does not say which region an
image comes from, so the CLUT can no longer be seeded from "its" region.
Seed it from the colors of every region that may be drawn; colors that
are not seeded are still added from the image itself, as before.
While at it, use bounded insertion for every CLUT seed color,
including the text and background colors added in the character loop.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
Reported-by: Forgejo_Fairy
Reference: #24992
diff --git a/libavcodec/libaribcaption.c b/libavcodec/libaribcaption.c
index 3e930d68a0..eeda071367 100644
--- a/libavcodec/libaribcaption.c
+++ b/libavcodec/libaribcaption.c
@@ -250,57 +250,36 @@ static uint8_t clut_pick_or_set(ARIBCaptionContext *ctx, int r, int g, int b, in
return c;
}
-/* initialize CLUT with each character colors */
-static void clut_init(ARIBCaptionContext *ctx, aribcc_caption_region_t *region)
+/* initialize CLUT with only the transparent color */
+static void clut_reset(ARIBCaptionContext *ctx)
{
- aribcc_color_t text_color, back_color, stroke_color;
- uint32_t rgba;
-
ctx->clut[0] = CLUT_RGBA(0,0,0,0); /* transparent */
ctx->clut_alpha[0] = 0xFF;
ctx->clut_idx = 1;
ctx->clut_overflow = 0;
- text_color = region->chars[0].text_color;
- back_color = region->chars[0].back_color;
- stroke_color = region->chars[0].stroke_color;
- rgba = ARIBCC_COLOR_TO_CLUT_RGBA(text_color, ARIBC_ALPHA_DEFAULT_FRONT);
- ctx->clut[ctx->clut_idx++] = rgba;
- clut_set_alpha(ctx, CLUT_A(rgba));
- rgba = ARIBCC_COLOR_TO_CLUT_RGBA(back_color, ARIBC_ALPHA_DEFAULT_BACK);
+}
+
+/* add a color to the CLUT unless it is already there or the CLUT is full */
+static void clut_add(ARIBCaptionContext *ctx, uint32_t rgba)
+{
+ if (clut_find(ctx, rgba) >= 0)
+ return;
+ if (ctx->clut_idx >= AVPALETTE_COUNT) {
+ ctx->clut_overflow++;
+ return;
+ }
ctx->clut[ctx->clut_idx++] = rgba;
clut_set_alpha(ctx, CLUT_A(rgba));
- rgba = ARIBCC_COLOR_TO_CLUT_RGBA(stroke_color, ARIBC_ALPHA_DEFAULT_FRONT);
- if (clut_find(ctx, rgba) < 0) {
- ctx->clut[ctx->clut_idx++] = rgba;
- clut_set_alpha(ctx, CLUT_A(rgba));
- }
+}
- for (int i = 1; i < region->char_count; i++) {
- if (region->chars[i].text_color != text_color) {
- rgba = ARIBCC_COLOR_TO_CLUT_RGBA(region->chars[i].text_color,
- ARIBC_ALPHA_DEFAULT_FRONT);
- if (clut_find(ctx, rgba) < 0) {
- ctx->clut[ctx->clut_idx++] = rgba;
- clut_set_alpha(ctx, CLUT_A(rgba));
- }
- }
- if (region->chars[i].back_color != back_color) {
- rgba = ARIBCC_COLOR_TO_CLUT_RGBA(region->chars[i].back_color,
- ARIBC_ALPHA_DEFAULT_BACK);
- if (clut_find(ctx, rgba) < 0) {
- ctx->clut[ctx->clut_idx++] = rgba;
- clut_set_alpha(ctx, CLUT_A(rgba));
- }
- }
- if (region->chars[i].stroke_color != stroke_color) {
- rgba = ARIBCC_COLOR_TO_CLUT_RGBA(region->chars[i].stroke_color,
- ARIBC_ALPHA_DEFAULT_FRONT);
- if (clut_find(ctx, rgba) < 0) {
- if (ctx->clut_idx < AVPALETTE_COUNT)
- ctx->clut[ctx->clut_idx++] = rgba;
- clut_set_alpha(ctx, CLUT_A(rgba));
- }
- }
+/* add the character colors of a region to the CLUT */
+static void clut_add_region(ARIBCaptionContext *ctx, aribcc_caption_region_t *region)
+{
+ for (int i = 0; i < region->char_count; i++) {
+ aribcc_caption_char_t *ch = ®ion->chars[i];
+ clut_add(ctx, ARIBCC_COLOR_TO_CLUT_RGBA(ch->text_color, ARIBC_ALPHA_DEFAULT_FRONT));
+ clut_add(ctx, ARIBCC_COLOR_TO_CLUT_RGBA(ch->back_color, ARIBC_ALPHA_DEFAULT_BACK));
+ clut_add(ctx, ARIBCC_COLOR_TO_CLUT_RGBA(ch->stroke_color, ARIBC_ALPHA_DEFAULT_FRONT));
}
}
@@ -318,7 +297,7 @@ static int aribcaption_trans_bitmap_subtitle(ARIBCaptionContext *ctx)
{
int ret = 0;
AVSubtitle *sub = ctx->sub;
- int status, rect_idx;
+ int status, rect_idx, image_count = 0;
int old_width = ctx->frame_width;
int old_height = ctx->frame_height;
@@ -396,13 +375,14 @@ static int aribcaption_trans_bitmap_subtitle(ARIBCaptionContext *ctx)
return 0;
}
+ image_count = ctx->render_result.image_count;
sub->format = 0; /* graphic */
- sub->rects = av_calloc(ctx->render_result.image_count, sizeof(*sub->rects));
+ sub->rects = av_calloc(image_count, sizeof(*sub->rects));
if (!sub->rects) {
ret = AVERROR(ENOMEM);
goto fail;
}
- for (int i = 0; i < ctx->render_result.image_count; i++) {
+ for (int i = 0; i < image_count; i++) {
sub->rects[i] = av_mallocz(sizeof(*sub->rects[i]));
if (!sub->rects[i]) {
ret = AVERROR(ENOMEM);
@@ -410,12 +390,18 @@ static int aribcaption_trans_bitmap_subtitle(ARIBCaptionContext *ctx)
}
}
- for (rect_idx = 0; rect_idx < ctx->caption.region_count; rect_idx++) {
+ for (rect_idx = 0; rect_idx < image_count; rect_idx++) {
AVSubtitleRect *rect = sub->rects[rect_idx];
aribcc_image_t *image = &ctx->render_result.images[rect_idx];
int w, h, shrink_height, dst_idx;
- clut_init(ctx, &ctx->caption.regions[rect_idx]);
+ /* The renderer drops ruby regions with ignore_ruby and regions too
+ small to draw, without telling which region an image comes from.
+ Seed the CLUT with the colors of every region that may be drawn. */
+ clut_reset(ctx);
+ for (int i = 0; i < ctx->caption.region_count; i++)
+ if (!(ctx->ignore_ruby && ctx->caption.regions[i].is_ruby))
+ clut_add_region(ctx, &ctx->caption.regions[i]);
rect->w = image->width * ctx->bitmap_plane_width / ctx->frame_width;
rect->h = image->height * ctx->bitmap_plane_height / ctx->frame_height;
@@ -475,7 +461,7 @@ static int aribcaption_trans_bitmap_subtitle(ARIBCaptionContext *ctx)
/* ARIB TR-B14 version 3.8 Fascicle 1-(2/2) Volume 3 [Section 4] */
/* No position information is provided for profile C */
rect->x = (ctx->frame_width - rect->w) / 2;
- rect->y = ctx->frame_height - rect->h * (ctx->caption.region_count - rect_idx);
+ rect->y = ctx->frame_height - rect->h * (image_count - rect_idx);
} else {
rect->x = image->dst_x * ctx->bitmap_plane_width / ctx->frame_width;
rect->y = image->dst_y * ctx->bitmap_plane_height / ctx->frame_height;
@@ -484,8 +470,7 @@ static int aribcaption_trans_bitmap_subtitle(ARIBCaptionContext *ctx)
rect->linesize[0] = rect->w;
rect->nb_colors = 256;
- ff_dlog(ctx, "BITMAP subtitle%s (%d,%d) %dx%d -> (%d,%d) %dx%d [%d]: %d colors\n",
- (ctx->caption.regions[rect_idx].is_ruby) ? " (ruby)" : "",
+ ff_dlog(ctx, "BITMAP subtitle (%d,%d) %dx%d -> (%d,%d) %dx%d [%d]: %d colors\n",
image->dst_x, image->dst_y, image->width, image->height,
rect->x, rect->y, rect->w, rect->h,
rect_idx, ctx->clut_idx);
@@ -499,7 +484,7 @@ static int aribcaption_trans_bitmap_subtitle(ARIBCaptionContext *ctx)
fail:
if (sub->rects) {
- for (int i = 0; i < ctx->caption.region_count; i++) {
+ for (int i = 0; i < image_count; i++) {
if (sub->rects[i]) {
av_freep(&sub->rects[i]->data[0]);
av_freep(&sub->rects[i]->data[1]);