void BLI_ewa_filter(const int width, const int height, const bool intpol, const bool use_alpha, const float uv[2], const float du[2], const float dv[2], ewa_filter_read_pixel_cb read_pixel_cb, void *userdata, float result[4]) { /* scaling dxt/dyt by full resolution can cause overflow because of huge A/B/C and esp. F values, * scaling by aspect ratio alone does the opposite, so try something in between instead... */ const float ff2 = (float)width, ff = sqrtf(ff2), q = (float)height / ff; const float Ux = du[0] * ff, Vx = du[1] * q, Uy = dv[0] * ff, Vy = dv[1] * q; float A = Vx * Vx + Vy * Vy; float B = -2.0f * (Ux * Vx + Uy * Vy); float C = Ux * Ux + Uy * Uy; float F = A * C - B * B * 0.25f; float a, b, th, ecc, a2, b2, ue, ve, U0, V0, DDQ, U, ac1, ac2, BU, d; int u, v, u1, u2, v1, v2; /* The so-called 'high' quality ewa method simply adds a constant of 1 to both A & C, * so the ellipse always covers at least some texels. But since the filter is now always larger, * it also means that everywhere else it's also more blurry then ideally should be the case. * So instead here the ellipse radii are modified instead whenever either is too low. * Use a different radius based on interpolation switch, just enough to anti-alias when interpolation is off, * and slightly larger to make result a bit smoother than bilinear interpolation when interpolation is on * (minimum values: const float rmin = intpol ? 1.f : 0.5f;) */ const float rmin = (intpol ? 1.5625f : 0.765625f) / ff2; BLI_ewa_imp2radangle(A, B, C, F, &a, &b, &th, &ecc); if ((b2 = b * b) < rmin) { if ((a2 = a * a) < rmin) { B = 0.0f; A = C = rmin; F = A * C; } else { b2 = rmin; radangle2imp(a2, b2, th, &A, &B, &C, &F); } } ue = ff * sqrtf(C); ve = ff * sqrtf(A); d = (float)(EWA_MAXIDX + 1) / (F * ff2); A *= d; B *= d; C *= d; U0 = uv[0] * (float)width; V0 = uv[1] * (float)height; u1 = (int)(floorf(U0 - ue)); u2 = (int)(ceilf(U0 + ue)); v1 = (int)(floorf(V0 - ve)); v2 = (int)(ceilf(V0 + ve)); /* sane clamping to avoid unnecessarily huge loops */ /* note: if eccentricity gets clamped (see above), * the ue/ve limits can also be lowered accordingly */ if (U0 - (float)u1 > EWA_MAXIDX) u1 = (int)U0 - EWA_MAXIDX; if ((float)u2 - U0 > EWA_MAXIDX) u2 = (int)U0 + EWA_MAXIDX; if (V0 - (float)v1 > EWA_MAXIDX) v1 = (int)V0 - EWA_MAXIDX; if ((float)v2 - V0 > EWA_MAXIDX) v2 = (int)V0 + EWA_MAXIDX; /* Early output check for cases the whole region is outside of the buffer. */ if ((u2 < 0 || u1 >= width) || (v2 < 0 || v1 >= height)) { zero_v4(result); return; } U0 -= 0.5f; V0 -= 0.5f; DDQ = 2.0f * A; U = (float)u1 - U0; ac1 = A * (2.0f * U + 1.0f); ac2 = A * U * U; BU = B * U; d = 0.0f; zero_v4(result); for (v = v1; v <= v2; ++v) { const float V = (float)v - V0; float DQ = ac1 + B * V; float Q = (C * V + BU) * V + ac2; for (u = u1; u <= u2; ++u) { if (Q < (float)(EWA_MAXIDX + 1)) { float tc[4]; const float wt = EWA_WTS[(Q < 0.0f) ? 0 : (unsigned int)Q]; read_pixel_cb(userdata, u, v, tc); madd_v3_v3fl(result, tc, wt); result[3] += use_alpha ? tc[3] * wt : 0.0f; d += wt; } Q += DQ; DQ += DDQ; } } /* d should hopefully never be zero anymore */ d = 1.0f / d; mul_v3_fl(result, d); /* clipping can be ignored if alpha used, texr->ta already includes filtered edge */ result[3] = use_alpha ? result[3] * d : 1.0f; }
static int imagewraposa_aniso(Tex *tex, Image *ima, ImBuf *ibuf, const float texvec[3], float dxt[2], float dyt[2], TexResult *texres, struct ImagePool *pool, const bool skip_load_image) { TexResult texr; float fx, fy, minx, maxx, miny, maxy; float maxd, val1, val2, val3; int curmap, retval, intpol, extflag = 0; afdata_t AFD; void (*filterfunc)(TexResult*, ImBuf*, float, float, afdata_t*); switch (tex->texfilter) { case TXF_EWA: filterfunc = ewa_eval; break; case TXF_FELINE: filterfunc = feline_eval; break; case TXF_AREA: default: filterfunc = area_sample; } texres->tin = texres->ta = texres->tr = texres->tg = texres->tb = 0.f; /* we need to set retval OK, otherwise texture code generates normals itself... */ retval = texres->nor ? 3 : 1; /* quick tests */ if (ibuf==NULL && ima==NULL) return retval; if (ima) { /* hack for icon render */ if (skip_load_image && !BKE_image_has_loaded_ibuf(ima)) { return retval; } ibuf = BKE_image_pool_acquire_ibuf(ima, &tex->iuser, pool); } if ((ibuf == NULL) || ((ibuf->rect == NULL) && (ibuf->rect_float == NULL))) { if (ima) BKE_image_pool_release_ibuf(ima, ibuf, pool); return retval; } if (ima) { ima->flag |= IMA_USED_FOR_RENDER; } /* mipmap test */ image_mipmap_test(tex, ibuf); if (ima) { if ((tex->imaflag & TEX_USEALPHA) && (ima->flag & IMA_IGNORE_ALPHA) == 0) { if ((tex->imaflag & TEX_CALCALPHA) == 0) { texres->talpha = 1; } } } texr.talpha = texres->talpha; if (tex->imaflag & TEX_IMAROT) { fy = texvec[0]; fx = texvec[1]; } else { fx = texvec[0]; fy = texvec[1]; } if (ibuf->flags & IB_fields) { if (R.r.mode & R_FIELDS) { /* field render */ if (R.flag & R_SEC_FIELD) { /* correction for 2nd field */ /* fac1= 0.5/( (float)ibuf->y ); */ /* fy-= fac1; */ } else /* first field */ fy += 0.5f/( (float)ibuf->y ); } } /* pixel coordinates */ minx = min_fff(dxt[0], dyt[0], dxt[0] + dyt[0]); maxx = max_fff(dxt[0], dyt[0], dxt[0] + dyt[0]); miny = min_fff(dxt[1], dyt[1], dxt[1] + dyt[1]); maxy = max_fff(dxt[1], dyt[1], dxt[1] + dyt[1]); /* tex_sharper has been removed */ minx = (maxx - minx)*0.5f; miny = (maxy - miny)*0.5f; if (tex->imaflag & TEX_FILTER_MIN) { /* make sure the filtersize is minimal in pixels (normal, ref map can have miniature pixel dx/dy) */ const float addval = (0.5f * tex->filtersize) / (float)MIN2(ibuf->x, ibuf->y); if (addval > minx) minx = addval; if (addval > miny) miny = addval; } else if (tex->filtersize != 1.f) { minx *= tex->filtersize; miny *= tex->filtersize; dxt[0] *= tex->filtersize; dxt[1] *= tex->filtersize; dyt[0] *= tex->filtersize; dyt[1] *= tex->filtersize; } if (tex->imaflag & TEX_IMAROT) { float t; SWAP(float, minx, miny); /* must rotate dxt/dyt 90 deg * yet another blender problem is that swapping X/Y axes (or any tex proj switches) should do something similar, * but it doesn't, it only swaps coords, so filter area will be incorrect in those cases. */ t = dxt[0]; dxt[0] = dxt[1]; dxt[1] = -t; t = dyt[0]; dyt[0] = dyt[1]; dyt[1] = -t; } /* side faces of unit-cube */ minx = (minx > 0.25f) ? 0.25f : ((minx < 1e-5f) ? 1e-5f : minx); miny = (miny > 0.25f) ? 0.25f : ((miny < 1e-5f) ? 1e-5f : miny); /* repeat and clip */ if (tex->extend == TEX_REPEAT) { if ((tex->flag & (TEX_REPEAT_XMIR | TEX_REPEAT_YMIR)) == (TEX_REPEAT_XMIR | TEX_REPEAT_YMIR)) extflag = TXC_EXTD; else if (tex->flag & TEX_REPEAT_XMIR) extflag = TXC_XMIR; else if (tex->flag & TEX_REPEAT_YMIR) extflag = TXC_YMIR; else extflag = TXC_REPT; } else if (tex->extend == TEX_EXTEND) extflag = TXC_EXTD; if (tex->extend == TEX_CHECKER) { int xs = (int)floorf(fx), ys = (int)floorf(fy); /* both checkers available, no boundary exceptions, checkerdist will eat aliasing */ if ((tex->flag & TEX_CHECKER_ODD) && (tex->flag & TEX_CHECKER_EVEN)) { fx -= xs; fy -= ys; } else if ((tex->flag & TEX_CHECKER_ODD) == 0 && (tex->flag & TEX_CHECKER_EVEN) == 0) { if (ima) BKE_image_pool_release_ibuf(ima, ibuf, pool); return retval; } else { int xs1 = (int)floorf(fx - minx); int ys1 = (int)floorf(fy - miny); int xs2 = (int)floorf(fx + minx); int ys2 = (int)floorf(fy + miny); if ((xs1 != xs2) || (ys1 != ys2)) { if (tex->flag & TEX_CHECKER_ODD) { fx -= ((xs1 + ys) & 1) ? xs2 : xs1; fy -= ((ys1 + xs) & 1) ? ys2 : ys1; } if (tex->flag & TEX_CHECKER_EVEN) { fx -= ((xs1 + ys) & 1) ? xs1 : xs2; fy -= ((ys1 + xs) & 1) ? ys1 : ys2; } } else { if ((tex->flag & TEX_CHECKER_ODD) == 0 && ((xs + ys) & 1) == 0) { if (ima) BKE_image_pool_release_ibuf(ima, ibuf, pool); return retval; } if ((tex->flag & TEX_CHECKER_EVEN) == 0 && (xs + ys) & 1) { if (ima) BKE_image_pool_release_ibuf(ima, ibuf, pool); return retval; } fx -= xs; fy -= ys; } } /* scale around center, (0.5, 0.5) */ if (tex->checkerdist < 1.f) { const float omcd = 1.f / (1.f - tex->checkerdist); fx = (fx - 0.5f)*omcd + 0.5f; fy = (fy - 0.5f)*omcd + 0.5f; minx *= omcd; miny *= omcd; } } if (tex->extend == TEX_CLIPCUBE) { if ((fx + minx) < 0.f || (fy + miny) < 0.f || (fx - minx) > 1.f || (fy - miny) > 1.f || texvec[2] < -1.f || texvec[2] > 1.f) { if (ima) BKE_image_pool_release_ibuf(ima, ibuf, pool); return retval; } } else if (tex->extend == TEX_CLIP || tex->extend == TEX_CHECKER) { if ((fx + minx) < 0.f || (fy + miny) < 0.f || (fx - minx) > 1.f || (fy - miny) > 1.f) { if (ima) BKE_image_pool_release_ibuf(ima, ibuf, pool); return retval; } } else { if (tex->extend == TEX_EXTEND) { fx = (fx > 1.f) ? 1.f : ((fx < 0.f) ? 0.f : fx); fy = (fy > 1.f) ? 1.f : ((fy < 0.f) ? 0.f : fy); } else { fx -= floorf(fx); fy -= floorf(fy); } } intpol = tex->imaflag & TEX_INTERPOL; /* warning no return! */ if ((R.flag & R_SEC_FIELD) && (ibuf->flags & IB_fields)) ibuf->rect += ibuf->x*ibuf->y; /* struct common data */ copy_v2_v2(AFD.dxt, dxt); copy_v2_v2(AFD.dyt, dyt); AFD.intpol = intpol; AFD.extflag = extflag; /* brecht: added stupid clamping here, large dx/dy can give very large * filter sizes which take ages to render, it may be better to do this * more intelligently later in the code .. probably it's not noticeable */ if (AFD.dxt[0]*AFD.dxt[0] + AFD.dxt[1]*AFD.dxt[1] > 2.0f*2.0f) mul_v2_fl(AFD.dxt, 2.0f/len_v2(AFD.dxt)); if (AFD.dyt[0]*AFD.dyt[0] + AFD.dyt[1]*AFD.dyt[1] > 2.0f*2.0f) mul_v2_fl(AFD.dyt, 2.0f/len_v2(AFD.dyt)); /* choice: */ if (tex->imaflag & TEX_MIPMAP) { ImBuf *previbuf, *curibuf; float levf; int maxlev; ImBuf *mipmaps[IMB_MIPMAP_LEVELS + 1]; /* modify ellipse minor axis if too eccentric, use for area sampling as well * scaling dxt/dyt as done in pbrt is not the same * (as in ewa_eval(), scale by sqrt(ibuf->x) to maximize precision) */ const float ff = sqrtf(ibuf->x), q = ibuf->y/ff; const float Ux = dxt[0]*ff, Vx = dxt[1]*q, Uy = dyt[0]*ff, Vy = dyt[1]*q; const float A = Vx*Vx + Vy*Vy; const float B = -2.f*(Ux*Vx + Uy*Vy); const float C = Ux*Ux + Uy*Uy; const float F = A*C - B*B*0.25f; float a, b, th, ecc; BLI_ewa_imp2radangle(A, B, C, F, &a, &b, &th, &ecc); if (tex->texfilter == TXF_FELINE) { float fProbes; a *= ff; b *= ff; a = max_ff(a, 1.0f); b = max_ff(b, 1.0f); fProbes = 2.f*(a / b) - 1.f; AFD.iProbes = round_fl_to_int(fProbes); AFD.iProbes = MIN2(AFD.iProbes, tex->afmax); if (AFD.iProbes < fProbes) b = 2.f*a / (float)(AFD.iProbes + 1); AFD.majrad = a/ff; AFD.minrad = b/ff; AFD.theta = th; AFD.dusc = 1.f/ff; AFD.dvsc = ff / (float)ibuf->y; } else { /* EWA & area */ if (ecc > (float)tex->afmax) b = a / (float)tex->afmax; b *= ff; } maxd = max_ff(b, 1e-8f); levf = ((float)M_LOG2E) * logf(maxd); curmap = 0; maxlev = 1; mipmaps[0] = ibuf; while (curmap < IMB_MIPMAP_LEVELS) { mipmaps[curmap + 1] = ibuf->mipmap[curmap]; if (ibuf->mipmap[curmap]) maxlev++; curmap++; } /* mipmap level */ if (levf < 0.f) { /* original image only */ previbuf = curibuf = mipmaps[0]; levf = 0.f; } else if (levf >= maxlev - 1) { previbuf = curibuf = mipmaps[maxlev - 1]; levf = 0.f; if (tex->texfilter == TXF_FELINE) AFD.iProbes = 1; } else { const int lev = isnan(levf) ? 0 : (int)levf; curibuf = mipmaps[lev]; previbuf = mipmaps[lev + 1]; levf -= floorf(levf); } /* filter functions take care of interpolation themselves, no need to modify dxt/dyt here */ if (texres->nor && ((tex->imaflag & TEX_NORMALMAP) == 0)) { /* color & normal */ filterfunc(texres, curibuf, fx, fy, &AFD); val1 = texres->tr + texres->tg + texres->tb; filterfunc(&texr, curibuf, fx + dxt[0], fy + dxt[1], &AFD); val2 = texr.tr + texr.tg + texr.tb; filterfunc(&texr, curibuf, fx + dyt[0], fy + dyt[1], &AFD); val3 = texr.tr + texr.tg + texr.tb; /* don't switch x or y! */ texres->nor[0] = val1 - val2; texres->nor[1] = val1 - val3; if (previbuf != curibuf) { /* interpolate */ filterfunc(&texr, previbuf, fx, fy, &AFD); /* rgb */ texres->tr += levf*(texr.tr - texres->tr); texres->tg += levf*(texr.tg - texres->tg); texres->tb += levf*(texr.tb - texres->tb); texres->ta += levf*(texr.ta - texres->ta); /* normal */ val1 += levf*((texr.tr + texr.tg + texr.tb) - val1); filterfunc(&texr, previbuf, fx + dxt[0], fy + dxt[1], &AFD); val2 += levf*((texr.tr + texr.tg + texr.tb) - val2); filterfunc(&texr, previbuf, fx + dyt[0], fy + dyt[1], &AFD); val3 += levf*((texr.tr + texr.tg + texr.tb) - val3); texres->nor[0] = val1 - val2; /* vals have been interpolated above! */ texres->nor[1] = val1 - val3; } } else { /* color */ filterfunc(texres, curibuf, fx, fy, &AFD); if (previbuf != curibuf) { /* interpolate */ filterfunc(&texr, previbuf, fx, fy, &AFD); texres->tr += levf*(texr.tr - texres->tr); texres->tg += levf*(texr.tg - texres->tg); texres->tb += levf*(texr.tb - texres->tb); texres->ta += levf*(texr.ta - texres->ta); } if (tex->texfilter != TXF_EWA) { alpha_clip_aniso(ibuf, fx-minx, fy-miny, fx+minx, fy+miny, extflag, texres); } } } else { /* no mipmap */ /* filter functions take care of interpolation themselves, no need to modify dxt/dyt here */ if (tex->texfilter == TXF_FELINE) { const float ff = sqrtf(ibuf->x), q = ibuf->y/ff; const float Ux = dxt[0]*ff, Vx = dxt[1]*q, Uy = dyt[0]*ff, Vy = dyt[1]*q; const float A = Vx*Vx + Vy*Vy; const float B = -2.f*(Ux*Vx + Uy*Vy); const float C = Ux*Ux + Uy*Uy; const float F = A*C - B*B*0.25f; float a, b, th, ecc, fProbes; BLI_ewa_imp2radangle(A, B, C, F, &a, &b, &th, &ecc); a *= ff; b *= ff; a = max_ff(a, 1.0f); b = max_ff(b, 1.0f); fProbes = 2.f*(a / b) - 1.f; /* no limit to number of Probes here */ AFD.iProbes = round_fl_to_int(fProbes); if (AFD.iProbes < fProbes) b = 2.f*a / (float)(AFD.iProbes + 1); AFD.majrad = a/ff; AFD.minrad = b/ff; AFD.theta = th; AFD.dusc = 1.f/ff; AFD.dvsc = ff / (float)ibuf->y; } if (texres->nor && ((tex->imaflag & TEX_NORMALMAP) == 0)) { /* color & normal */ filterfunc(texres, ibuf, fx, fy, &AFD); val1 = texres->tr + texres->tg + texres->tb; filterfunc(&texr, ibuf, fx + dxt[0], fy + dxt[1], &AFD); val2 = texr.tr + texr.tg + texr.tb; filterfunc(&texr, ibuf, fx + dyt[0], fy + dyt[1], &AFD); val3 = texr.tr + texr.tg + texr.tb; /* don't switch x or y! */ texres->nor[0] = val1 - val2; texres->nor[1] = val1 - val3; } else { filterfunc(texres, ibuf, fx, fy, &AFD); if (tex->texfilter != TXF_EWA) { alpha_clip_aniso(ibuf, fx-minx, fy-miny, fx+minx, fy+miny, extflag, texres); } } } if (tex->imaflag & TEX_CALCALPHA) texres->ta = texres->tin = texres->ta * max_fff(texres->tr, texres->tg, texres->tb); else texres->tin = texres->ta; if (tex->flag & TEX_NEGALPHA) texres->ta = 1.f - texres->ta; if ((R.flag & R_SEC_FIELD) && (ibuf->flags & IB_fields)) ibuf->rect -= ibuf->x*ibuf->y; if (texres->nor && (tex->imaflag & TEX_NORMALMAP)) { /* normal from color */ /* The invert of the red channel is to make * the normal map compliant with the outside world. * It needs to be done because in Blender * the normal used in the renderer points inward. It is generated * this way in calc_vertexnormals(). Should this ever change * this negate must be removed. */ texres->nor[0] = -2.f*(texres->tr - 0.5f); texres->nor[1] = 2.f*(texres->tg - 0.5f); texres->nor[2] = 2.f*(texres->tb - 0.5f); } /* de-premul, this is being premulled in shade_input_do_shade() * TXF: this currently does not (yet?) work properly, destroys edge AA in clip/checker mode, so for now commented out * also disabled in imagewraposa() to be able to compare results with blender's default texture filtering */ /* brecht: tried to fix this, see "TXF alpha" comments */ /* do not de-premul for generated alpha, it is already in straight */ if (texres->ta!=1.0f && texres->ta>1e-4f && !(tex->imaflag & TEX_CALCALPHA)) { fx = 1.f/texres->ta; texres->tr *= fx; texres->tg *= fx; texres->tb *= fx; } if (ima) BKE_image_pool_release_ibuf(ima, ibuf, pool); BRICONTRGB; return retval; }