Path: blob/master/thirdparty/basis_universal/encoder/basisu_resampler.cpp
9902 views
// basisu_resampler.cpp1// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.2//3// Licensed under the Apache License, Version 2.0 (the "License");4// you may not use this file except in compliance with the License.5// You may obtain a copy of the License at6//7// http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// distributed under the License is distributed on an "AS IS" BASIS,11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12// See the License for the specific language governing permissions and13// limitations under the License.14#include "basisu_resampler.h"15#include "basisu_resampler_filters.h"1617#define RESAMPLER_DEBUG 01819namespace basisu20{21static inline int resampler_range_check(int v, int h)22{23BASISU_NOTE_UNUSED(h);24assert((v >= 0) && (v < h));25return v;26}2728// Float to int cast with truncation.29static inline int cast_to_int(Resample_Real i)30{31return (int)i;32}3334// Ensure that the contributing source sample is within bounds. If not, reflect, clamp, or wrap.35int Resampler::reflect(const int j, const int src_x, const Boundary_Op boundary_op)36{37int n;3839if (j < 0)40{41if (boundary_op == BOUNDARY_REFLECT)42{43n = -j;4445if (n >= src_x)46n = src_x - 1;47}48else if (boundary_op == BOUNDARY_WRAP)49n = posmod(j, src_x);50else51n = 0;52}53else if (j >= src_x)54{55if (boundary_op == BOUNDARY_REFLECT)56{57n = (src_x - j) + (src_x - 1);5859if (n < 0)60n = 0;61}62else if (boundary_op == BOUNDARY_WRAP)63n = posmod(j, src_x);64else65n = src_x - 1;66}67else68n = j;6970return n;71}7273// The make_clist() method generates, for all destination samples,74// the list of all source samples with non-zero weighted contributions.75Resampler::Contrib_List * Resampler::make_clist(76int src_x, int dst_x, Boundary_Op boundary_op,77Resample_Real(*Pfilter)(Resample_Real),78Resample_Real filter_support,79Resample_Real filter_scale,80Resample_Real src_ofs)81{82struct Contrib_Bounds83{84// The center of the range in DISCRETE coordinates (pixel center = 0.0f).85Resample_Real center;86int left, right;87};8889int i, j, k, n, left, right;90Resample_Real total_weight;91Resample_Real xscale, center, half_width, weight;92Contrib_List* Pcontrib;93Contrib* Pcpool;94Contrib* Pcpool_next;95Contrib_Bounds* Pcontrib_bounds;9697if ((Pcontrib = (Contrib_List*)calloc(dst_x, sizeof(Contrib_List))) == NULL)98return NULL;99100Pcontrib_bounds = (Contrib_Bounds*)calloc(dst_x, sizeof(Contrib_Bounds));101if (!Pcontrib_bounds)102{103free(Pcontrib);104return (NULL);105}106107const Resample_Real oo_filter_scale = 1.0f / filter_scale;108109const Resample_Real NUDGE = 0.5f;110xscale = dst_x / (Resample_Real)src_x;111112if (xscale < 1.0f)113{114int total;115(void)total;116117// Handle case when there are fewer destination samples than source samples (downsampling/minification).118119// stretched half width of filter120half_width = (filter_support / xscale) * filter_scale;121122// Find the range of source sample(s) that will contribute to each destination sample.123124for (i = 0, n = 0; i < dst_x; i++)125{126// Convert from discrete to continuous coordinates, scale, then convert back to discrete.127center = ((Resample_Real)i + NUDGE) / xscale;128center -= NUDGE;129center += src_ofs;130131left = cast_to_int((Resample_Real)floor(center - half_width));132right = cast_to_int((Resample_Real)ceil(center + half_width));133134Pcontrib_bounds[i].center = center;135Pcontrib_bounds[i].left = left;136Pcontrib_bounds[i].right = right;137138n += (right - left + 1);139}140141// Allocate memory for contributors.142143if ((n == 0) || ((Pcpool = (Contrib*)calloc(n, sizeof(Contrib))) == NULL))144{145free(Pcontrib);146free(Pcontrib_bounds);147return NULL;148}149total = n;150151Pcpool_next = Pcpool;152153// Create the list of source samples which contribute to each destination sample.154155for (i = 0; i < dst_x; i++)156{157int max_k = -1;158Resample_Real max_w = -1e+20f;159160center = Pcontrib_bounds[i].center;161left = Pcontrib_bounds[i].left;162right = Pcontrib_bounds[i].right;163164Pcontrib[i].n = 0;165Pcontrib[i].p = Pcpool_next;166Pcpool_next += (right - left + 1);167assert((Pcpool_next - Pcpool) <= total);168169total_weight = 0;170171for (j = left; j <= right; j++)172total_weight += (*Pfilter)((center - (Resample_Real)j) * xscale * oo_filter_scale);173const Resample_Real norm = static_cast<Resample_Real>(1.0f / total_weight);174175total_weight = 0;176177#if RESAMPLER_DEBUG178printf("%i: ", i);179#endif180181for (j = left; j <= right; j++)182{183weight = (*Pfilter)((center - (Resample_Real)j) * xscale * oo_filter_scale) * norm;184if (weight == 0.0f)185continue;186187n = reflect(j, src_x, boundary_op);188189#if RESAMPLER_DEBUG190printf("%i(%f), ", n, weight);191#endif192193// Increment the number of source samples which contribute to the current destination sample.194195k = Pcontrib[i].n++;196197Pcontrib[i].p[k].pixel = (unsigned short)n; /* store src sample number */198Pcontrib[i].p[k].weight = weight; /* store src sample weight */199200total_weight += weight; /* total weight of all contributors */201202if (weight > max_w)203{204max_w = weight;205max_k = k;206}207}208209#if RESAMPLER_DEBUG210printf("\n\n");211#endif212213//assert(Pcontrib[i].n);214//assert(max_k != -1);215if ((max_k == -1) || (Pcontrib[i].n == 0))216{217free(Pcpool);218free(Pcontrib);219free(Pcontrib_bounds);220return NULL;221}222223if (total_weight != 1.0f)224Pcontrib[i].p[max_k].weight += 1.0f - total_weight;225}226}227else228{229// Handle case when there are more destination samples than source samples (upsampling).230231half_width = filter_support * filter_scale;232233// Find the source sample(s) that contribute to each destination sample.234235for (i = 0, n = 0; i < dst_x; i++)236{237// Convert from discrete to continuous coordinates, scale, then convert back to discrete.238center = ((Resample_Real)i + NUDGE) / xscale;239center -= NUDGE;240center += src_ofs;241242left = cast_to_int((Resample_Real)floor(center - half_width));243right = cast_to_int((Resample_Real)ceil(center + half_width));244245Pcontrib_bounds[i].center = center;246Pcontrib_bounds[i].left = left;247Pcontrib_bounds[i].right = right;248249n += (right - left + 1);250}251252/* Allocate memory for contributors. */253254int total = n;255if ((total == 0) || ((Pcpool = (Contrib*)calloc(total, sizeof(Contrib))) == NULL))256{257free(Pcontrib);258free(Pcontrib_bounds);259return NULL;260}261262Pcpool_next = Pcpool;263264// Create the list of source samples which contribute to each destination sample.265266for (i = 0; i < dst_x; i++)267{268int max_k = -1;269Resample_Real max_w = -1e+20f;270271center = Pcontrib_bounds[i].center;272left = Pcontrib_bounds[i].left;273right = Pcontrib_bounds[i].right;274275Pcontrib[i].n = 0;276Pcontrib[i].p = Pcpool_next;277Pcpool_next += (right - left + 1);278assert((Pcpool_next - Pcpool) <= total);279280total_weight = 0;281for (j = left; j <= right; j++)282total_weight += (*Pfilter)((center - (Resample_Real)j) * oo_filter_scale);283284const Resample_Real norm = static_cast<Resample_Real>(1.0f / total_weight);285286total_weight = 0;287288#if RESAMPLER_DEBUG289printf("%i: ", i);290#endif291292for (j = left; j <= right; j++)293{294weight = (*Pfilter)((center - (Resample_Real)j) * oo_filter_scale) * norm;295if (weight == 0.0f)296continue;297298n = reflect(j, src_x, boundary_op);299300#if RESAMPLER_DEBUG301printf("%i(%f), ", n, weight);302#endif303304// Increment the number of source samples which contribute to the current destination sample.305306k = Pcontrib[i].n++;307308Pcontrib[i].p[k].pixel = (unsigned short)n; /* store src sample number */309Pcontrib[i].p[k].weight = weight; /* store src sample weight */310311total_weight += weight; /* total weight of all contributors */312313if (weight > max_w)314{315max_w = weight;316max_k = k;317}318}319320#if RESAMPLER_DEBUG321printf("\n\n");322#endif323324//assert(Pcontrib[i].n);325//assert(max_k != -1);326327if ((max_k == -1) || (Pcontrib[i].n == 0))328{329free(Pcpool);330free(Pcontrib);331free(Pcontrib_bounds);332return NULL;333}334335if (total_weight != 1.0f)336Pcontrib[i].p[max_k].weight += 1.0f - total_weight;337}338}339340#if RESAMPLER_DEBUG341printf("*******\n");342#endif343344free(Pcontrib_bounds);345346return Pcontrib;347}348349void Resampler::resample_x(Sample * Pdst, const Sample * Psrc)350{351assert(Pdst);352assert(Psrc);353354int i, j;355Sample total;356Contrib_List* Pclist = m_Pclist_x;357Contrib* p;358359for (i = m_resample_dst_x; i > 0; i--, Pclist++)360{361#if BASISU_RESAMPLER_DEBUG_OPS362total_ops += Pclist->n;363#endif364365for (j = Pclist->n, p = Pclist->p, total = 0; j > 0; j--, p++)366total += Psrc[p->pixel] * p->weight;367368*Pdst++ = total;369}370}371372void Resampler::scale_y_mov(Sample * Ptmp, const Sample * Psrc, Resample_Real weight, int dst_x)373{374int i;375376#if BASISU_RESAMPLER_DEBUG_OPS377total_ops += dst_x;378#endif379380// Not += because temp buf wasn't cleared.381for (i = dst_x; i > 0; i--)382* Ptmp++ = *Psrc++ * weight;383}384385void Resampler::scale_y_add(Sample * Ptmp, const Sample * Psrc, Resample_Real weight, int dst_x)386{387#if BASISU_RESAMPLER_DEBUG_OPS388total_ops += dst_x;389#endif390391for (int i = dst_x; i > 0; i--)392(*Ptmp++) += *Psrc++ * weight;393}394395void Resampler::clamp(Sample * Pdst, int n)396{397while (n > 0)398{399Sample x = *Pdst;400*Pdst++ = clamp_sample(x);401n--;402}403}404405void Resampler::resample_y(Sample * Pdst)406{407int i, j;408Sample* Psrc;409Contrib_List* Pclist = &m_Pclist_y[m_cur_dst_y];410411Sample* Ptmp = m_delay_x_resample ? m_Ptmp_buf : Pdst;412assert(Ptmp);413414/* Process each contributor. */415416for (i = 0; i < Pclist->n; i++)417{418// locate the contributor's location in the scan buffer -- the contributor must always be found!419for (j = 0; j < MAX_SCAN_BUF_SIZE; j++)420if (m_Pscan_buf->scan_buf_y[j] == Pclist->p[i].pixel)421break;422423assert(j < MAX_SCAN_BUF_SIZE);424425Psrc = m_Pscan_buf->scan_buf_l[j];426427if (!i)428scale_y_mov(Ptmp, Psrc, Pclist->p[i].weight, m_intermediate_x);429else430scale_y_add(Ptmp, Psrc, Pclist->p[i].weight, m_intermediate_x);431432/* If this source line doesn't contribute to any433* more destination lines then mark the scanline buffer slot434* which holds this source line as free.435* (The max. number of slots used depends on the Y436* axis sampling factor and the scaled filter width.)437*/438439if (--m_Psrc_y_count[resampler_range_check(Pclist->p[i].pixel, m_resample_src_y)] == 0)440{441m_Psrc_y_flag[resampler_range_check(Pclist->p[i].pixel, m_resample_src_y)] = false;442m_Pscan_buf->scan_buf_y[j] = -1;443}444}445446/* Now generate the destination line */447448if (m_delay_x_resample) // Was X resampling delayed until after Y resampling?449{450assert(Pdst != Ptmp);451resample_x(Pdst, Ptmp);452}453else454{455assert(Pdst == Ptmp);456}457458if (m_lo < m_hi)459clamp(Pdst, m_resample_dst_x);460}461462bool Resampler::put_line(const Sample * Psrc)463{464int i;465466if (m_cur_src_y >= m_resample_src_y)467return false;468469/* Does this source line contribute470* to any destination line? if not,471* exit now.472*/473474if (!m_Psrc_y_count[resampler_range_check(m_cur_src_y, m_resample_src_y)])475{476m_cur_src_y++;477return true;478}479480/* Find an empty slot in the scanline buffer. (FIXME: Perf. is terrible here with extreme scaling ratios.) */481482for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)483if (m_Pscan_buf->scan_buf_y[i] == -1)484break;485486/* If the buffer is full, exit with an error. */487488if (i == MAX_SCAN_BUF_SIZE)489{490m_status = STATUS_SCAN_BUFFER_FULL;491return false;492}493494m_Psrc_y_flag[resampler_range_check(m_cur_src_y, m_resample_src_y)] = true;495m_Pscan_buf->scan_buf_y[i] = m_cur_src_y;496497/* Does this slot have any memory allocated to it? */498499if (!m_Pscan_buf->scan_buf_l[i])500{501if ((m_Pscan_buf->scan_buf_l[i] = (Sample*)malloc(m_intermediate_x * sizeof(Sample))) == NULL)502{503m_status = STATUS_OUT_OF_MEMORY;504return false;505}506}507508// Resampling on the X axis first?509if (m_delay_x_resample)510{511assert(m_intermediate_x == m_resample_src_x);512513// Y-X resampling order514memcpy(m_Pscan_buf->scan_buf_l[i], Psrc, m_intermediate_x * sizeof(Sample));515}516else517{518assert(m_intermediate_x == m_resample_dst_x);519520// X-Y resampling order521resample_x(m_Pscan_buf->scan_buf_l[i], Psrc);522}523524m_cur_src_y++;525526return true;527}528529const Resampler::Sample* Resampler::get_line()530{531int i;532533/* If all the destination lines have been534* generated, then always return NULL.535*/536537if (m_cur_dst_y == m_resample_dst_y)538return NULL;539540/* Check to see if all the required541* contributors are present, if not,542* return NULL.543*/544545for (i = 0; i < m_Pclist_y[m_cur_dst_y].n; i++)546if (!m_Psrc_y_flag[resampler_range_check(m_Pclist_y[m_cur_dst_y].p[i].pixel, m_resample_src_y)])547return NULL;548549resample_y(m_Pdst_buf);550551m_cur_dst_y++;552553return m_Pdst_buf;554}555556Resampler::~Resampler()557{558int i;559560#if BASISU_RESAMPLER_DEBUG_OPS561printf("actual ops: %i\n", total_ops);562#endif563564free(m_Pdst_buf);565m_Pdst_buf = NULL;566567if (m_Ptmp_buf)568{569free(m_Ptmp_buf);570m_Ptmp_buf = NULL;571}572573/* Don't deallocate a contibutor list574* if the user passed us one of their own.575*/576577if ((m_Pclist_x) && (!m_clist_x_forced))578{579free(m_Pclist_x->p);580free(m_Pclist_x);581m_Pclist_x = NULL;582}583584if ((m_Pclist_y) && (!m_clist_y_forced))585{586free(m_Pclist_y->p);587free(m_Pclist_y);588m_Pclist_y = NULL;589}590591free(m_Psrc_y_count);592m_Psrc_y_count = NULL;593594free(m_Psrc_y_flag);595m_Psrc_y_flag = NULL;596597if (m_Pscan_buf)598{599for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)600free(m_Pscan_buf->scan_buf_l[i]);601602free(m_Pscan_buf);603m_Pscan_buf = NULL;604}605}606607void Resampler::restart()608{609if (STATUS_OKAY != m_status)610return;611612m_cur_src_y = m_cur_dst_y = 0;613614int i, j;615for (i = 0; i < m_resample_src_y; i++)616{617m_Psrc_y_count[i] = 0;618m_Psrc_y_flag[i] = false;619}620621for (i = 0; i < m_resample_dst_y; i++)622{623for (j = 0; j < m_Pclist_y[i].n; j++)624m_Psrc_y_count[resampler_range_check(m_Pclist_y[i].p[j].pixel, m_resample_src_y)]++;625}626627for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)628{629m_Pscan_buf->scan_buf_y[i] = -1;630631free(m_Pscan_buf->scan_buf_l[i]);632m_Pscan_buf->scan_buf_l[i] = NULL;633}634}635636Resampler::Resampler(int src_x, int src_y,637int dst_x, int dst_y,638Boundary_Op boundary_op,639Resample_Real sample_low, Resample_Real sample_high,640const char* Pfilter_name,641Contrib_List * Pclist_x,642Contrib_List * Pclist_y,643Resample_Real filter_x_scale,644Resample_Real filter_y_scale,645Resample_Real src_x_ofs,646Resample_Real src_y_ofs)647{648int i, j;649Resample_Real support, (*func)(Resample_Real);650651assert(src_x > 0);652assert(src_y > 0);653assert(dst_x > 0);654assert(dst_y > 0);655656#if BASISU_RESAMPLER_DEBUG_OPS657total_ops = 0;658#endif659660m_lo = sample_low;661m_hi = sample_high;662663m_delay_x_resample = false;664m_intermediate_x = 0;665m_Pdst_buf = NULL;666m_Ptmp_buf = NULL;667m_clist_x_forced = false;668m_Pclist_x = NULL;669m_clist_y_forced = false;670m_Pclist_y = NULL;671m_Psrc_y_count = NULL;672m_Psrc_y_flag = NULL;673m_Pscan_buf = NULL;674m_status = STATUS_OKAY;675676m_resample_src_x = src_x;677m_resample_src_y = src_y;678m_resample_dst_x = dst_x;679m_resample_dst_y = dst_y;680681m_boundary_op = boundary_op;682683if ((m_Pdst_buf = (Sample*)malloc(m_resample_dst_x * sizeof(Sample))) == NULL)684{685m_status = STATUS_OUT_OF_MEMORY;686return;687}688689// Find the specified filter.690691if (Pfilter_name == NULL)692Pfilter_name = BASISU_RESAMPLER_DEFAULT_FILTER;693694for (i = 0; i < g_num_resample_filters; i++)695if (strcmp(Pfilter_name, g_resample_filters[i].name) == 0)696break;697698if (i == g_num_resample_filters)699{700m_status = STATUS_BAD_FILTER_NAME;701return;702}703704func = g_resample_filters[i].func;705support = g_resample_filters[i].support;706707/* Create contributor lists, unless the user supplied custom lists. */708709if (!Pclist_x)710{711m_Pclist_x = make_clist(m_resample_src_x, m_resample_dst_x, m_boundary_op, func, support, filter_x_scale, src_x_ofs);712if (!m_Pclist_x)713{714m_status = STATUS_OUT_OF_MEMORY;715return;716}717}718else719{720m_Pclist_x = Pclist_x;721m_clist_x_forced = true;722}723724if (!Pclist_y)725{726m_Pclist_y = make_clist(m_resample_src_y, m_resample_dst_y, m_boundary_op, func, support, filter_y_scale, src_y_ofs);727if (!m_Pclist_y)728{729m_status = STATUS_OUT_OF_MEMORY;730return;731}732}733else734{735m_Pclist_y = Pclist_y;736m_clist_y_forced = true;737}738739if ((m_Psrc_y_count = (int*)calloc(m_resample_src_y, sizeof(int))) == NULL)740{741m_status = STATUS_OUT_OF_MEMORY;742return;743}744745if ((m_Psrc_y_flag = (unsigned char*)calloc(m_resample_src_y, sizeof(unsigned char))) == NULL)746{747m_status = STATUS_OUT_OF_MEMORY;748return;749}750751// Count how many times each source line contributes to a destination line.752753for (i = 0; i < m_resample_dst_y; i++)754for (j = 0; j < m_Pclist_y[i].n; j++)755m_Psrc_y_count[resampler_range_check(m_Pclist_y[i].p[j].pixel, m_resample_src_y)]++;756757if ((m_Pscan_buf = (Scan_Buf*)malloc(sizeof(Scan_Buf))) == NULL)758{759m_status = STATUS_OUT_OF_MEMORY;760return;761}762763for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)764{765m_Pscan_buf->scan_buf_y[i] = -1;766m_Pscan_buf->scan_buf_l[i] = NULL;767}768769m_cur_src_y = m_cur_dst_y = 0;770{771// Determine which axis to resample first by comparing the number of multiplies required772// for each possibility.773int x_ops = count_ops(m_Pclist_x, m_resample_dst_x);774int y_ops = count_ops(m_Pclist_y, m_resample_dst_y);775776// Hack 10/2000: Weight Y axis ops a little more than X axis ops.777// (Y axis ops use more cache resources.)778int xy_ops = x_ops * m_resample_src_y +779(4 * y_ops * m_resample_dst_x) / 3;780781int yx_ops = (4 * y_ops * m_resample_src_x) / 3 +782x_ops * m_resample_dst_y;783784#if BASISU_RESAMPLER_DEBUG_OPS785printf("src: %i %i\n", m_resample_src_x, m_resample_src_y);786printf("dst: %i %i\n", m_resample_dst_x, m_resample_dst_y);787printf("x_ops: %i\n", x_ops);788printf("y_ops: %i\n", y_ops);789printf("xy_ops: %i\n", xy_ops);790printf("yx_ops: %i\n", yx_ops);791#endif792793// Now check which resample order is better. In case of a tie, choose the order794// which buffers the least amount of data.795if ((xy_ops > yx_ops) ||796((xy_ops == yx_ops) && (m_resample_src_x < m_resample_dst_x)))797{798m_delay_x_resample = true;799m_intermediate_x = m_resample_src_x;800}801else802{803m_delay_x_resample = false;804m_intermediate_x = m_resample_dst_x;805}806#if BASISU_RESAMPLER_DEBUG_OPS807printf("delaying: %i\n", m_delay_x_resample);808#endif809}810811if (m_delay_x_resample)812{813if ((m_Ptmp_buf = (Sample*)malloc(m_intermediate_x * sizeof(Sample))) == NULL)814{815m_status = STATUS_OUT_OF_MEMORY;816return;817}818}819}820821void Resampler::get_clists(Contrib_List * *ptr_clist_x, Contrib_List * *ptr_clist_y)822{823if (ptr_clist_x)824* ptr_clist_x = m_Pclist_x;825826if (ptr_clist_y)827* ptr_clist_y = m_Pclist_y;828}829830int Resampler::get_filter_num()831{832return g_num_resample_filters;833}834835const char* Resampler::get_filter_name(int filter_num)836{837if ((filter_num < 0) || (filter_num >= g_num_resample_filters))838return NULL;839else840return g_resample_filters[filter_num].name;841}842843} // namespace basisu844845846