Skip to content

Commit

Permalink
Merge pull request #4 from cubicibo/MR/optimizepal
Browse files Browse the repository at this point in the history
Optimise palette generation for PGS.
  • Loading branch information
cubicibo authored Dec 13, 2023
2 parents 81f50d0 + f0453c0 commit bc2d19e
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 56 deletions.
27 changes: 16 additions & 11 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,19 +77,12 @@ The following optional arguments are available:
| ``-c`` | Flag to name the output XML according to the input ASS |
| ``--copyname`` | file. The input ASS file must have a valid extension. |
+--------------------+--------------------------------------------------------+
| ``-n`` | Merge together events that are reported as different |
| ``--no-dupes`` | by libass yet identical when composited (e.g ASSDraw). |
+--------------------+--------------------------------------------------------+
| ``-t`` | Sets the human-readable name of the subtitle track. |
| ``--trackname`` | Default: ``Undefined`` |
+--------------------+--------------------------------------------------------+
| ``-l`` | Sets the language of the subtitle track. |
| ``--language`` | Default: ``und`` |
+--------------------+--------------------------------------------------------+
| ``-d`` | Flag to apply a contrast change that may improve |
| ``--dvd-mode`` | subtitle appearance with the limited resolution and |
| | color palette of DVD subtitles. |
+--------------------+--------------------------------------------------------+
| ``-w`` | Sets the width to use as ASS frame & storage space |
| ``--render-width`` | Defaults to output width if not specified. Some ass |
| | tags may not render properly if the value is improper. |
Expand All @@ -98,9 +91,6 @@ The following optional arguments are available:
| ``--render-height``| Defaults to output height if not specified. Some ass |
| | tags may not render properly if the value is improper. |
+--------------------+--------------------------------------------------------+
| ``-g`` | Flag to enable libass soft hinting. |
| ``--hinting`` | |
+--------------------+--------------------------------------------------------+
| ``-x`` | Sets the ASS storage width. I.e the pre-anamorphic |
| ``--width-store`` | width. ``-p`` should be preferred, Last resort option. |
+--------------------+--------------------------------------------------------+
Expand All @@ -123,4 +113,19 @@ Below are parameters to tune libimagequant (LIQ). Those shall only be used along
| ``--liq-dither`` | Dithering level, value must be within [0; 1.0] incl. |
| | Default: ``1.0``. Disable: ``0``. LIQ dithering is soft|
| | so default or ``0.5`` is perfect in general. |
+--------------------+--------------------------------------------------------+
+--------------------+--------------------------------------------------------+

Moreover, the last table has debugging parameters. These should not have any practical in most scenarios.

+--------------------+--------------------------------------------------------+
| Option | Effect |
+====================+========================================================+
| ``-d`` | Flag to apply a contrast change that may improve |
| ``--dvd-mode`` | subtitle appearance with the limited resolution and |
| | color palette of DVD subtitles. |
+--------------------+--------------------------------------------------------+
| ``--keep-dupes`` | Flag to not merge events that are reported as different|
| | by libass yet identical when composited (e.g ASSDraw). |
+--------------------+--------------------------------------------------------+
| ``--hinting`` | Flag to enable soft hinting in libass. |
+--------------------+--------------------------------------------------------+
70 changes: 36 additions & 34 deletions ass2bdnxml.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,12 @@ vfmt_t vfmts[] = {
{NULL, 0, 0}
};

#define OPT_LIQ_SPEED 1000
#define OPT_LIQ_DITHER 1001
#define OPT_LIQ_MAXQUAL 1002
#define OPT_ARG_HINTING 998
#define OPT_ARG_KEEPDUPES 999

#define OPT_LIQ_SPEED 1000
#define OPT_LIQ_DITHER 1001
#define OPT_LIQ_MAXQUAL 1002

static void die_usage(const char *name)
{
Expand Down Expand Up @@ -229,26 +232,26 @@ int main(int argc, char *argv[])
}

