mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-17 13:56:32 +00:00
1cf3cae5e1
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>
261 lines
7.5 KiB
C
261 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,
|
|
¢roids->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, ¢roids->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);
|
|
}
|