gstreamer/gst/dvbsubenc/libimagequant/nearest.c
Jan Schmidt 1cf3cae5e1 dvbsubenc: Add DVB Subtitle encoder
Add an element that converts AYUV video frames to a DVB
subpicture stream.

It's fairly simple for now. Later it would be good to support
input via a stream that contains only GstVideoOverlayComposition
meta.

The element searches each input video frame for the largest
sub-region containing non-transparent pixels and encodes that
as a single DVB subpicture region. It can also do palette
reduction of the input frames using code taken from
libimagequant.

There are various FIXME for potential improvements for now, but
it works.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/1227>
2020-06-17 12:50:13 +10:00

262 lines
7.5 KiB
C

#include "libimagequant.h"
#include "pam.h"
#include "nearest.h"
#include "mempool.h"
#include <stdlib.h>
struct sorttmp
{
float radius;
unsigned int index;
};
struct head
{
// colors less than radius away from vantage_point color will have best match in candidates
f_pixel vantage_point;
float radius;
unsigned int num_candidates;
f_pixel *candidates_color;
unsigned short *candidates_index;
};
struct nearest_map
{
const colormap *map;
float nearest_other_color_dist[256];
mempool mempool;
struct head heads[];
};
static float
distance_from_nearest_other_color (const colormap * map, const unsigned int i)
{
float second_best = MAX_DIFF;
for (unsigned int j = 0; j < map->colors; j++) {
float diff;
if (i == j)
continue;
diff = colordifference (map->palette[i].acolor, map->palette[j].acolor);
if (diff <= second_best) {
second_best = diff;
}
}
return second_best;
}
static int
compareradius (const void *ap, const void *bp)
{
float a = ((const struct sorttmp *) ap)->radius;
float b = ((const struct sorttmp *) bp)->radius;
return a > b ? 1 : (a < b ? -1 : 0);
}
static struct head
build_head (f_pixel px, const colormap * map, unsigned int num_candidates,
mempool * m, float error_margin, bool skip_index[], unsigned int *skipped)
{
struct sorttmp *colors = g_alloca (sizeof (struct sorttmp) * map->colors);
unsigned int colorsused, i;
struct head h;
colorsused = 0;
for (i = 0; i < map->colors; i++) {
if (skip_index[i])
continue; // colors in skip_index have been eliminated already in previous heads
colors[colorsused].index = i;
colors[colorsused].radius = colordifference (px, map->palette[i].acolor);
colorsused++;
}
qsort (colors, colorsused, sizeof (colors[0]), compareradius);
assert (colorsused < 2 || colors[0].radius <= colors[1].radius); // closest first
num_candidates = MIN (colorsused, num_candidates);
h.candidates_color =
mempool_alloc (m, num_candidates * sizeof (h.candidates_color[0]), 0);
h.candidates_index =
mempool_alloc (m, num_candidates * sizeof (h.candidates_index[0]), 0);
h.vantage_point = px;
h.num_candidates = num_candidates;
for (i = 0; i < num_candidates; i++) {
h.candidates_color[i] = map->palette[colors[i].index].acolor;
h.candidates_index[i] = colors[i].index;
}
// if all colors within this radius are included in candidates, then there cannot be any other better match
// farther away from the vantage point than half of the radius. Due to alpha channel must assume pessimistic radius.
h.radius = min_colordifference (px, h.candidates_color[num_candidates - 1]) / 4.0f; // /4 = half of radius, but radius is squared
for (i = 0; i < num_candidates; i++) {
// divide again as that's matching certain subset within radius-limited subset
// - 1/256 is a tolerance for miscalculation (seems like colordifference isn't exact)
if (colors[i].radius < h.radius / 4.f - error_margin) {
skip_index[colors[i].index] = true;
(*skipped)++;
}
}
return h;
}
static colormap *
get_subset_palette (const colormap * map)
{
unsigned int subset_size, i;
colormap *subset_palette;
if (map->subset_palette) {
return map->subset_palette;
}
subset_size = (map->colors + 3) / 4;
subset_palette = pam_colormap (subset_size, map->malloc, map->free);
for (i = 0; i < subset_size; i++) {
subset_palette->palette[i] = map->palette[i];
}
return subset_palette;
}
LIQ_PRIVATE struct nearest_map *
nearest_init (const colormap * map, bool fast)
{
colormap *subset_palette = get_subset_palette (map);
const unsigned int num_vantage_points =
map->colors > 16 ? MIN (map->colors / (fast ? 4 : 3),
subset_palette->colors) : 0;
const unsigned long heads_size = sizeof (struct head) * (num_vantage_points + 1); // +1 is fallback head
const unsigned long mempool_size =
(sizeof (f_pixel) +
sizeof (unsigned int)) * subset_palette->colors * map->colors / 5 +
(1 << 14);
mempool m = NULL;
struct nearest_map *centroids = mempool_create (&m,
sizeof (*centroids) + heads_size /* heads array is appended to it */ ,
mempool_size, map->malloc, map->free);
unsigned int skipped;
const float error_margin = fast ? 0 : 8.f / 256.f / 256.f;
unsigned int h, i, j;
bool *skip_index;
centroids->mempool = m;
for (i = 0; i < map->colors; i++) {
const float dist = distance_from_nearest_other_color (map, i);
centroids->nearest_other_color_dist[i] = dist / 4.f; // half of squared distance
}
centroids->map = map;
skipped = 0;
assert (map->colors > 0);
skip_index = g_alloca (sizeof (bool) * map->colors);
for (j = 0; j < map->colors; j++)
skip_index[j] = false;
// floats and colordifference calculations are not perfect
for (h = 0; h < num_vantage_points; h++) {
unsigned int num_candiadtes =
1 + (map->colors - skipped) / ((1 + num_vantage_points - h) / 2);
centroids->heads[h] =
build_head (subset_palette->palette[h].acolor, map, num_candiadtes,
&centroids->mempool, error_margin, skip_index, &skipped);
if (centroids->heads[h].num_candidates == 0) {
break;
}
}
// assumption that there is no better color within radius of vantage point color
// holds true only for colors within convex hull formed by palette colors.
// The fallback must contain all colors, since there are too many edge cases to cover.
if (!fast)
for (j = 0; j < map->colors; j++) {
skip_index[j] = false;
}
centroids->heads[h] = build_head ((f_pixel) {
0, 0, 0, 0}
, map, map->colors, &centroids->mempool, error_margin,
skip_index, &skipped);
centroids->heads[h].radius = MAX_DIFF;
// get_subset_palette could have created a copy
if (subset_palette != map->subset_palette) {
pam_freecolormap (subset_palette);
}
return centroids;
}
LIQ_PRIVATE unsigned int
nearest_search (const struct nearest_map *centroids, const f_pixel px,
int likely_colormap_index, const float min_opaque_val, float *diff)
{
const bool iebug = px.a > min_opaque_val;
const struct head *const heads = centroids->heads;
float guess_diff;
unsigned int i;
assert (likely_colormap_index < centroids->map->colors);
guess_diff =
colordifference (centroids->map->palette[likely_colormap_index].acolor,
px);
if (guess_diff < centroids->nearest_other_color_dist[likely_colormap_index]) {
if (diff)
*diff = guess_diff;
return likely_colormap_index;
}
for (i = 0; /* last head will always be selected */ ; i++) {
float vantage_point_dist = colordifference (px, heads[i].vantage_point);
if (vantage_point_dist <= heads[i].radius) {
unsigned int ind = 0;
float dist;
assert (heads[i].num_candidates);
dist = colordifference (px, heads[i].candidates_color[0]);
/* penalty for making holes in IE */
if (iebug && heads[i].candidates_color[0].a < 1) {
dist += 1.f / 1024.f;
}
for (unsigned int j = 1; j < heads[i].num_candidates; j++) {
float newdist = colordifference (px, heads[i].candidates_color[j]);
/* penalty for making holes in IE */
if (iebug && heads[i].candidates_color[j].a < 1) {
newdist += 1.f / 1024.f;
}
if (newdist < dist) {
dist = newdist;
ind = j;
}
}
if (diff)
*diff = dist;
return heads[i].candidates_index[ind];
}
}
}
LIQ_PRIVATE void
nearest_free (struct nearest_map *centroids)
{
mempool_destroy (centroids->mempool);
}