static struct option longopts[] = {
{"fontdir", required_argument, 0, 'a'},
{"copyname", no_argument, 0, 'c'},
{"dvd-mode", no_argument, 0, 'd'},
{"hinting", no_argument, 0, 'g'},
{"negative", no_argument, 0, 'z'},
{"fps", required_argument, 0, 'f'},
{"height-render",required_argument, 0, 'h'},
{"language", required_argument, 0, 'l'},
{"splitmargin", required_argument, 0, 'm'},
{"offset", required_argument, 0, 'o'},
{"par", required_argument, 0, 'p'},
{"quantize", required_argument, 0, 'q'},
{"rleopt", no_argument, 0, 'r'},
{"copyname", no_argument, 0, 'c'},
{"no-dupes", no_argument, 0, 'n'},
{"split", required_argument, 0, 's'},
{"splitmargin", required_argument, 0, 'm'},
{"trackname", required_argument, 0, 't'},
{"language", required_argument, 0, 'l'},
{"video-format", required_argument, 0, 'v'},
{"fps", required_argument, 0, 'f'},
{"width-render", required_argument, 0, 'w'},
{"height-render",required_argument, 0, 'h'},
{"width-store", required_argument, 0, 'x'},
{"height-store", required_argument, 0, 'y'},
{"par", required_argument, 0, 'p'},
{"fontdir", required_argument, 0, 'a'},
{"offset", required_argument, 0, 'o'},
{"quantize", required_argument, 0, 'q'},
{"negative", no_argument, 0, 'z'},
{"hinting", no_argument, 0, OPT_ARG_HINTING},
{"keep-dupes", no_argument, 0, OPT_ARG_KEEPDUPES},
{"liq-dither", required_argument, 0, OPT_LIQ_DITHER},
{"liq-quality", required_argument, 0, OPT_LIQ_MAXQUAL},
{"liq-speed", required_argument, 0, OPT_LIQ_SPEED},
Expand All @@ -257,7 +260,7 @@ int main(int argc, char *argv[])

while (1) {
int opt_index = 0;
int c = getopt_long(argc, argv, "czdgjrt:l:v:f:w:h:x:y:p:a:o:q:s:m:k:", longopts, &opt_index);
int c = getopt_long(argc, argv, "cdgrza:f:h:l:m:o:p:q:s:t:v:w:x:y:", longopts, &opt_index);

if (c == -1)
break;
Expand All @@ -278,7 +281,7 @@ int main(int argc, char *argv[])
case 'r':
args.rle_optimise = 1;
break;
case 'g':
case OPT_ARG_HINTING:
args.hinting = 1;
break;
case 't':
Expand Down Expand Up @@ -344,14 +347,16 @@ int main(int argc, char *argv[])
break;
case 'q':
args.quantize = (uint16_t)strtol(optarg, NULL, 10);
if (args.quantize > 255) {
printf("Colours must be within [0; 255] incl. (default: 0, no quantization, output are 32-bit RGBA PNGs).\n");
if (args.quantize > 256) {
printf("Colours must be within [0; 256] incl. (default: 0, no quantization, output 32-bit RGBA PNGs).\n");
exit(1);
} else if (1 == args.quantize) {
//Cannot quantize with just a single color.
++args.quantize;
}
args.quantize += (args.quantize == 1);
break;
case 'n':
args.find_dupes = 1;
case OPT_ARG_KEEPDUPES:
args.keep_dupes = 1;
break;
case OPT_LIQ_SPEED:
liqargs.speed = (uint8_t)strtol(optarg, NULL, 10);
Expand Down Expand Up @@ -383,18 +388,17 @@ int main(int argc, char *argv[])
}
}

if (argc - optind == 1)
if (argc - optind == 1) {
subfile = argv[optind];
else {
} else {
printf("Only a single input file allowed.\n");
exit(1);
}
if (copy_name) {
int len = strlen(subfile);
int ext_pos = -1;

for (i = len-1; i >= -1; --i)
{
for (i = len-1; i >= -1; --i) {
if (i == -1 || subfile[i] == '\\'|| subfile[i] == '/') {
bdnfile = (char*)malloc(len - i + 1);
memcpy(bdnfile, &subfile[i+1], len - i);
Expand All @@ -415,11 +419,9 @@ int main(int argc, char *argv[])

i = 0;

while (frates[i].name != NULL)
{
while (frates[i].name != NULL) {
if (!strcasecmp(frates[i].name, frame_rate))
frate = &frates[i];

i++;
}

Expand All @@ -430,11 +432,9 @@ int main(int argc, char *argv[])

i = 0;

while (vfmts[i].name != NULL)
{
while (vfmts[i].name != NULL) {
if (!strcasecmp(vfmts[i].name, video_format))
vfmt = &vfmts[i];

i++;
}

Expand Down Expand Up @@ -473,9 +473,11 @@ int main(int argc, char *argv[])
args.offset *= -1;

if (args.quantize) {
//RLE optimise discard palette entry zero, we have 254 usable colors.
if (args.rle_optimise && args.quantize == 255)
//RLE optimise discard palette entry zero, we have one less usable entry, ensure we don't overshoot the 8-bit id
if (args.rle_optimise && args.quantize >= 256) {
args.quantize -= 1;
printf("ass2bdnxml: RLE optimisation enabled, only using %d colors.\n", args.quantize);
}
liqargs.max_quality = MAX(0, MIN(100, liqargs.max_quality));
} else if (liq_params) {
printf("Set up libimagequant parameters but not using --quantize.\n");
Expand Down
2 changes: 1 addition & 1 deletion common.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ typedef struct opts_s {
uint8_t hinting : 1;
uint8_t split : 1;
uint8_t rle_optimise : 1;
uint8_t find_dupes : 1;
uint8_t keep_dupes : 1;
uint8_t _pad : 3;
const char *fontdir;
} opts_t;
Expand Down
42 changes: 32 additions & 10 deletions render.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,14 @@ static void write_png_palette(uint32_t count, image_t *rgba_img, liq_image **img
liq_set_dithering_level(*res, dither_val);
liq_write_remapped_image(*res, *img, (void*)bitmap, rgba_img->width*h);

//Palette entry zero is annoying in PGS, let's use it for a single pixel so authoring software can't shift the palette to id zero.
//One pixel of palette entry zero needs at least two bytes to be encoded with PGS.
//This is an issue whenever the color index changes frequently due to max line coding limit.
//To avoid RLE line length overshoot, this palette entry may not be used.
if (rle_optimise) {
for (k = 0; k < rgba_img->width*h; k++)
bitmap[k] += 1;
memcpy(&palette[0], &palette[bitmap[0]], sizeof(png_color));
trans[0] = trans[bitmap[0]];
bitmap[0] = 0;
}

for (uint8_t split_cnt = 0; split_cnt < MIN(2, 1 + is_split); split_cnt++) {
Expand Down Expand Up @@ -314,6 +315,11 @@ static void init(opts_t *args, liqopts_t *liqargs)
liq_set_max_colors(attr, args->quantize);
liq_set_quality(attr, 0, liqargs->max_quality);
liq_set_speed(attr, liqargs->speed);

//Palette entry 0xFF must always be transparent.
if (args->quantize + args->rle_optimise >= 256)
liq_set_last_index_transparent(attr, 1);

printf("libimagequant: Version %s\n", LIQ_VERSION_STRING);
printf("libimagequant: Settings: max-colors=%d, max-quality=%d, speed=%d, dithering=%.02f\n",
liq_get_max_colors(attr), liq_get_max_quality(attr), liq_get_speed(attr), liqargs->dither);
Expand Down Expand Up @@ -608,7 +614,6 @@ static int get_frame(ASS_Renderer *renderer, ASS_Track *track, image_t *prev_fra
ASS_Image *img = ass_render_frame(renderer, track, ms, &changed);

if (changed && img) {
frame->out = frame_cnt + 1;
blend(frame, img);
//frame differ from the previous?
if (NULL == prev_frame) {
Expand All @@ -622,6 +627,7 @@ static int get_frame(ASS_Renderer *renderer, ASS_Track *track, image_t *prev_fra
++frame->out;
return 1;
}
frame->out = frame_cnt + 1;

if (frame->subx1 == -1 || frame->suby1 == -1)
return 2;
Expand All @@ -635,17 +641,33 @@ static int get_frame(ASS_Renderer *renderer, ASS_Track *track, image_t *prev_fra
}
}

static int quantize_event(image_t *frame, liq_image **img, liq_result **qtz_res)
static int quantize_event(image_t *frame, liq_image **img, liq_result **qtz_res, opts_t *args)
{
*img = liq_image_create_rgba(attr, &frame->buffer[frame->stride*frame->suby1], frame->width, frame->suby2-frame->suby1+1, 0);
if (*img == NULL) {
if (NULL == *img)
return -1;
}

if(liq_image_quantize(*img, attr, qtz_res) != LIQ_OK) {
if(liq_image_quantize(*img, attr, qtz_res) != LIQ_OK)
return -1;

int ret = 0;
if (args->quantize + args->rle_optimise >= 256) {
const liq_palette *pal = liq_get_palette(*qtz_res);
//Using the entire palette and the last palette entry is not transparent?
if (pal->count == args->quantize && pal->entries[pal->count - 1].a > 0) {
const uint16_t max_colors = liq_get_max_colors(attr);

//destroy invalid result and quantize with colors-1
liq_result_destroy(*qtz_res);
liq_set_max_colors(attr, max_colors - 1);

if(liq_image_quantize(*img, attr, qtz_res) != LIQ_OK)
ret = -1;
//reset color count to user config
liq_set_max_colors(attr, max_colors);
}
}
return 0;
return ret;
}

eventlist_t *render_subs(char *subfile, frate_t *frate, opts_t *args, liqopts_t *liqargs)
Expand All @@ -670,7 +692,7 @@ eventlist_t *render_subs(char *subfile, frate_t *frate, opts_t *args, liqopts_t

image_t *frame = image_init(args->frame_w, args->frame_h, args->dvd_mode);
image_t *prev_frame;
prev_frame = args->find_dupes ? image_init(args->frame_w, args->frame_h, args->dvd_mode) : NULL;
prev_frame = args->keep_dupes ? NULL : image_init(args->frame_w, args->frame_h, args->dvd_mode);

while (1) {
if (fres && fres != 2 && count) {
Expand All @@ -682,7 +704,7 @@ eventlist_t *render_subs(char *subfile, frate_t *frate, opts_t *args, liqopts_t
switch (fres) {
case 3:
{
if (args->quantize && quantize_event(frame, &img, &res)) {
if (args->quantize && quantize_event(frame, &img, &res, args)) {
printf("Quantization failed for " FILENAME_FMT FILENAME_EXT ".\n", count);
exit(1);
}
Expand Down

0 comments on commit bc2d19e

Please sign in to comment.