/* Copyright 2007 nVidia, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Thanks to Jacob Munkberg (jacob@cs.lth.se) for the shortcut of using SVD to do the equivalent of principal components analysis // x100000 2r 777x2 8x2 2bi 2bi #include "bits.h" #include "tile.h" #include "avpcl.h" #include "nvcore/debug.h" #include "nvmath/vector.inl" #include "nvmath/matrix.inl" #include "nvmath/fitting.h" #include "avpcl_utils.h" #include "endpts.h" #include #include using namespace nv; using namespace AVPCL; // there are 2 index arrays. INDEXMODE selects between the arrays being 2 & 3 bits or 3 & 2 bits // array 0 is always the RGB array and array 1 is always the A array #define NINDEXARRAYS 2 #define INDEXARRAY_RGB 0 #define INDEXARRAY_A 1 #define INDEXARRAY_2BITS(indexmode) ((indexmode == INDEXMODE_ALPHA_IS_2BITS) ? INDEXARRAY_A : INDEXARRAY_RGB) #define INDEXARRAY_3BITS(indexmode) ((indexmode == INDEXMODE_ALPHA_IS_3BITS) ? INDEXARRAY_A : INDEXARRAY_RGB) #define NINDICES3 4 #define INDEXBITS3 2 #define HIGH_INDEXBIT3 (1<<(INDEXBITS3-1)) #define DENOM3 (NINDICES3-1) #define BIAS3 (DENOM3/2) #define NINDICES2 4 #define INDEXBITS2 2 #define HIGH_INDEXBIT2 (1<<(INDEXBITS2-1)) #define DENOM2 (NINDICES2-1) #define BIAS2 (DENOM2/2) #define NINDICES_RGB(indexmode) ((indexmode == INDEXMODE_ALPHA_IS_2BITS) ? NINDICES3 : NINDICES2) #define INDEXBITS_RGB(indexmode) ((indexmode == INDEXMODE_ALPHA_IS_2BITS) ? INDEXBITS3 : INDEXBITS2) #define HIGH_INDEXBIT_RGB(indexmode)((indexmode == INDEXMODE_ALPHA_IS_2BITS) ? HIGH_INDEXBIT3 : HIGH_INDEXBIT2) #define DENOM_RGB(indexmode) ((indexmode == INDEXMODE_ALPHA_IS_2BITS) ? DENOM3 : DENOM2) #define BIAS_RGB(indexmode) ((indexmode == INDEXMODE_ALPHA_IS_2BITS) ? BIAS3 : BIAS2) #define NINDICES_A(indexmode) ((indexmode == INDEXMODE_ALPHA_IS_2BITS) ? NINDICES2 : NINDICES3) #define INDEXBITS_A(indexmode) ((indexmode == INDEXMODE_ALPHA_IS_2BITS) ? INDEXBITS2 : INDEXBITS3) #define HIGH_INDEXBIT_A(indexmode) ((indexmode == INDEXMODE_ALPHA_IS_2BITS) ? HIGH_INDEXBIT2 : HIGH_INDEXBIT3) #define DENOM_A(indexmode) ((indexmode == INDEXMODE_ALPHA_IS_2BITS) ? DENOM2 : DENOM3) #define BIAS_A(indexmode) ((indexmode == INDEXMODE_ALPHA_IS_2BITS) ? BIAS2 : BIAS3) #define NSHAPES 1 static int shapes[NSHAPES] = { 0x0000, }; #define REGION(x,y,shapeindex) ((shapes[shapeindex]&(1<<(15-(x)-4*(y))))!=0) #define NREGIONS 1 // keep the region stuff in just in case... // encoded index compression location: region 0 is always at 0,0. #define NBITSIZES 2 // one endpoint pair struct ChanBits { int nbitsizes[NBITSIZES]; // bitsizes for one channel }; struct Pattern { ChanBits chan[NCHANNELS_RGBA];// bit patterns used per channel int transform_mode; // x0 means alpha channel not transformed, x1 otherwise. 0x rgb not transformed, 1x otherwise. int mode; // associated mode value int modebits; // number of mode bits const char *encoding; // verilog description of encoding for this mode }; #define TRANSFORM_MODE_ALPHA 1 #define TRANSFORM_MODE_RGB 2 #define NPATTERNS 1 static Pattern patterns[NPATTERNS] = { // red green blue alpha xfm mode mb encoding 7,7, 7,7, 7,7, 8,8, 0x0, 0x20, 6, "", }; struct RegionPrec { int endpt_a_prec[NCHANNELS_RGBA]; int endpt_b_prec[NCHANNELS_RGBA]; }; struct PatternPrec { RegionPrec region_precs[NREGIONS]; }; // this is the precision for each channel and region // NOTE: this MUST match the corresponding data in "patterns" above -- WARNING: there is NO nvAssert to check this! static PatternPrec pattern_precs[NPATTERNS] = { 7,7,7,8, 7,7,7,8, }; // return # of bits needed to store n. handle signed or unsigned cases properly static int nbits(int n, bool issigned) { int nb; if (n==0) return 0; // no bits needed for 0, signed or not else if (n > 0) { for (nb=0; n; ++nb, n>>=1) ; return nb + (issigned?1:0); } else { nvAssert (issigned); for (nb=0; n<-1; ++nb, n>>=1) ; return nb + 1; } } #define R_0 ep[0].A[i] #define R_1 ep[0].B[i] static void transform_forward(int transform_mode, IntEndptsRGBA ep[NREGIONS]) { int i; if (transform_mode & TRANSFORM_MODE_RGB) for (i=CHANNEL_R; i> 2) & 3 and x = index & 3 static void swap_indices(int shapeindex, int indexmode, IntEndptsRGBA endpts[NREGIONS], int indices[NINDEXARRAYS][Tile::TILE_H][Tile::TILE_W]) { int index_positions[NREGIONS]; index_positions[0] = 0; // since WLOG we have the high bit of the shapes at 0 for (int region = 0; region < NREGIONS; ++region) { int x = index_positions[region] & 3; int y = (index_positions[region] >> 2) & 3; nvAssert(REGION(x,y,shapeindex) == region); // double check the table // swap RGB if (indices[INDEXARRAY_RGB][y][x] & HIGH_INDEXBIT_RGB(indexmode)) { // high bit is set, swap the endpts and indices for this region int t; for (int i=CHANNEL_R; i<=CHANNEL_B; ++i) { t = endpts[region].A[i]; endpts[region].A[i] = endpts[region].B[i]; endpts[region].B[i] = t; } for (int y = 0; y < Tile::TILE_H; y++) for (int x = 0; x < Tile::TILE_W; x++) if (REGION(x,y,shapeindex) == region) indices[INDEXARRAY_RGB][y][x] = NINDICES_RGB(indexmode) - 1 - indices[INDEXARRAY_RGB][y][x]; } // swap A if (indices[INDEXARRAY_A][y][x] & HIGH_INDEXBIT_A(indexmode)) { // high bit is set, swap the endpts and indices for this region int t; for (int i=CHANNEL_A; i<=CHANNEL_A; ++i) { t = endpts[region].A[i]; endpts[region].A[i] = endpts[region].B[i]; endpts[region].B[i] = t; } for (int y = 0; y < Tile::TILE_H; y++) for (int x = 0; x < Tile::TILE_W; x++) if (REGION(x,y,shapeindex) == region) indices[INDEXARRAY_A][y][x] = NINDICES_A(indexmode) - 1 - indices[INDEXARRAY_A][y][x]; } } } static bool endpts_fit(IntEndptsRGBA endpts[NREGIONS], const Pattern &p) { return true; } static void write_header(const IntEndptsRGBA endpts[NREGIONS], int shapeindex, const Pattern &p, int rotatemode, int indexmode, Bits &out) { // ignore shapeindex out.write(p.mode, p.modebits); out.write(rotatemode, ROTATEMODE_BITS); // out.write(indexmode, INDEXMODE_BITS); for (int i=0; i= 0 && pat_index < NPATTERNS); nvAssert (in.getptr() == patterns[pat_index].modebits); p = patterns[pat_index]; shapeindex = 0; // we don't have any rotatemode = in.read(ROTATEMODE_BITS); indexmode = 0; // we don't have any for (int i=0; i>2][i&3], INDEXBITS2 - (i==0?1:0)); // write i..[1:0] or i..[0] // then the 3 bit indices nvAssert ((indices[INDEXARRAY_3BITS(indexmode)][0][0] & HIGH_INDEXBIT3) == 0); for (int i = 0; i < Tile::TILE_TOTAL; ++i) out.write(indices[INDEXARRAY_3BITS(indexmode)][i>>2][i&3], INDEXBITS3 - (i==0?1:0)); // write i..[2:0] or i..[1:0] } static void read_indices(Bits &in, int shapeindex, int indexmode, int indices[NINDEXARRAYS][Tile::TILE_H][Tile::TILE_W]) { // the indices we shorten is always index 0 // do the 2 bit indices first for (int i = 0; i < Tile::TILE_TOTAL; ++i) indices[INDEXARRAY_2BITS(indexmode)][i>>2][i&3] = in.read(INDEXBITS2 - (i==0?1:0)); // read i..[1:0] or i..[0] // then the 3 bit indices for (int i = 0; i < Tile::TILE_TOTAL; ++i) indices[INDEXARRAY_3BITS(indexmode)][i>>2][i&3] = in.read(INDEXBITS3 - (i==0?1:0)); // read i..[1:0] or i..[0] } static void emit_block(const IntEndptsRGBA endpts[NREGIONS], int shapeindex, const Pattern &p, const int indices[NINDEXARRAYS][Tile::TILE_H][Tile::TILE_W], int rotatemode, int indexmode, char *block) { Bits out(block, AVPCL::BITSIZE); write_header(endpts, shapeindex, p, rotatemode, indexmode, out); write_indices(indices, shapeindex, indexmode, out); nvAssert(out.getptr() == AVPCL::BITSIZE); } static void generate_palette_quantized_rgb_a(const IntEndptsRGBA &endpts, const RegionPrec ®ion_prec, int indexmode, Vector3 palette_rgb[NINDICES3], float palette_a[NINDICES3]) { // scale endpoints for RGB int a, b; a = Utils::unquantize(endpts.A[0], region_prec.endpt_a_prec[0]); b = Utils::unquantize(endpts.B[0], region_prec.endpt_b_prec[0]); // interpolate R for (int i = 0; i < NINDICES_RGB(indexmode); ++i) palette_rgb[i].x = float(Utils::lerp(a, b, i, BIAS_RGB(indexmode), DENOM_RGB(indexmode))); a = Utils::unquantize(endpts.A[1], region_prec.endpt_a_prec[1]); b = Utils::unquantize(endpts.B[1], region_prec.endpt_b_prec[1]); // interpolate G for (int i = 0; i < NINDICES_RGB(indexmode); ++i) palette_rgb[i].y = float(Utils::lerp(a, b, i, BIAS_RGB(indexmode), DENOM_RGB(indexmode))); a = Utils::unquantize(endpts.A[2], region_prec.endpt_a_prec[2]); b = Utils::unquantize(endpts.B[2], region_prec.endpt_b_prec[2]); // interpolate B for (int i = 0; i < NINDICES_RGB(indexmode); ++i) palette_rgb[i].z = float(Utils::lerp(a, b, i, BIAS_RGB(indexmode), DENOM_RGB(indexmode))); a = Utils::unquantize(endpts.A[3], region_prec.endpt_a_prec[3]); b = Utils::unquantize(endpts.B[3], region_prec.endpt_b_prec[3]); // interpolate A for (int i = 0; i < NINDICES_A(indexmode); ++i) palette_a[i] = float(Utils::lerp(a, b, i, BIAS_A(indexmode), DENOM_A(indexmode))); } static void sign_extend(Pattern &p, IntEndptsRGBA endpts[NREGIONS]) { for (int i=0; i 0; ++j) { err = Utils::metric1(a, palette_a[j], rotatemode); if (err > besterr) // error increased, so we're done searching break; if (err < besterr) { besterr = err; palette_alpha = palette_a[j]; indices[INDEXARRAY_A][i] = j; } } toterr += besterr; // squared-error norms are additive since we don't do the square root // do RGB index besterr = FLT_MAX; for (int j = 0; j < NINDICES_RGB(indexmode) && besterr > 0; ++j) { err = !AVPCL::flag_premult ? Utils::metric3(rgb, palette_rgb[j], rotatemode) : Utils::metric3premult_alphaout(rgb, tile_alpha, palette_rgb[j], palette_alpha); if (err > besterr) // error increased, so we're done searching break; if (err < besterr) { besterr = err; indices[INDEXARRAY_RGB][i] = j; } } toterr += besterr; if (toterr > current_besterr) { // fill out bogus index values so it's initialized at least for (int k = i; k < np; ++k) { indices[INDEXARRAY_RGB][k] = -1; indices[INDEXARRAY_A][k] = -1; } return FLT_MAX; } } else { // do RGB index besterr = FLT_MAX; int bestindex; for (int j = 0; j < NINDICES_RGB(indexmode) && besterr > 0; ++j) { err = !AVPCL::flag_premult ? Utils::metric3(rgb, palette_rgb[j], rotatemode) : Utils::metric3premult_alphain(rgb, palette_rgb[j], rotatemode); if (err > besterr) // error increased, so we're done searching break; if (err < besterr) { besterr = err; bestindex = j; indices[INDEXARRAY_RGB][i] = j; } } palette_alpha = (rotatemode == ROTATEMODE_RGBA_AGBR) ? (palette_rgb[bestindex]).x : (rotatemode == ROTATEMODE_RGBA_RABG) ? (palette_rgb[bestindex]).y : (rotatemode == ROTATEMODE_RGBA_RGAB) ? (palette_rgb[bestindex]).z : nvCheckMacro(0); toterr += besterr; // do A index besterr = FLT_MAX; for (int j = 0; j < NINDICES_A(indexmode) && besterr > 0; ++j) { err = !AVPCL::flag_premult ? Utils::metric1(a, palette_a[j], rotatemode) : Utils::metric1premult(a, tile_alpha, palette_a[j], palette_alpha, rotatemode); if (err > besterr) // error increased, so we're done searching break; if (err < besterr) { besterr = err; indices[INDEXARRAY_A][i] = j; } } toterr += besterr; // squared-error norms are additive since we don't do the square root if (toterr > current_besterr) { // fill out bogus index values so it's initialized at least for (int k = i; k < np; ++k) { indices[INDEXARRAY_RGB][k] = -1; indices[INDEXARRAY_A][k] = -1; } return FLT_MAX; } } } return toterr; } // assign indices given a tile, shape, and quantized endpoints, return toterr for each region static void assign_indices(const Tile &tile, int shapeindex, int rotatemode, int indexmode, IntEndptsRGBA endpts[NREGIONS], const PatternPrec &pattern_prec, int indices[NINDEXARRAYS][Tile::TILE_H][Tile::TILE_W], float toterr[NREGIONS]) { Vector3 palette_rgb[NREGIONS][NINDICES3]; // could be nindices2 float palette_a[NREGIONS][NINDICES3]; // could be nindices2 for (int region = 0; region < NREGIONS; ++region) { generate_palette_quantized_rgb_a(endpts[region], pattern_prec.region_precs[region], indexmode, &palette_rgb[region][0], &palette_a[region][0]); toterr[region] = 0; } Vector3 rgb; float a; for (int y = 0; y < tile.size_y; y++) for (int x = 0; x < tile.size_x; x++) { int region = REGION(x,y,shapeindex); float err, besterr; float palette_alpha = 0, tile_alpha = 0; rgb.x = (tile.data[y][x]).x; rgb.y = (tile.data[y][x]).y; rgb.z = (tile.data[y][x]).z; a = (tile.data[y][x]).w; if(AVPCL::flag_premult) tile_alpha = (rotatemode == ROTATEMODE_RGBA_AGBR) ? (tile.data[y][x]).x : (rotatemode == ROTATEMODE_RGBA_RABG) ? (tile.data[y][x]).y : (rotatemode == ROTATEMODE_RGBA_RGAB) ? (tile.data[y][x]).z : (tile.data[y][x]).w; // compute the two indices separately // if we're doing premultiplied alpha, we need to choose first the index that // determines the alpha value, and then do the other index if (rotatemode == ROTATEMODE_RGBA_RGBA) { // do A index first as it has the alpha besterr = FLT_MAX; for (int i = 0; i < NINDICES_A(indexmode) && besterr > 0; ++i) { err = Utils::metric1(a, palette_a[region][i], rotatemode); if (err > besterr) // error increased, so we're done searching break; if (err < besterr) { besterr = err; indices[INDEXARRAY_A][y][x] = i; palette_alpha = palette_a[region][i]; } } toterr[region] += besterr; // squared-error norms are additive since we don't do the square root // do RGB index besterr = FLT_MAX; for (int i = 0; i < NINDICES_RGB(indexmode) && besterr > 0; ++i) { err = !AVPCL::flag_premult ? Utils::metric3(rgb, palette_rgb[region][i], rotatemode) : Utils::metric3premult_alphaout(rgb, tile_alpha, palette_rgb[region][i], palette_alpha); if (err > besterr) // error increased, so we're done searching break; if (err < besterr) { besterr = err; indices[INDEXARRAY_RGB][y][x] = i; } } toterr[region] += besterr; } else { // do RGB index first as it has the alpha besterr = FLT_MAX; int bestindex; for (int i = 0; i < NINDICES_RGB(indexmode) && besterr > 0; ++i) { err = !AVPCL::flag_premult ? Utils::metric3(rgb, palette_rgb[region][i], rotatemode) : Utils::metric3premult_alphain(rgb, palette_rgb[region][i], rotatemode); if (err > besterr) // error increased, so we're done searching break; if (err < besterr) { besterr = err; indices[INDEXARRAY_RGB][y][x] = i; bestindex = i; } } palette_alpha = (rotatemode == ROTATEMODE_RGBA_AGBR) ? (palette_rgb[region][bestindex]).x : (rotatemode == ROTATEMODE_RGBA_RABG) ? (palette_rgb[region][bestindex]).y : (rotatemode == ROTATEMODE_RGBA_RGAB) ? (palette_rgb[region][bestindex]).z : nvCheckMacro(0); toterr[region] += besterr; // do A index besterr = FLT_MAX; for (int i = 0; i < NINDICES_A(indexmode) && besterr > 0; ++i) { err = !AVPCL::flag_premult ? Utils::metric1(a, palette_a[region][i], rotatemode) : Utils::metric1premult(a, tile_alpha, palette_a[region][i], palette_alpha, rotatemode); if (err > besterr) // error increased, so we're done searching break; if (err < besterr) { besterr = err; indices[INDEXARRAY_A][y][x] = i; } } toterr[region] += besterr; // squared-error norms are additive since we don't do the square root } } } // note: indices are valid only if the value returned is less than old_err; otherwise they contain -1's // this function returns either old_err or a value smaller (if it was successful in improving the error) static float perturb_one(const Vector4 colors[], const float importance[], int np, int rotatemode, int indexmode, int ch, const RegionPrec ®ion_prec, const IntEndptsRGBA &old_endpts, IntEndptsRGBA &new_endpts, float old_err, int do_b, int indices[NINDEXARRAYS][Tile::TILE_TOTAL]) { // we have the old endpoints: old_endpts // we have the perturbed endpoints: new_endpts // we have the temporary endpoints: temp_endpts IntEndptsRGBA temp_endpts; float min_err = old_err; // start with the best current error int beststep; int temp_indices[NINDEXARRAYS][Tile::TILE_TOTAL]; for (int j=0; j>= 1) { bool improved = false; for (int sign = -1; sign <= 1; sign += 2) { if (do_b == 0) { temp_endpts.A[ch] = new_endpts.A[ch] + sign * step; if (temp_endpts.A[ch] < 0 || temp_endpts.A[ch] >= (1 << prec)) continue; } else { temp_endpts.B[ch] = new_endpts.B[ch] + sign * step; if (temp_endpts.B[ch] < 0 || temp_endpts.B[ch] >= (1 << prec)) continue; } float err = map_colors(colors, importance, np, rotatemode, indexmode, temp_endpts, region_prec, min_err, temp_indices); if (err < min_err) { improved = true; min_err = err; beststep = sign * step; for (int j=0; j 5000 perturb endpoints 50% of precision // if err > 1000 25% // if err > 200 12.5% // if err > 40 6.25% // for np = 16 -- adjust error thresholds as a function of np // always ensure endpoint ordering is preserved (no need to overlap the scan) static float exhaustive(const Vector4 colors[], const float importance[], int np, int rotatemode, int indexmode, int ch, const RegionPrec ®ion_prec, float orig_err, IntEndptsRGBA &opt_endpts, int indices[NINDEXARRAYS][Tile::TILE_TOTAL]) { IntEndptsRGBA temp_endpts; float best_err = orig_err; int aprec = region_prec.endpt_a_prec[ch]; int bprec = region_prec.endpt_b_prec[ch]; int good_indices[NINDEXARRAYS][Tile::TILE_TOTAL]; int temp_indices[NINDEXARRAYS][Tile::TILE_TOTAL]; for (int j=0; j 5000.0*thr_scale) { adelta = (1 << aprec)/2; bdelta = (1 << bprec)/2; } else if (orig_err > 1000.0*thr_scale) { adelta = (1 << aprec)/4; bdelta = (1 << bprec)/4; } else if (orig_err > 200.0*thr_scale) { adelta = (1 << aprec)/8; bdelta = (1 << bprec)/8; } else if (orig_err > 40.0*thr_scale) { adelta = (1 << aprec)/16; bdelta = (1 << bprec)/16; } adelta = max(adelta, 3); bdelta = max(bdelta, 3); #ifdef DISABLE_EXHAUSTIVE adelta = bdelta = 3; #endif temp_endpts = opt_endpts; // ok figure out the range of A and B int alow = max(0, opt_endpts.A[ch] - adelta); int ahigh = min((1<= initial_error) break rgb0 += delta0 next = 1 else if (err1 >= initial_error) break rgb1 += delta1 next = 0 initial_err = map() for (;;) err = perturb(next ? rgb1:rgb0, delta) if (err >= initial_err) break next? rgb1 : rgb0 += delta initial_err = err */ IntEndptsRGBA new_a, new_b; IntEndptsRGBA new_endpt; int do_b; int orig_indices[NINDEXARRAYS][Tile::TILE_TOTAL]; int new_indices[NINDEXARRAYS][Tile::TILE_TOTAL]; int temp_indices0[NINDEXARRAYS][Tile::TILE_TOTAL]; int temp_indices1[NINDEXARRAYS][Tile::TILE_TOTAL]; // now optimize each channel separately for (int ch = 0; ch < NCHANNELS_RGBA; ++ch) { // figure out which endpoint when perturbed gives the most improvement and start there // if we just alternate, we can easily end up in a local minima float err0 = perturb_one(colors, importance, np, rotatemode, indexmode, ch, region_prec, opt_endpts, new_a, opt_err, 0, temp_indices0); // perturb endpt A float err1 = perturb_one(colors, importance, np, rotatemode, indexmode, ch, region_prec, opt_endpts, new_b, opt_err, 1, temp_indices1); // perturb endpt B if (err0 < err1) { if (err0 >= opt_err) continue; for (int j=0; j= opt_err) continue; for (int j=0; j= opt_err) break; for (int j=0; j 255.0f) v.x = 255.0f; if (v.y < 0.0f) v.y = 0.0f; if (v.y > 255.0f) v.y = 255.0f; if (v.z < 0.0f) v.z = 0.0f; if (v.z > 255.0f) v.z = 255.0f; if (v.w < 0.0f) v.w = 0.0f; if (v.w > 255.0f) v.w = 255.0f; } // compute initial endpoints for the "RGB" portion and the "A" portion. // Note these channels may have been rotated. static void rough(const Tile &tile, int shapeindex, FltEndpts endpts[NREGIONS]) { for (int region=0; region maxp) maxp = dp; dp = alphas[i] - mean.w; if (dp < mina) mina = dp; if (dp > maxa) maxa = dp; } // choose as endpoints 2 points along the principal direction that span the projections of all of the pixel values endpts[region].A = mean + Vector4(minp*direction, mina); endpts[region].B = mean + Vector4(maxp*direction, maxa); // clamp endpoints // the argument for clamping is that the actual endpoints need to be clamped and thus we need to choose the best // shape based on endpoints being clamped clamp(endpts[region].A); clamp(endpts[region].B); } } float AVPCL::compress_mode5(const Tile &t, char *block) { FltEndpts endpts[NREGIONS]; char tempblock[AVPCL::BLOCKSIZE]; float msebest = FLT_MAX; int shape = 0; Tile t1; // try all rotations. refine tries the 2 different indexings. for (int r = 0; r < NROTATEMODES && msebest > 0; ++r) { rotate_tile(t, r, t1); rough(t1, shape, endpts); // for (int i = 0; i < NINDEXMODES && msebest > 0; ++i) for (int i = 0; i < 1 && msebest > 0; ++i) { float mse = refine(t1, shape, r, i, endpts, tempblock); if (mse < msebest) { memcpy(block, tempblock, sizeof(tempblock)); msebest = mse; } } } return msebest; }