gstreamer/gst/dvbsubenc/libimagequant/pam.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

323 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);
}
}