3e12aff909
DXT Testing:
The modified algorithm has been tested on the Kodak test set using 64-bit build with default settings (running on Windows 10, i7-4790, 3.6GHz). All the decompressed test images are identical to the images being compressed and decompressed using original version of Crunch (revision ea9b8d8).
[Compressing Kodak set without mipmaps using DXT1 encoding]
Original: 1582222 bytes / 28.866 sec
Modified: 1468204 bytes / 11.858 sec
Improvement: 7.21% (compression ratio) / 58.92% (compression time)
[Compressing Kodak set with mipmaps using DXT1 encoding]
Original: 2065243 bytes / 36.878 sec
Modified: 1914805 bytes / 15.625 sec
Improvement: 7.28% (compression ratio) / 57.63% (compression time)
ETC Testing:
The modified algorithm has been tested on the Kodak test set using 64-bit build with default settings (running on Windows 10, i7-4790, 3.6GHz). The ETC1 quantization parameters have been selected in such a way, so that ETC1 compression gives approximately the same average Luma PSNR as the corresponding DXT1 compression (which is equal to 34.044 dB for the Kodak test set compressed without mipmaps using DXT1 encoding and default quality settings).
[Compressing Kodak set without mipmaps using ETC1 encoding]
Total size: 1607858 bytes
Total time: 17.181 sec
Average bitrate: 1.363 bpp
Average Luma PSNR: 34.050 dB
807 lines
21 KiB
C++
807 lines
21 KiB
C++
// File: crn_resampler.h
|
|
// RG: This is public domain code, originally derived from Graphics Gems 3, see: http://code.google.com/p/imageresampler/
|
|
#include "crn_core.h"
|
|
#include "crn_resampler.h"
|
|
#include "crn_resample_filters.h"
|
|
|
|
namespace crnlib {
|
|
#define resampler_assert CRNLIB_ASSERT
|
|
|
|
static inline int resampler_range_check(int v, int h) {
|
|
(void)h;
|
|
resampler_assert((v >= 0) && (v < h));
|
|
return v;
|
|
}
|
|
|
|
#ifndef max
|
|
#define max(a, b) (((a) > (b)) ? (a) : (b))
|
|
#endif
|
|
|
|
#ifndef min
|
|
#define min(a, b) (((a) < (b)) ? (a) : (b))
|
|
#endif
|
|
|
|
#ifndef TRUE
|
|
#define TRUE (1)
|
|
#endif
|
|
|
|
#ifndef FALSE
|
|
#define FALSE (0)
|
|
#endif
|
|
|
|
#define RESAMPLER_DEBUG 0
|
|
|
|
// (x mod y) with special handling for negative x values.
|
|
static inline int posmod(int x, int y) {
|
|
if (x >= 0)
|
|
return (x % y);
|
|
else {
|
|
int m = (-x) % y;
|
|
|
|
if (m != 0)
|
|
m = y - m;
|
|
|
|
return (m);
|
|
}
|
|
}
|
|
|
|
// Float to int cast with truncation.
|
|
static inline int cast_to_int(Resample_Real i) {
|
|
return (int)i;
|
|
}
|
|
|
|
/* Ensure that the contributing source sample is
|
|
* within bounds. If not, reflect, clamp, or wrap.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
// The make_clist() method generates, for all destination samples,
|
|
// the list of all source samples with non-zero weighted contributions.
|
|
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) {
|
|
typedef struct
|
|
{
|
|
// The center of the range in DISCRETE coordinates (pixel center = 0.0f).
|
|
Resample_Real center;
|
|
int left, right;
|
|
} Contrib_Bounds;
|
|
|
|
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*)crnlib_calloc(dst_x, sizeof(Contrib_List))) == NULL)
|
|
return NULL;
|
|
|
|
Pcontrib_bounds = (Contrib_Bounds*)crnlib_calloc(dst_x, sizeof(Contrib_Bounds));
|
|
if (!Pcontrib_bounds) {
|
|
crnlib_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;
|
|
|
|
/* Handle case when there are fewer destination
|
|
* samples than source samples (downsampling/minification).
|
|
*/
|
|
|
|
// stretched half width of filter
|
|
half_width = (filter_support / xscale) * filter_scale;
|
|
|
|
// Find the range of source sample(s) that will contribute to each destination sample.
|
|
|
|
for (i = 0, n = 0; i < dst_x; i++) {
|
|
// Convert from discrete to continuous coordinates, scale, then convert back to discrete.
|
|
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);
|
|
}
|
|
|
|
/* Allocate memory for contributors. */
|
|
|
|
if ((n == 0) || ((Pcpool = (Contrib*)crnlib_calloc(n, sizeof(Contrib))) == NULL)) {
|
|
crnlib_free(Pcontrib);
|
|
crnlib_free(Pcontrib_bounds);
|
|
return NULL;
|
|
}
|
|
total = n;
|
|
|
|
Pcpool_next = Pcpool;
|
|
|
|
/* Create the list of source samples which
|
|
* contribute to each destination sample.
|
|
*/
|
|
|
|
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);
|
|
resampler_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
|
|
|
|
/* Increment the number of source
|
|
* samples which contribute to the
|
|
* current destination sample.
|
|
*/
|
|
|
|
k = Pcontrib[i].n++;
|
|
|
|
Pcontrib[i].p[k].pixel = (unsigned short)n; /* store src sample number */
|
|
Pcontrib[i].p[k].weight = weight; /* store src sample weight */
|
|
|
|
total_weight += weight; /* total weight of all contributors */
|
|
|
|
if (weight > max_w) {
|
|
max_w = weight;
|
|
max_k = k;
|
|
}
|
|
}
|
|
|
|
#if RESAMPLER_DEBUG
|
|
printf("\n\n");
|
|
#endif
|
|
|
|
//resampler_assert(Pcontrib[i].n);
|
|
//resampler_assert(max_k != -1);
|
|
if ((max_k == -1) || (Pcontrib[i].n == 0)) {
|
|
crnlib_free(Pcpool);
|
|
crnlib_free(Pcontrib);
|
|
crnlib_free(Pcontrib_bounds);
|
|
return NULL;
|
|
}
|
|
|
|
if (total_weight != 1.0f)
|
|
Pcontrib[i].p[max_k].weight += 1.0f - total_weight;
|
|
}
|
|
} else {
|
|
/* Handle case when there are more
|
|
* destination samples than source
|
|
* samples (upsampling).
|
|
*/
|
|
|
|
half_width = filter_support * filter_scale;
|
|
|
|
// Find the source sample(s) that contribute to each destination sample.
|
|
|
|
for (i = 0, n = 0; i < dst_x; i++) {
|
|
// Convert from discrete to continuous coordinates, scale, then convert back to discrete.
|
|
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);
|
|
}
|
|
|
|
/* Allocate memory for contributors. */
|
|
|
|
int total = n;
|
|
if ((total == 0) || ((Pcpool = (Contrib*)crnlib_calloc(total, sizeof(Contrib))) == NULL)) {
|
|
crnlib_free(Pcontrib);
|
|
crnlib_free(Pcontrib_bounds);
|
|
return NULL;
|
|
}
|
|
|
|
Pcpool_next = Pcpool;
|
|
|
|
/* Create the list of source samples which
|
|
* contribute to each destination sample.
|
|
*/
|
|
|
|
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);
|
|
resampler_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
|
|
|
|
/* Increment the number of source
|
|
* samples which contribute to the
|
|
* current destination sample.
|
|
*/
|
|
|
|
k = Pcontrib[i].n++;
|
|
|
|
Pcontrib[i].p[k].pixel = (unsigned short)n; /* store src sample number */
|
|
Pcontrib[i].p[k].weight = weight; /* store src sample weight */
|
|
|
|
total_weight += weight; /* total weight of all contributors */
|
|
|
|
if (weight > max_w) {
|
|
max_w = weight;
|
|
max_k = k;
|
|
}
|
|
}
|
|
|
|
#if RESAMPLER_DEBUG
|
|
printf("\n\n");
|
|
#endif
|
|
|
|
//resampler_assert(Pcontrib[i].n);
|
|
//resampler_assert(max_k != -1);
|
|
|
|
if ((max_k == -1) || (Pcontrib[i].n == 0)) {
|
|
crnlib_free(Pcpool);
|
|
crnlib_free(Pcontrib);
|
|
crnlib_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
|
|
|
|
crnlib_free(Pcontrib_bounds);
|
|
|
|
return Pcontrib;
|
|
}
|
|
|
|
void Resampler::resample_x(Sample* Pdst, const Sample* Psrc) {
|
|
resampler_assert(Pdst);
|
|
resampler_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 CRNLIB_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 CRNLIB_RESAMPLER_DEBUG_OPS
|
|
total_ops += dst_x;
|
|
#endif
|
|
|
|
// Not += because temp buf wasn't cleared.
|
|
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 CRNLIB_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;
|
|
resampler_assert(Ptmp);
|
|
|
|
/* Process each contributor. */
|
|
|
|
for (i = 0; i < Pclist->n; i++) {
|
|
/* locate the contributor's location in the scan
|
|
* buffer -- the contributor must always be found!
|
|
*/
|
|
|
|
for (j = 0; j < MAX_SCAN_BUF_SIZE; j++)
|
|
if (m_Pscan_buf->scan_buf_y[j] == Pclist->p[i].pixel)
|
|
break;
|
|
|
|
resampler_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 this source line doesn't contribute to any
|
|
* more destination lines then mark the scanline buffer slot
|
|
* which holds this source line as free.
|
|
* (The max. number of slots used depends on the Y
|
|
* axis sampling factor and the scaled filter width.)
|
|
*/
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* Now generate the destination line */
|
|
|
|
if (m_delay_x_resample) // Was X resampling delayed until after Y resampling?
|
|
{
|
|
resampler_assert(Pdst != Ptmp);
|
|
resample_x(Pdst, Ptmp);
|
|
} else {
|
|
resampler_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;
|
|
|
|
/* Does this source line contribute
|
|
* to any destination line? if not,
|
|
* exit now.
|
|
*/
|
|
|
|
if (!m_Psrc_y_count[resampler_range_check(m_cur_src_y, m_resample_src_y)]) {
|
|
m_cur_src_y++;
|
|
return true;
|
|
}
|
|
|
|
/* Find an empty slot in the scanline buffer. (FIXME: Perf. is terrible here with extreme scaling ratios.) */
|
|
|
|
for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)
|
|
if (m_Pscan_buf->scan_buf_y[i] == -1)
|
|
break;
|
|
|
|
/* If the buffer is full, exit with an error. */
|
|
|
|
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;
|
|
|
|
/* Does this slot have any memory allocated to it? */
|
|
|
|
if (!m_Pscan_buf->scan_buf_l[i]) {
|
|
if ((m_Pscan_buf->scan_buf_l[i] = (Sample*)crnlib_malloc(m_intermediate_x * sizeof(Sample))) == NULL) {
|
|
m_status = STATUS_OUT_OF_MEMORY;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Resampling on the X axis first?
|
|
if (m_delay_x_resample) {
|
|
resampler_assert(m_intermediate_x == m_resample_src_x);
|
|
|
|
// Y-X resampling order
|
|
memcpy(m_Pscan_buf->scan_buf_l[i], Psrc, m_intermediate_x * sizeof(Sample));
|
|
} else {
|
|
resampler_assert(m_intermediate_x == m_resample_dst_x);
|
|
|
|
// X-Y resampling order
|
|
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 all the destination lines have been
|
|
* generated, then always return NULL.
|
|
*/
|
|
|
|
if (m_cur_dst_y == m_resample_dst_y)
|
|
return NULL;
|
|
|
|
/* Check to see if all the required
|
|
* contributors are present, if not,
|
|
* 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 CRNLIB_RESAMPLER_DEBUG_OPS
|
|
printf("actual ops: %i\n", total_ops);
|
|
#endif
|
|
|
|
crnlib_free(m_Pdst_buf);
|
|
m_Pdst_buf = NULL;
|
|
|
|
if (m_Ptmp_buf) {
|
|
crnlib_free(m_Ptmp_buf);
|
|
m_Ptmp_buf = NULL;
|
|
}
|
|
|
|
/* Don't deallocate a contibutor list
|
|
* if the user passed us one of their own.
|
|
*/
|
|
|
|
if ((m_Pclist_x) && (!m_clist_x_forced)) {
|
|
crnlib_free(m_Pclist_x->p);
|
|
crnlib_free(m_Pclist_x);
|
|
m_Pclist_x = NULL;
|
|
}
|
|
|
|
if ((m_Pclist_y) && (!m_clist_y_forced)) {
|
|
crnlib_free(m_Pclist_y->p);
|
|
crnlib_free(m_Pclist_y);
|
|
m_Pclist_y = NULL;
|
|
}
|
|
|
|
crnlib_free(m_Psrc_y_count);
|
|
m_Psrc_y_count = NULL;
|
|
|
|
crnlib_free(m_Psrc_y_flag);
|
|
m_Psrc_y_flag = NULL;
|
|
|
|
if (m_Pscan_buf) {
|
|
for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)
|
|
crnlib_free(m_Pscan_buf->scan_buf_l[i]);
|
|
|
|
crnlib_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;
|
|
|
|
crnlib_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);
|
|
|
|
resampler_assert(src_x > 0);
|
|
resampler_assert(src_y > 0);
|
|
resampler_assert(dst_x > 0);
|
|
resampler_assert(dst_y > 0);
|
|
|
|
#if CRNLIB_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*)crnlib_malloc(m_resample_dst_x * sizeof(Sample))) == NULL) {
|
|
m_status = STATUS_OUT_OF_MEMORY;
|
|
return;
|
|
}
|
|
|
|
// Find the specified filter.
|
|
|
|
if (Pfilter_name == NULL)
|
|
Pfilter_name = CRNLIB_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;
|
|
|
|
/* Create contributor lists, unless the user supplied custom lists. */
|
|
|
|
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*)crnlib_calloc(m_resample_src_y, sizeof(int))) == NULL) {
|
|
m_status = STATUS_OUT_OF_MEMORY;
|
|
return;
|
|
}
|
|
|
|
if ((m_Psrc_y_flag = (unsigned char*)crnlib_calloc(m_resample_src_y, sizeof(unsigned char))) == NULL) {
|
|
m_status = STATUS_OUT_OF_MEMORY;
|
|
return;
|
|
}
|
|
|
|
/* Count how many times each source line
|
|
* contributes to a destination line.
|
|
*/
|
|
|
|
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*)crnlib_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;
|
|
{
|
|
// Determine which axis to resample first by comparing the number of multiplies required
|
|
// for each possibility.
|
|
int x_ops = count_ops(m_Pclist_x, m_resample_dst_x);
|
|
int y_ops = count_ops(m_Pclist_y, m_resample_dst_y);
|
|
|
|
// Hack 10/2000: Weight Y axis ops a little more than X axis ops.
|
|
// (Y axis ops use more cache resources.)
|
|
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 CRNLIB_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
|
|
|
|
// Now check which resample order is better. In case of a tie, choose the order
|
|
// which buffers the least amount of data.
|
|
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 CRNLIB_RESAMPLER_DEBUG_OPS
|
|
printf("delaying: %i\n", m_delay_x_resample);
|
|
#endif
|
|
}
|
|
|
|
if (m_delay_x_resample) {
|
|
if ((m_Ptmp_buf = (Sample*)crnlib_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;
|
|
}
|
|
|
|
} // namespace crnlib
|