You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

699 lines
20 KiB

/*
* zoom.c - resize an image to an arbitrary size with filtering
* Based on "Filtered Image Rescaling" by Dale Schumacher (public domain).
*
* This file is part of transcode, a video stream processing tool.
* transcode is free software, distributable under the terms of the GNU
* General Public License (version 2 or later). See the file COPYING
* for details.
*/
#include "tcvideo.h"
#include "zoom.h"
#include "src/transcode.h"
#include <math.h>
/*************************************************************************/
/* Data structures for holding resizing data (used internally). */
/* A contributor to a single pixel */
struct contrib {
int pixel;
double weight;
};
/* List of all contributors to a single pixel */
struct clist {
int n; /* Number of contributors */
struct contrib *list; /* Pointer to list of contributors */
};
/* Data for a resize operation */
struct zoominfo {
int old_w, old_h; /* Original width and height */
int new_w, new_h; /* New width and height */
int Bpp; /* Bytes per pixel */
int old_stride; /* Bytes per line (original image) */
int new_stride; /* Bytes per line (new image) */
double (*filter)(double); /* Filter function */
double fwidth; /* Filter width */
int32_t *x_contrib; /* Contributors in the horizontal direction */
int32_t *y_contrib; /* Contributors in the vertical direction */
uint8_t *tmpimage; /* Temporary buffer */
};
/* Convert a double to a 16.16 fixed-point value */
#define DOUBLE_TO_FIXED(v) ((int32_t)((v)*65536))
/* Convert a 16.16 fixed-point value to an integer */
#define FIXED_TO_INT(v) ((v)>>16)
/*************************************************************************/
/* FIXME: use a static table for every data related to a filter,
* accessed by filter_id;
* typedef struct tcvzoomdata_ TCVZoomData;
* struct tcvzoom_data {
* const char *name;
* double support;
* double (*filter)(double t);
* };
*/
/**
* tcv_zoom_filter_to_string:
* return the human-readable name of a given filter, as string.
*
* Parameters:
* filter: Filter identifier (TCV_ZOOM_*).
* Return value:
* name of the given filter. DO NOT free() it.
* NULL if the filter is unknown/unsupported.
*/
const char *tcv_zoom_filter_to_string(TCVZoomFilter filter)
{
const char *name = NULL;
if (filter == TCV_ZOOM_BELL) {
name = "Bell";
} else if (filter == TCV_ZOOM_BOX) {
name = "Box";
} else if (filter == TCV_ZOOM_B_SPLINE) {
name = "B_spline";
} else if (filter == TCV_ZOOM_HERMITE) {
name = "Hermite";
} else if (filter == TCV_ZOOM_LANCZOS3) {
name = "Lanczos3";
} else if (filter == TCV_ZOOM_MITCHELL) {
name = "Mitchell";
} else if (filter == TCV_ZOOM_TRIANGLE) {
name = "Triangle";
} else if (filter == TCV_ZOOM_CUBIC_KEYS4) {
name = "Cubic_Keys4";
} else if (filter == TCV_ZOOM_SINC8) {
name = "Sinc8";
} else if (filter == TCV_ZOOM_DEFAULT) {
name = "Lanczos3";
} else {
name = NULL;
}
return name;
}
/**
* tcv_zoom_filter_from_string:
* return the filter id given its human-readable name, as string.
* case insensitive.
*
* Parameters:
* name: name fo the given filter.
* Return value:
* the corrisponding identifier of the given name.
* TCV_ZOOM_NULL if the filter is unknown/unsupported.
*/
TCVZoomFilter tcv_zoom_filter_from_string(const char *name)
{
TCVZoomFilter filter = TCV_ZOOM_NULL;
if (strcasecmp(name, "bell") == 0) {
filter = TCV_ZOOM_BELL;
} else if (strcasecmp(name, "box") == 0) {
filter = TCV_ZOOM_BOX;
} else if (strcasecmp(name, "b_spline") == 0) {
filter = TCV_ZOOM_B_SPLINE;
} else if (strcasecmp(name, "hermite") == 0) {
filter = TCV_ZOOM_HERMITE;
} else if (strcasecmp(name, "lanczos3") == 0) {
filter = TCV_ZOOM_LANCZOS3;
} else if (strcasecmp(name, "mitchell") == 0) {
filter = TCV_ZOOM_MITCHELL;
} else if (strcasecmp(name, "triangle") == 0) {
filter = TCV_ZOOM_TRIANGLE;
} else if (strcasecmp(name, "cubic_keys4") == 0) {
filter = TCV_ZOOM_CUBIC_KEYS4;
} else if (strcasecmp(name, "sinc8") == 0) {
filter = TCV_ZOOM_SINC8;
} else if (strcasecmp(name, "default") == 0) {
filter = TCV_ZOOM_LANCZOS3;
} else {
filter = TCV_ZOOM_NULL;
}
return filter;
}
/*************************************************************************/
/*************************************************************************/
/**
* *_filter: Filter functions for resizing.
*
* Parameters:
* t: Filter parameter.
* Return value:
* Filter result.
* Postconditions: 0 <= t && t <= 1
*/
/************************************/
static const double hermite_support = 1.0;
static double hermite_filter(double t)
{
/* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */
if (t < 0.0)
t = -t;
if (t < 1.0)
return (2.0 * t - 3.0) * t * t + 1.0;
return 0.0;
}
/************************************/
static const double box_support = 0.5;
static double box_filter(double t)
{
if ((t > -0.5) && (t <= 0.5))
return 1.0;
return 0.0;
}
/************************************/
static const double triangle_support = 1.0;
static double triangle_filter(double t)
{
if (t < 0.0)
t = -t;
if (t < 1.0)
return 1.0 - t;
return 0.0;
}
/************************************/
static const double bell_support = 1.5;
static double bell_filter(double t)
{
if (t < 0)
t = -t;
if (t < .5)
return .75 - (t * t);
if (t < 1.5) {
t = (t - 1.5);
return .5 * (t * t);
}
return 0.0;
}
/************************************/
static const double B_spline_support = 2.0;
static double B_spline_filter(double t)
{
double tt;
if (t < 0)
t = -t;
if (t < 1) {
tt = t * t;
return (.5 * tt * t) - tt + (2.0 / 3.0);
} else if (t < 2) {
t = 2 - t;
return (1.0 / 6.0) * (t * t * t);
}
return 0.0;
}
/************************************/
static const double lanczos3_support = 3.0;
#ifndef PI
# define PI 3.14159265358979323846264338327950
#endif
#define SINC(x) ((x) != 0 ? sin((x)*PI) / ((x)*PI) : 1.0)
static double lanczos3_filter(double t)
{
if (t < 0)
t = -t;
if (t < 3.0)
return SINC(t) * SINC(t/3.0);
return 0.0;
}
#undef SINC
/************************************/
static const double mitchell_support = 2.0;
#define B (1.0 / 3.0)
#define C (1.0 / 3.0)
static double mitchell_filter(double t)
{
double tt;
tt = t * t;
if (t < 0)
t = -t;
if (t < 1.0) {
t = (((12.0 - 9.0 * B - 6.0 * C) * (t * tt))
+ ((-18.0 + 12.0 * B + 6.0 * C) * tt)
+ (6.0 - 2 * B));
return t / 6.0;
} else if (t < 2.0) {
t = (((-1.0 * B - 6.0 * C) * (t * tt))
+ ((6.0 * B + 30.0 * C) * tt)
+ ((-12.0 * B - 48.0 * C) * t)
+ (8.0 * B + 24 * C));
return t / 6.0;
}
return 0.0;
}
#undef B
#undef C
/************************************/
/* Keys 4th-order Cubic */
static const double cubic_keys4_support = 3.0;
static double cubic_keys4_filter(double t)
{
if (t < 0.0)
t = -t;
if (t < 1.0)
return (3.0 + (t * t * (-7.0 + (t * 4.0)))) / 3.0;
if (t < 2.0)
return (30.0 + (t * (-59.0 + (t * (36.0 + (t * -7.0)))))) / 12.0;
if (t < 3.0)
return (-18.0 + (t * (21.0 + (t * (-8.0 + t))))) / 12.0;
return 0.0;
}
/************************************/
/* Sinc with Lanczos window, 8 cycles */
static const double sinc8_support = 8.0;
static double sinc8_filter(double t)
{
if (t < 0.0)
t = -t;
if (t == 0.0) {
return 1.0;
} else if (t < 8.0) {
double w = sin(PI*t / 8.0) / (PI*t / 8.0);
return w * sin(t*PI) / (t*PI);
} else {
return 0.0;
}
}
/*************************************************************************/
/**
* gen_contrib: Helper function to generate the list of contributors to
* each resized pixel in one direction (horizontal or vertical).
*
* Parameters:
* oldsize: Size of original image in the direction for which
* contributors are being generated.
* newsize: Size of resized image in the direction for which
* contributors are being generated.
* stride: Number of bytes between adjacent pixels in the direction
* for which contributors are being generated.
* filter: As for zoom_process().
* fwidth: As for zoom_process().
* Return value:
* A pointer to a `newsize'-element array of `struct clist' elements,
* or NULL on error (out of memory).
* Preconditions:
* oldsize > 0
* newsize > 0
* stride > 0
* filter != NULL
* fwidth > 0
*/
static struct clist *gen_contrib(int oldsize, int newsize, int stride,
double (*filter)(double), double fwidth)
{
struct clist *contrib;
double scale = (double)newsize / (double)oldsize;
double new_fwidth, fscale;
int i, j;
contrib = tc_zalloc(newsize * sizeof(struct clist));
if (scale < 1.0) {
fscale = 1.0 / scale;
} else {
fscale = 1.0;
}
new_fwidth = fwidth * fscale;
for (i = 0; i < newsize; ++i) {
double center = (double) i / scale;
int left = ceil(center - new_fwidth);
int right = floor(center + new_fwidth);
contrib[i].n = 0;
contrib[i].list = tc_zalloc((right-left+1) * sizeof(struct contrib));
for (j = left; j <= right; ++j) {
int k, n;
double weight = center - (double) j;
weight = (*filter)(weight / fscale) / fscale;
if (j < 0) {
n = -j;
} else if (j >= oldsize) {
n = (oldsize - j) + oldsize - 1;
} else {
n = j;
}
k = contrib[i].n++;
contrib[i].list[k].pixel = n*stride;
contrib[i].list[k].weight = weight;
}
}
return contrib;
}
/*************************************************************************/
/*************************************************************************/
/* External interface. */
/*************************************************************************/
/**
* zoom_init: Allocate, initialize, and return a ZoomInfo structure for
* use in subsequent zoom_process() calls. The structure should be freed
* with zoom_free() when no longer needed.
*
* Parameters:
* old_w: Width of original image.
* old_h: Height of original image.
* new_w: Width of resized image.
* new_h: Height of resized image.
* Bpp: Bytes (not bits!) per pixel.
* old_stride: Bytes per line of original image.
* new_stride: Bytes per line of resized image.
* filter: Filter identifier (TCV_ZOOM_*).
* Return value:
* A pointer to a newly allocated ZoomInfo structure, or NULL on error
* (invalid parameters or out of memory).
*/
ZoomInfo *zoom_init(int old_w, int old_h, int new_w, int new_h, int Bpp,
int old_stride, int new_stride, TCVZoomFilter filter)
{
ZoomInfo *zi;
struct clist *x_contrib = NULL, *y_contrib = NULL;
/* Sanity check */
if (old_w <= 0 || old_h <= 0 || new_w <= 0 || new_h <= 0 || Bpp <= 0
|| old_stride <= 0 || new_stride <= 0)
return NULL;
/* Allocate structure */
zi = tc_malloc(sizeof(*zi));
if (!zi)
return NULL;
/* Set up scalar members, and check filter value */
zi->old_w = old_w;
zi->old_h = old_h;
zi->new_w = new_w;
zi->new_h = new_h;
zi->Bpp = Bpp;
zi->old_stride = old_stride;
zi->new_stride = new_stride;
switch (filter) {
case TCV_ZOOM_BOX:
zi->filter = box_filter;
zi->fwidth = box_support;
break;
case TCV_ZOOM_TRIANGLE:
zi->filter = triangle_filter;
zi->fwidth = triangle_support;
break;
case TCV_ZOOM_HERMITE:
zi->filter = hermite_filter;
zi->fwidth = hermite_support;
break;
case TCV_ZOOM_BELL:
zi->filter = bell_filter;
zi->fwidth = bell_support;
break;
case TCV_ZOOM_B_SPLINE:
zi->filter = B_spline_filter;
zi->fwidth = B_spline_support;
break;
case TCV_ZOOM_MITCHELL:
zi->filter = mitchell_filter;
zi->fwidth = mitchell_support;
break;
case TCV_ZOOM_LANCZOS3:
zi->filter = lanczos3_filter;
zi->fwidth = lanczos3_support;
break;
case TCV_ZOOM_CUBIC_KEYS4:
zi->filter = cubic_keys4_filter;
zi->fwidth = cubic_keys4_support;
break;
case TCV_ZOOM_SINC8:
zi->filter = sinc8_filter;
zi->fwidth = sinc8_support;
break;
default:
free(zi);
return NULL;
}
/* Generate contributor lists and allocate temporary image buffer */
zi->x_contrib = NULL;
zi->y_contrib = NULL;
zi->tmpimage = tc_malloc(new_w * old_h * Bpp);
if (!zi->tmpimage)
goto error_out;
if (old_w != new_w) {
x_contrib = gen_contrib(old_w, new_w, Bpp, zi->filter, zi->fwidth);
if (!x_contrib)
goto error_out;
}
if (old_h != new_h) {
/* Calculate the correct stride--if the width isn't changing,
* this will just be old_stride */
int stride = (old_w==new_w) ? old_stride : Bpp*new_w;
y_contrib = gen_contrib(old_h, new_h, stride, zi->filter,
zi->fwidth);
if (!y_contrib)
goto error_out;
}
/* Convert contributor lists into flat arrays and fixed-point values.
* The flat array consists of a contributor count plus two values per
* contributor (index and fixed-point weight) for each output pixel.
* Note that for the horizontal direction, we make `Bpp' copies of the
* contributors, adjusting the offset for each byte of the pixel. */
if (x_contrib) {
int count = 0, i;
int32_t *ptr;
for (i = 0; i < new_w; i++)
count += 1 + 2 * x_contrib[i].n;
zi->x_contrib = tc_malloc(sizeof(int32_t) * count * Bpp);
if (!zi->x_contrib)
goto error_out;
for (ptr = zi->x_contrib, i = 0; i < new_w * Bpp; i++) {
int j;
*ptr++ = x_contrib[i/Bpp].n;
for (j = 0; j < x_contrib[i/Bpp].n; j++) {
*ptr++ = x_contrib[i/Bpp].list[j].pixel + i%Bpp;
*ptr++ = DOUBLE_TO_FIXED(x_contrib[i/Bpp].list[j].weight);
}
}
/* Free original contributor list */
for (i = 0; i < new_w; i++)
free(x_contrib[i].list);
free(x_contrib);
x_contrib = NULL;
}
if (y_contrib) {
int count = 0, i;
int32_t *ptr;
for (i = 0; i < new_h; i++)
count += 1 + 2 * y_contrib[i].n;
zi->y_contrib = tc_malloc(sizeof(int32_t) * count);
if (!zi->y_contrib)
goto error_out;
for (ptr = zi->y_contrib, i = 0; i < new_h; i++) {
int j;
*ptr++ = y_contrib[i].n;
for (j = 0; j < y_contrib[i].n; j++) {
*ptr++ = y_contrib[i].list[j].pixel;
*ptr++ = DOUBLE_TO_FIXED(y_contrib[i].list[j].weight);
}
}
for (i = 0; i < new_h; i++)
free(y_contrib[i].list);
free(y_contrib);
y_contrib = NULL;
}
/* Done */
return zi;
error_out:
{
if (x_contrib) {
int i;
for (i = 0; i < new_w; i++)
free(x_contrib[i].list);
free(x_contrib);
}
if (y_contrib) {
int i;
for (i = 0; i < new_w; i++)
free(x_contrib[i].list);
free(x_contrib);
}
zoom_free(zi);
return NULL;
}
}
/*************************************************************************/
/**
* zoom_process: Image resizing core.
*
* Parameters:
* zi: ZoomInfo structure allocated by zoom_init().
* src: Source data plane.
* dest: Destination data plane.
* Return value: None.
* Preconditions:
* zi was allocated by zoom_init()
* src != NULL
* dest != NULL
* src and dest do not overlap
*/
/* clamp the input to the specified range */
#define CLAMP(v,l,h) ((v)<(l) ? (l) : (v) > (h) ? (h) : (v))
void zoom_process(const ZoomInfo *zi, const uint8_t *src, uint8_t *dest)
{
int from_stride, to_stride;
const uint8_t *from;
uint8_t *to;
from = src;
from_stride = zi->old_stride;
/* Apply filter to zoom horizontally from src to tmp (if necessary) */
if (zi->x_contrib) {
int y;
to = zi->tmpimage;
to_stride = zi->new_w * zi->Bpp;
for (y = 0; y < zi->old_h; y++, from += from_stride, to += to_stride) {
int32_t *contrib = zi->x_contrib;
int x;
for (x = 0; x < zi->new_w * zi->Bpp; x++) {
int32_t weight = DOUBLE_TO_FIXED(0.5);
int n = *contrib++, i;
for (i = 0; i < n; i++) {
int pixel = *contrib++;
weight += from[pixel] * (*contrib++);
}
to[x] = CLAMP(FIXED_TO_INT(weight), 0, 255);
}
}
from = zi->tmpimage;
from_stride = to_stride;
}
/* Apply filter to zoom vertically from tmp (or src) to dest */
/* Use Y as the outside loop to avoid cache thrashing on output buffer */
to = dest;
to_stride = zi->new_stride;
if (zi->y_contrib) {
int32_t *contrib = zi->y_contrib;
int y;
for (y = 0; y < zi->new_h; y++, to += to_stride) {
int n = *contrib++, x;
for (x = 0; x < zi->new_w * zi->Bpp; x++) {
int32_t weight = DOUBLE_TO_FIXED(0.5);
int i;
for (i = 0; i < n; i++) {
int pixel = contrib[i*2];
weight += from[x+pixel] * contrib[i*2+1];
}
to[x] = CLAMP(FIXED_TO_INT(weight), 0, 255);
}
contrib += 2*n;
}
} else {
/* No zooming necessary, just copy */
if (from_stride == zi->new_w*zi->Bpp
&& to_stride == zi->new_w*zi->Bpp
) {
/* We can copy the whole frame at once */
ac_memcpy(to, from, to_stride * zi->new_h);
} else {
/* Copy one row at a time */
int y;
for (y = 0; y < zi->new_h; y++) {
ac_memcpy(to + y*to_stride, from + y*from_stride,
zi->new_w * zi->Bpp);
}
}
}
}
/*************************************************************************/
/**
* zoom_free(): Free a ZoomInfo structure.
*
* Parameters:
* zi: ZoomInfo structure allocated by zoom_init().
* Return value:
* None.
* Preconditions:
* zi was allocated by zoom_init()
* Postconditions:
* zi is freed
*/
void zoom_free(ZoomInfo *zi)
{
free(zi->x_contrib);
free(zi->y_contrib);
free(zi->tmpimage);
free(zi);
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/