mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 13:41:48 +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>
322 lines
10 KiB
C
322 lines
10 KiB
C
/* pam.c - pam (portable alpha map) utility library
|
|
**
|
|
** Copyright (C) 1989, 1991 by Jef Poskanzer.
|
|
** Copyright (C) 1997, 2000, 2002 by Greg Roelofs; based on an idea by
|
|
** Stefan Schneider.
|
|
** © 2009-2013 by Kornel Lesinski.
|
|
**
|
|
** Permission to use, copy, modify, and distribute this software and its
|
|
** documentation for any purpose and without fee is hereby granted, provided
|
|
** that the above copyright notice appear in all copies and that both that
|
|
** copyright notice and this permission notice appear in supporting
|
|
** documentation. This software is provided "as is" without express or
|
|
** implied warranty.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "libimagequant.h"
|
|
#include "pam.h"
|
|
#include "mempool.h"
|
|
|
|
/* *INDENT-OFF* */
|
|
LIQ_PRIVATE bool
|
|
pam_computeacolorhash (struct acolorhash_table *acht,
|
|
const rgba_pixel * const pixels[], unsigned int cols, unsigned int rows,
|
|
const unsigned char *importance_map)
|
|
/* *INDENT-ON* */
|
|
{
|
|
const unsigned int maxacolors = acht->maxcolors, ignorebits =
|
|
acht->ignorebits;
|
|
const unsigned int channel_mask = 255U >> ignorebits << ignorebits;
|
|
const unsigned int channel_hmask = (255U >> ignorebits) ^ 0xFFU;
|
|
const unsigned int posterize_mask =
|
|
channel_mask << 24 | channel_mask << 16 | channel_mask << 8 |
|
|
channel_mask;
|
|
const unsigned int posterize_high_mask =
|
|
channel_hmask << 24 | channel_hmask << 16 | channel_hmask << 8 |
|
|
channel_hmask;
|
|
struct acolorhist_arr_head *const buckets = acht->buckets;
|
|
|
|
unsigned int colors = acht->colors;
|
|
const unsigned int hash_size = acht->hash_size;
|
|
|
|
const unsigned int stacksize =
|
|
sizeof (acht->freestack) / sizeof (acht->freestack[0]);
|
|
struct acolorhist_arr_item **freestack = acht->freestack;
|
|
unsigned int freestackp = acht->freestackp;
|
|
|
|
/* Go through the entire image, building a hash table of colors. */
|
|
for (unsigned int row = 0; row < rows; ++row) {
|
|
|
|
float boost = 1.0;
|
|
for (unsigned int col = 0; col < cols; ++col) {
|
|
union rgba_as_int px = { pixels[row][col] };
|
|
unsigned int hash;
|
|
struct acolorhist_arr_head *achl;
|
|
|
|
if (importance_map) {
|
|
boost = 0.5f + (double) *importance_map++ / 255.f;
|
|
}
|
|
// RGBA color is casted to long for easier hasing/comparisons
|
|
if (!px.rgba.a) {
|
|
// "dirty alpha" has different RGBA values that end up being the same fully transparent color
|
|
px.l = 0;
|
|
hash = 0;
|
|
} else {
|
|
// mask posterizes all 4 channels in one go
|
|
px.l =
|
|
(px.l & posterize_mask) | ((px.l & posterize_high_mask) >> (8 -
|
|
ignorebits));
|
|
// fancier hashing algorithms didn't improve much
|
|
hash = px.l % hash_size;
|
|
}
|
|
|
|
/* head of the hash function stores first 2 colors inline (achl->used = 1..2),
|
|
to reduce number of allocations of achl->other_items.
|
|
*/
|
|
achl = &buckets[hash];
|
|
if (achl->inline1.color.l == px.l && achl->used) {
|
|
achl->inline1.perceptual_weight += boost;
|
|
continue;
|
|
}
|
|
if (achl->used) {
|
|
if (achl->used > 1) {
|
|
struct acolorhist_arr_item *other_items;
|
|
unsigned int i = 0;
|
|
struct acolorhist_arr_item *new_items;
|
|
unsigned int capacity;
|
|
|
|
if (achl->inline2.color.l == px.l) {
|
|
achl->inline2.perceptual_weight += boost;
|
|
continue;
|
|
}
|
|
// other items are stored as an array (which gets reallocated if needed)
|
|
other_items = achl->other_items;
|
|
for (i = 0; i < achl->used - 2; i++) {
|
|
if (other_items[i].color.l == px.l) {
|
|
other_items[i].perceptual_weight += boost;
|
|
goto continue_outer_loop;
|
|
}
|
|
}
|
|
|
|
// the array was allocated with spare items
|
|
if (i < achl->capacity) {
|
|
other_items[i] = (struct acolorhist_arr_item) {
|
|
.color = px,.perceptual_weight = boost,};
|
|
achl->used++;
|
|
++colors;
|
|
continue;
|
|
}
|
|
|
|
if (++colors > maxacolors) {
|
|
acht->colors = colors;
|
|
acht->freestackp = freestackp;
|
|
return false;
|
|
}
|
|
|
|
if (!other_items) { // there was no array previously, alloc "small" array
|
|
capacity = 8;
|
|
if (freestackp <= 0) {
|
|
// estimate how many colors are going to be + headroom
|
|
const int mempool_size =
|
|
((acht->rows + rows - row) * 2 * colors / (acht->rows + row +
|
|
1) + 1024) * sizeof (struct acolorhist_arr_item);
|
|
new_items =
|
|
mempool_alloc (&acht->mempool,
|
|
sizeof (struct acolorhist_arr_item) * capacity, mempool_size);
|
|
} else {
|
|
// freestack stores previously freed (reallocated) arrays that can be reused
|
|
// (all pesimistically assumed to be capacity = 8)
|
|
new_items = freestack[--freestackp];
|
|
}
|
|
} else {
|
|
// simply reallocs and copies array to larger capacity
|
|
capacity = achl->capacity * 2 + 16;
|
|
if (freestackp < stacksize - 1) {
|
|
freestack[freestackp++] = other_items;
|
|
}
|
|
|
|
{
|
|
const int mempool_size =
|
|
((acht->rows + rows - row) * 2 * colors / (acht->rows + row +
|
|
1) + 32 * capacity) * sizeof (struct acolorhist_arr_item);
|
|
new_items =
|
|
mempool_alloc (&acht->mempool,
|
|
sizeof (struct acolorhist_arr_item) * capacity, mempool_size);
|
|
}
|
|
if (!new_items)
|
|
return false;
|
|
memcpy (new_items, other_items,
|
|
sizeof (other_items[0]) * achl->capacity);
|
|
}
|
|
|
|
achl->other_items = new_items;
|
|
achl->capacity = capacity;
|
|
new_items[i] = (struct acolorhist_arr_item) {
|
|
.color = px,.perceptual_weight = boost,};
|
|
achl->used++;
|
|
} else {
|
|
// these are elses for first checks whether first and second inline-stored colors are used
|
|
achl->inline2.color.l = px.l;
|
|
achl->inline2.perceptual_weight = boost;
|
|
achl->used = 2;
|
|
++colors;
|
|
}
|
|
} else {
|
|
achl->inline1.color.l = px.l;
|
|
achl->inline1.perceptual_weight = boost;
|
|
achl->used = 1;
|
|
++colors;
|
|
}
|
|
|
|
continue_outer_loop:;
|
|
}
|
|
|
|
}
|
|
acht->colors = colors;
|
|
acht->cols = cols;
|
|
acht->rows += rows;
|
|
acht->freestackp = freestackp;
|
|
return true;
|
|
}
|
|
|
|
LIQ_PRIVATE struct acolorhash_table *
|
|
pam_allocacolorhash (unsigned int maxcolors, unsigned int surface,
|
|
unsigned int ignorebits, void *(*malloc) (size_t), void (*free) (void *))
|
|
{
|
|
const unsigned int estimated_colors =
|
|
MIN (maxcolors, surface / (ignorebits + (surface > 512 * 512 ? 5 : 4)));
|
|
const unsigned int hash_size =
|
|
estimated_colors < 66000 ? 6673 : (estimated_colors <
|
|
200000 ? 12011 : 24019);
|
|
|
|
mempool m = NULL;
|
|
const unsigned int buckets_size =
|
|
hash_size * sizeof (struct acolorhist_arr_head);
|
|
const unsigned int mempool_size =
|
|
sizeof (struct acolorhash_table) + buckets_size +
|
|
estimated_colors * sizeof (struct acolorhist_arr_item);
|
|
struct acolorhash_table *t =
|
|
mempool_create (&m, sizeof (*t) + buckets_size, mempool_size, malloc,
|
|
free);
|
|
if (!t)
|
|
return NULL;
|
|
*t = (struct acolorhash_table) {
|
|
.mempool = m,.hash_size = hash_size,.maxcolors = maxcolors,.ignorebits =
|
|
ignorebits,};
|
|
memset (t->buckets, 0, hash_size * sizeof (struct acolorhist_arr_head));
|
|
return t;
|
|
}
|
|
|
|
#define PAM_ADD_TO_HIST(entry) { \
|
|
hist->achv[j].acolor = to_f(gamma_lut, entry.color.rgba); \
|
|
total_weight += hist->achv[j].adjusted_weight = hist->achv[j].perceptual_weight = MIN(entry.perceptual_weight, max_perceptual_weight); \
|
|
++j; \
|
|
}
|
|
|
|
LIQ_PRIVATE histogram *
|
|
pam_acolorhashtoacolorhist (const struct acolorhash_table * acht,
|
|
const double gamma, void *(*malloc) (size_t), void (*free) (void *))
|
|
{
|
|
histogram *hist = malloc (sizeof (hist[0]));
|
|
float gamma_lut[256];
|
|
float max_perceptual_weight;
|
|
double total_weight;
|
|
unsigned int i, j, k;
|
|
|
|
if (!hist || !acht)
|
|
return NULL;
|
|
*hist = (histogram) {
|
|
.achv = malloc (acht->colors * sizeof (hist->achv[0])),.size =
|
|
acht->colors,.free = free,.ignorebits = acht->ignorebits,};
|
|
if (!hist->achv)
|
|
return NULL;
|
|
|
|
to_f_set_gamma (gamma_lut, gamma);
|
|
|
|
/* Limit perceptual weight to 1/10th of the image surface area to prevent
|
|
a single color from dominating all others. */
|
|
max_perceptual_weight = 0.1f * acht->cols * acht->rows;
|
|
total_weight = 0;
|
|
|
|
for (j = 0, i = 0; i < acht->hash_size; ++i) {
|
|
const struct acolorhist_arr_head *const achl = &acht->buckets[i];
|
|
if (achl->used) {
|
|
PAM_ADD_TO_HIST (achl->inline1);
|
|
|
|
if (achl->used > 1) {
|
|
PAM_ADD_TO_HIST (achl->inline2);
|
|
|
|
for (k = 0; k < achl->used - 2; k++) {
|
|
PAM_ADD_TO_HIST (achl->other_items[k]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
hist->total_perceptual_weight = total_weight;
|
|
return hist;
|
|
}
|
|
|
|
|
|
LIQ_PRIVATE void
|
|
pam_freeacolorhash (struct acolorhash_table *acht)
|
|
{
|
|
mempool_destroy (acht->mempool);
|
|
}
|
|
|
|
LIQ_PRIVATE void
|
|
pam_freeacolorhist (histogram * hist)
|
|
{
|
|
hist->free (hist->achv);
|
|
hist->free (hist);
|
|
}
|
|
|
|
LIQ_PRIVATE colormap *
|
|
pam_colormap (unsigned int colors, void *(*malloc) (size_t),
|
|
void (*free) (void *))
|
|
{
|
|
const size_t colors_size = colors * sizeof (colormap_item);
|
|
colormap *map;
|
|
|
|
assert (colors > 0 && colors < 65536);
|
|
|
|
map = malloc (sizeof (colormap) + colors_size);
|
|
if (!map)
|
|
return NULL;
|
|
*map = (colormap) {
|
|
.malloc = malloc,.free = free,.subset_palette = NULL,.colors = colors,};
|
|
memset (map->palette, 0, colors_size);
|
|
return map;
|
|
}
|
|
|
|
LIQ_PRIVATE colormap *
|
|
pam_duplicate_colormap (colormap * map)
|
|
{
|
|
colormap *dupe = pam_colormap (map->colors, map->malloc, map->free);
|
|
for (unsigned int i = 0; i < map->colors; i++) {
|
|
dupe->palette[i] = map->palette[i];
|
|
}
|
|
if (map->subset_palette) {
|
|
dupe->subset_palette = pam_duplicate_colormap (map->subset_palette);
|
|
}
|
|
return dupe;
|
|
}
|
|
|
|
LIQ_PRIVATE void
|
|
pam_freecolormap (colormap * c)
|
|
{
|
|
if (c->subset_palette)
|
|
pam_freecolormap (c->subset_palette);
|
|
c->free (c);
|
|
}
|
|
|
|
LIQ_PRIVATE void
|
|
to_f_set_gamma (float gamma_lut[], const double gamma)
|
|
{
|
|
for (int i = 0; i < 256; i++) {
|
|
gamma_lut[i] = pow ((double) i / 255.0, internal_gamma / gamma);
|
|
}
|
|
}
|