#include "basisu_resampler.h"
#include "basisu_resampler_filters.h"
#define RESAMPLER_DEBUG 0
namespace basisu
{
static inline int resampler_range_check(int v, int h)
{
BASISU_NOTE_UNUSED(h);
assert((v >= 0) && (v < h));
return v;
}
static inline int cast_to_int(Resample_Real i)
{
return (int)i;
}
int Resampler::reflect(const int j, const int src_x, const Boundary_Op boundary_op)
{
int n;
if (j < 0)
{
if (boundary_op == BOUNDARY_REFLECT)
{
n = -j;
if (n >= src_x)
n = src_x - 1;
}
else if (boundary_op == BOUNDARY_WRAP)
n = posmod(j, src_x);
else
n = 0;
}
else if (j >= src_x)
{
if (boundary_op == BOUNDARY_REFLECT)
{
n = (src_x - j) + (src_x - 1);
if (n < 0)
n = 0;
}
else if (boundary_op == BOUNDARY_WRAP)
n = posmod(j, src_x);
else
n = src_x - 1;
}
else
n = j;
return n;
}
Resampler::Contrib_List * Resampler::make_clist(
int src_x, int dst_x, Boundary_Op boundary_op,
Resample_Real(*Pfilter)(Resample_Real),
Resample_Real filter_support,
Resample_Real filter_scale,
Resample_Real src_ofs)
{
struct Contrib_Bounds
{
Resample_Real center;
int left, right;
};
int i, j, k, n, left, right;
Resample_Real total_weight;
Resample_Real xscale, center, half_width, weight;
Contrib_List* Pcontrib;
Contrib* Pcpool;
Contrib* Pcpool_next;
Contrib_Bounds* Pcontrib_bounds;
if ((Pcontrib = (Contrib_List*)calloc(dst_x, sizeof(Contrib_List))) == NULL)
return NULL;
Pcontrib_bounds = (Contrib_Bounds*)calloc(dst_x, sizeof(Contrib_Bounds));
if (!Pcontrib_bounds)
{
free(Pcontrib);
return (NULL);
}
const Resample_Real oo_filter_scale = 1.0f / filter_scale;
const Resample_Real NUDGE = 0.5f;
xscale = dst_x / (Resample_Real)src_x;
if (xscale < 1.0f)
{
int total;
(void)total;
half_width = (filter_support / xscale) * filter_scale;
for (i = 0, n = 0; i < dst_x; i++)
{
center = ((Resample_Real)i + NUDGE) / xscale;
center -= NUDGE;
center += src_ofs;
left = cast_to_int((Resample_Real)floor(center - half_width));
right = cast_to_int((Resample_Real)ceil(center + half_width));
Pcontrib_bounds[i].center = center;
Pcontrib_bounds[i].left = left;
Pcontrib_bounds[i].right = right;
n += (right - left + 1);
}
if ((n == 0) || ((Pcpool = (Contrib*)calloc(n, sizeof(Contrib))) == NULL))
{
free(Pcontrib);
free(Pcontrib_bounds);
return NULL;
}
total = n;
Pcpool_next = Pcpool;
for (i = 0; i < dst_x; i++)
{
int max_k = -1;
Resample_Real max_w = -1e+20f;
center = Pcontrib_bounds[i].center;
left = Pcontrib_bounds[i].left;
right = Pcontrib_bounds[i].right;
Pcontrib[i].n = 0;
Pcontrib[i].p = Pcpool_next;
Pcpool_next += (right - left + 1);
assert((Pcpool_next - Pcpool) <= total);
total_weight = 0;
for (j = left; j <= right; j++)
total_weight += (*Pfilter)((center - (Resample_Real)j) * xscale * oo_filter_scale);
const Resample_Real norm = static_cast<Resample_Real>(1.0f / total_weight);
total_weight = 0;
#if RESAMPLER_DEBUG
printf("%i: ", i);
#endif
for (j = left; j <= right; j++)
{
weight = (*Pfilter)((center - (Resample_Real)j) * xscale * oo_filter_scale) * norm;
if (weight == 0.0f)
continue;
n = reflect(j, src_x, boundary_op);
#if RESAMPLER_DEBUG
printf("%i(%f), ", n, weight);
#endif
k = Pcontrib[i].n++;
Pcontrib[i].p[k].pixel = (unsigned short)n;
Pcontrib[i].p[k].weight = weight;
total_weight += weight;
if (weight > max_w)
{
max_w = weight;
max_k = k;
}
}
#if RESAMPLER_DEBUG
printf("\n\n");
#endif
if ((max_k == -1) || (Pcontrib[i].n == 0))
{
free(Pcpool);
free(Pcontrib);
free(Pcontrib_bounds);
return NULL;
}
if (total_weight != 1.0f)
Pcontrib[i].p[max_k].weight += 1.0f - total_weight;
}
}
else
{
half_width = filter_support * filter_scale;
for (i = 0, n = 0; i < dst_x; i++)
{
center = ((Resample_Real)i + NUDGE) / xscale;
center -= NUDGE;
center += src_ofs;
left = cast_to_int((Resample_Real)floor(center - half_width));
right = cast_to_int((Resample_Real)ceil(center + half_width));
Pcontrib_bounds[i].center = center;
Pcontrib_bounds[i].left = left;
Pcontrib_bounds[i].right = right;
n += (right - left + 1);
}
int total = n;
if ((total == 0) || ((Pcpool = (Contrib*)calloc(total, sizeof(Contrib))) == NULL))
{
free(Pcontrib);
free(Pcontrib_bounds);
return NULL;
}
Pcpool_next = Pcpool;
for (i = 0; i < dst_x; i++)
{
int max_k = -1;
Resample_Real max_w = -1e+20f;
center = Pcontrib_bounds[i].center;
left = Pcontrib_bounds[i].left;
right = Pcontrib_bounds[i].right;
Pcontrib[i].n = 0;
Pcontrib[i].p = Pcpool_next;
Pcpool_next += (right - left + 1);
assert((Pcpool_next - Pcpool) <= total);
total_weight = 0;
for (j = left; j <= right; j++)
total_weight += (*Pfilter)((center - (Resample_Real)j) * oo_filter_scale);
const Resample_Real norm = static_cast<Resample_Real>(1.0f / total_weight);
total_weight = 0;
#if RESAMPLER_DEBUG
printf("%i: ", i);
#endif
for (j = left; j <= right; j++)
{
weight = (*Pfilter)((center - (Resample_Real)j) * oo_filter_scale) * norm;
if (weight == 0.0f)
continue;
n = reflect(j, src_x, boundary_op);
#if RESAMPLER_DEBUG
printf("%i(%f), ", n, weight);
#endif
k = Pcontrib[i].n++;
Pcontrib[i].p[k].pixel = (unsigned short)n;
Pcontrib[i].p[k].weight = weight;
total_weight += weight;
if (weight > max_w)
{
max_w = weight;
max_k = k;
}
}
#if RESAMPLER_DEBUG
printf("\n\n");
#endif
if ((max_k == -1) || (Pcontrib[i].n == 0))
{
free(Pcpool);
free(Pcontrib);
free(Pcontrib_bounds);
return NULL;
}
if (total_weight != 1.0f)
Pcontrib[i].p[max_k].weight += 1.0f - total_weight;
}
}
#if RESAMPLER_DEBUG
printf("*******\n");
#endif
free(Pcontrib_bounds);
return Pcontrib;
}
void Resampler::resample_x(Sample * Pdst, const Sample * Psrc)
{
assert(Pdst);
assert(Psrc);
int i, j;
Sample total;
Contrib_List* Pclist = m_Pclist_x;
Contrib* p;
for (i = m_resample_dst_x; i > 0; i--, Pclist++)
{
#if BASISU_RESAMPLER_DEBUG_OPS
total_ops += Pclist->n;
#endif
for (j = Pclist->n, p = Pclist->p, total = 0; j > 0; j--, p++)
total += Psrc[p->pixel] * p->weight;
*Pdst++ = total;
}
}
void Resampler::scale_y_mov(Sample * Ptmp, const Sample * Psrc, Resample_Real weight, int dst_x)
{
int i;
#if BASISU_RESAMPLER_DEBUG_OPS
total_ops += dst_x;
#endif
for (i = dst_x; i > 0; i--)
* Ptmp++ = *Psrc++ * weight;
}
void Resampler::scale_y_add(Sample * Ptmp, const Sample * Psrc, Resample_Real weight, int dst_x)
{
#if BASISU_RESAMPLER_DEBUG_OPS
total_ops += dst_x;
#endif
for (int i = dst_x; i > 0; i--)
(*Ptmp++) += *Psrc++ * weight;
}
void Resampler::clamp(Sample * Pdst, int n)
{
while (n > 0)
{
Sample x = *Pdst;
*Pdst++ = clamp_sample(x);
n--;
}
}
void Resampler::resample_y(Sample * Pdst)
{
int i, j;
Sample* Psrc;
Contrib_List* Pclist = &m_Pclist_y[m_cur_dst_y];
Sample* Ptmp = m_delay_x_resample ? m_Ptmp_buf : Pdst;
assert(Ptmp);
for (i = 0; i < Pclist->n; i++)
{
for (j = 0; j < MAX_SCAN_BUF_SIZE; j++)
if (m_Pscan_buf->scan_buf_y[j] == Pclist->p[i].pixel)
break;
assert(j < MAX_SCAN_BUF_SIZE);
Psrc = m_Pscan_buf->scan_buf_l[j];
if (!i)
scale_y_mov(Ptmp, Psrc, Pclist->p[i].weight, m_intermediate_x);
else
scale_y_add(Ptmp, Psrc, Pclist->p[i].weight, m_intermediate_x);
if (--m_Psrc_y_count[resampler_range_check(Pclist->p[i].pixel, m_resample_src_y)] == 0)
{
m_Psrc_y_flag[resampler_range_check(Pclist->p[i].pixel, m_resample_src_y)] = false;
m_Pscan_buf->scan_buf_y[j] = -1;
}
}
if (m_delay_x_resample) {
assert(Pdst != Ptmp);
resample_x(Pdst, Ptmp);
}
else
{
assert(Pdst == Ptmp);
}
if (m_lo < m_hi)
clamp(Pdst, m_resample_dst_x);
}
bool Resampler::put_line(const Sample * Psrc)
{
int i;
if (m_cur_src_y >= m_resample_src_y)
return false;
if (!m_Psrc_y_count[resampler_range_check(m_cur_src_y, m_resample_src_y)])
{
m_cur_src_y++;
return true;
}
for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)
if (m_Pscan_buf->scan_buf_y[i] == -1)
break;
if (i == MAX_SCAN_BUF_SIZE)
{
m_status = STATUS_SCAN_BUFFER_FULL;
return false;
}
m_Psrc_y_flag[resampler_range_check(m_cur_src_y, m_resample_src_y)] = true;
m_Pscan_buf->scan_buf_y[i] = m_cur_src_y;
if (!m_Pscan_buf->scan_buf_l[i])
{
if ((m_Pscan_buf->scan_buf_l[i] = (Sample*)malloc(m_intermediate_x * sizeof(Sample))) == NULL)
{
m_status = STATUS_OUT_OF_MEMORY;
return false;
}
}
if (m_delay_x_resample)
{
assert(m_intermediate_x == m_resample_src_x);
memcpy(m_Pscan_buf->scan_buf_l[i], Psrc, m_intermediate_x * sizeof(Sample));
}
else
{
assert(m_intermediate_x == m_resample_dst_x);
resample_x(m_Pscan_buf->scan_buf_l[i], Psrc);
}
m_cur_src_y++;
return true;
}
const Resampler::Sample* Resampler::get_line()
{
int i;
if (m_cur_dst_y == m_resample_dst_y)
return NULL;
for (i = 0; i < m_Pclist_y[m_cur_dst_y].n; i++)
if (!m_Psrc_y_flag[resampler_range_check(m_Pclist_y[m_cur_dst_y].p[i].pixel, m_resample_src_y)])
return NULL;
resample_y(m_Pdst_buf);
m_cur_dst_y++;
return m_Pdst_buf;
}
Resampler::~Resampler()
{
int i;
#if BASISU_RESAMPLER_DEBUG_OPS
printf("actual ops: %i\n", total_ops);
#endif
free(m_Pdst_buf);
m_Pdst_buf = NULL;
if (m_Ptmp_buf)
{
free(m_Ptmp_buf);
m_Ptmp_buf = NULL;
}
if ((m_Pclist_x) && (!m_clist_x_forced))
{
free(m_Pclist_x->p);
free(m_Pclist_x);
m_Pclist_x = NULL;
}
if ((m_Pclist_y) && (!m_clist_y_forced))
{
free(m_Pclist_y->p);
free(m_Pclist_y);
m_Pclist_y = NULL;
}
free(m_Psrc_y_count);
m_Psrc_y_count = NULL;
free(m_Psrc_y_flag);
m_Psrc_y_flag = NULL;
if (m_Pscan_buf)
{
for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)
free(m_Pscan_buf->scan_buf_l[i]);
free(m_Pscan_buf);
m_Pscan_buf = NULL;
}
}
void Resampler::restart()
{
if (STATUS_OKAY != m_status)
return;
m_cur_src_y = m_cur_dst_y = 0;
int i, j;
for (i = 0; i < m_resample_src_y; i++)
{
m_Psrc_y_count[i] = 0;
m_Psrc_y_flag[i] = false;
}
for (i = 0; i < m_resample_dst_y; i++)
{
for (j = 0; j < m_Pclist_y[i].n; j++)
m_Psrc_y_count[resampler_range_check(m_Pclist_y[i].p[j].pixel, m_resample_src_y)]++;
}
for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)
{
m_Pscan_buf->scan_buf_y[i] = -1;
free(m_Pscan_buf->scan_buf_l[i]);
m_Pscan_buf->scan_buf_l[i] = NULL;
}
}
Resampler::Resampler(int src_x, int src_y,
int dst_x, int dst_y,
Boundary_Op boundary_op,
Resample_Real sample_low, Resample_Real sample_high,
const char* Pfilter_name,
Contrib_List * Pclist_x,
Contrib_List * Pclist_y,
Resample_Real filter_x_scale,
Resample_Real filter_y_scale,
Resample_Real src_x_ofs,
Resample_Real src_y_ofs)
{
int i, j;
Resample_Real support, (*func)(Resample_Real);
assert(src_x > 0);
assert(src_y > 0);
assert(dst_x > 0);
assert(dst_y > 0);
#if BASISU_RESAMPLER_DEBUG_OPS
total_ops = 0;
#endif
m_lo = sample_low;
m_hi = sample_high;
m_delay_x_resample = false;
m_intermediate_x = 0;
m_Pdst_buf = NULL;
m_Ptmp_buf = NULL;
m_clist_x_forced = false;
m_Pclist_x = NULL;
m_clist_y_forced = false;
m_Pclist_y = NULL;
m_Psrc_y_count = NULL;
m_Psrc_y_flag = NULL;
m_Pscan_buf = NULL;
m_status = STATUS_OKAY;
m_resample_src_x = src_x;
m_resample_src_y = src_y;
m_resample_dst_x = dst_x;
m_resample_dst_y = dst_y;
m_boundary_op = boundary_op;
if ((m_Pdst_buf = (Sample*)malloc(m_resample_dst_x * sizeof(Sample))) == NULL)
{
m_status = STATUS_OUT_OF_MEMORY;
return;
}
if (Pfilter_name == NULL)
Pfilter_name = BASISU_RESAMPLER_DEFAULT_FILTER;
for (i = 0; i < g_num_resample_filters; i++)
if (strcmp(Pfilter_name, g_resample_filters[i].name) == 0)
break;
if (i == g_num_resample_filters)
{
m_status = STATUS_BAD_FILTER_NAME;
return;
}
func = g_resample_filters[i].func;
support = g_resample_filters[i].support;
if (!Pclist_x)
{
m_Pclist_x = make_clist(m_resample_src_x, m_resample_dst_x, m_boundary_op, func, support, filter_x_scale, src_x_ofs);
if (!m_Pclist_x)
{
m_status = STATUS_OUT_OF_MEMORY;
return;
}
}
else
{
m_Pclist_x = Pclist_x;
m_clist_x_forced = true;
}
if (!Pclist_y)
{
m_Pclist_y = make_clist(m_resample_src_y, m_resample_dst_y, m_boundary_op, func, support, filter_y_scale, src_y_ofs);
if (!m_Pclist_y)
{
m_status = STATUS_OUT_OF_MEMORY;
return;
}
}
else
{
m_Pclist_y = Pclist_y;
m_clist_y_forced = true;
}
if ((m_Psrc_y_count = (int*)calloc(m_resample_src_y, sizeof(int))) == NULL)
{
m_status = STATUS_OUT_OF_MEMORY;
return;
}
if ((m_Psrc_y_flag = (unsigned char*)calloc(m_resample_src_y, sizeof(unsigned char))) == NULL)
{
m_status = STATUS_OUT_OF_MEMORY;
return;
}
for (i = 0; i < m_resample_dst_y; i++)
for (j = 0; j < m_Pclist_y[i].n; j++)
m_Psrc_y_count[resampler_range_check(m_Pclist_y[i].p[j].pixel, m_resample_src_y)]++;
if ((m_Pscan_buf = (Scan_Buf*)malloc(sizeof(Scan_Buf))) == NULL)
{
m_status = STATUS_OUT_OF_MEMORY;
return;
}
for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)
{
m_Pscan_buf->scan_buf_y[i] = -1;
m_Pscan_buf->scan_buf_l[i] = NULL;
}
m_cur_src_y = m_cur_dst_y = 0;
{
int x_ops = count_ops(m_Pclist_x, m_resample_dst_x);
int y_ops = count_ops(m_Pclist_y, m_resample_dst_y);
int xy_ops = x_ops * m_resample_src_y +
(4 * y_ops * m_resample_dst_x) / 3;
int yx_ops = (4 * y_ops * m_resample_src_x) / 3 +
x_ops * m_resample_dst_y;
#if BASISU_RESAMPLER_DEBUG_OPS
printf("src: %i %i\n", m_resample_src_x, m_resample_src_y);
printf("dst: %i %i\n", m_resample_dst_x, m_resample_dst_y);
printf("x_ops: %i\n", x_ops);
printf("y_ops: %i\n", y_ops);
printf("xy_ops: %i\n", xy_ops);
printf("yx_ops: %i\n", yx_ops);
#endif
if ((xy_ops > yx_ops) ||
((xy_ops == yx_ops) && (m_resample_src_x < m_resample_dst_x)))
{
m_delay_x_resample = true;
m_intermediate_x = m_resample_src_x;
}
else
{
m_delay_x_resample = false;
m_intermediate_x = m_resample_dst_x;
}
#if BASISU_RESAMPLER_DEBUG_OPS
printf("delaying: %i\n", m_delay_x_resample);
#endif
}
if (m_delay_x_resample)
{
if ((m_Ptmp_buf = (Sample*)malloc(m_intermediate_x * sizeof(Sample))) == NULL)
{
m_status = STATUS_OUT_OF_MEMORY;
return;
}
}
}
void Resampler::get_clists(Contrib_List * *ptr_clist_x, Contrib_List * *ptr_clist_y)
{
if (ptr_clist_x)
* ptr_clist_x = m_Pclist_x;
if (ptr_clist_y)
* ptr_clist_y = m_Pclist_y;
}
int Resampler::get_filter_num()
{
return g_num_resample_filters;
}
const char* Resampler::get_filter_name(int filter_num)
{
if ((filter_num < 0) || (filter_num >= g_num_resample_filters))
return NULL;
else
return g_resample_filters[filter_num].name;
}
}