/** * Copyright (C) 2016-2017 IRIE Shinsuke * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* * smaa_areatex.cpp version 0.4.0 * * This is a part of smaa-cpp that is an implementation of * Enhanced Subpixel Morphological Antialiasing (SMAA) written in C++. * * This program is C++ rewrite of AreaTex.py included in the original * SMAA ditribution: * * https://github.com/iryoku/smaa/tree/master/Scripts */ #include #include #include #include /*------------------------------------------------------------------------------*/ /* Type Definitions */ class Int2; class Dbl2; class Int2 { public: int x, y; Int2() { this->x = this->y = 0; } Int2(int x) { this->x = this->y = x; } Int2(int x, int y) { this->x = x; this->y = y; } operator Dbl2(); Int2 operator + (Int2 other) { return Int2(x + other.x, y + other.y); } Int2 operator * (Int2 other) { return Int2(x * other.x, y * other.y); } }; class Dbl2 { public: double x, y; Dbl2() { this->x = this->y = 0.0; } Dbl2(double x) { this->x = this->y = x; } Dbl2(double x, double y) { this->x = x; this->y = y; } Dbl2 apply(double (* func)(double)) { return Dbl2(func(x), func(y)); } operator Int2(); Dbl2 operator + (Dbl2 other) { return Dbl2(x + other.x, y + other.y); } Dbl2 operator - (Dbl2 other) { return Dbl2(x - other.x, y - other.y); } Dbl2 operator * (Dbl2 other) { return Dbl2(x * other.x, y * other.y); } Dbl2 operator / (Dbl2 other) { return Dbl2(x / other.x, y / other.y); } Dbl2 operator += (Dbl2 other) { return Dbl2(x += other.x, y += other.y); } bool operator == (Dbl2 other) { return (x == other.x && y == other.y); } }; Int2::operator Dbl2() { return Dbl2((double)x, (double)y); } Dbl2::operator Int2() { return Int2((int)x, (int)y); } /*------------------------------------------------------------------------------*/ /* Data to Calculate Areatex */ /* Texture sizes: */ /* (it's quite possible that this is not easily configurable) */ static const int SUBSAMPLES_ORTHO = 7; static const int SUBSAMPLES_DIAG = 5; static const int MAX_DIST_ORTHO_COMPAT = 16; static const int MAX_DIST_ORTHO = 20; static const int MAX_DIST_DIAG = 20; static const int TEX_SIZE_ORTHO = 80; /* 16 * 5 slots = 80 */ static const int TEX_SIZE_DIAG = 80; /* 20 * 4 slots = 80 */ /* Number of samples for calculating areas in the diagonal textures: */ /* (diagonal areas are calculated using brute force sampling) */ static const int SAMPLES_DIAG = 30; /* Maximum distance for smoothing u-shapes: */ static const int SMOOTH_MAX_DISTANCE = 32; /*------------------------------------------------------------------------------*/ /* Offset Tables */ /* Offsets for subsample rendering */ static const double subsample_offsets_ortho[SUBSAMPLES_ORTHO] = { 0.0, /* 0 */ -0.25, /* 1 */ 0.25, /* 2 */ -0.125, /* 3 */ 0.125, /* 4 */ -0.375, /* 5 */ 0.375 /* 6 */ }; static const Dbl2 subsample_offsets_diag[SUBSAMPLES_DIAG] = { { 0.00, 0.00}, /* 0 */ { 0.25, -0.25}, /* 1 */ {-0.25, 0.25}, /* 2 */ { 0.125, -0.125}, /* 3 */ {-0.125, 0.125} /* 4 */ }; /* Mapping offsets for placing each pattern subtexture into its place */ enum edgesorthoIndices { EDGESORTHO_NONE_NONE = 0, EDGESORTHO_NONE_NEGA = 1, EDGESORTHO_NONE_POSI = 2, EDGESORTHO_NONE_BOTH = 3, EDGESORTHO_NEGA_NONE = 4, EDGESORTHO_NEGA_NEGA = 5, EDGESORTHO_NEGA_POSI = 6, EDGESORTHO_NEGA_BOTH = 7, EDGESORTHO_POSI_NONE = 8, EDGESORTHO_POSI_NEGA = 9, EDGESORTHO_POSI_POSI = 10, EDGESORTHO_POSI_BOTH = 11, EDGESORTHO_BOTH_NONE = 12, EDGESORTHO_BOTH_NEGA = 13, EDGESORTHO_BOTH_POSI = 14, EDGESORTHO_BOTH_BOTH = 15, }; static const Int2 edgesortho_compat[16] = { {0, 0}, {0, 1}, {0, 3}, {0, 4}, {1, 0}, {1, 1}, {1, 3}, {1, 4}, {3, 0}, {3, 1}, {3, 3}, {3, 4}, {4, 0}, {4, 1}, {4, 3}, {4, 4} }; static const Int2 edgesortho[16] = { {0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 0}, {1, 1}, {1, 2}, {1, 3}, {2, 0}, {2, 1}, {2, 2}, {2, 3}, {3, 0}, {3, 1}, {3, 2}, {3, 3} }; enum edgesdiagIndices { EDGESDIAG_NONE_NONE = 0, EDGESDIAG_NONE_VERT = 1, EDGESDIAG_NONE_HORZ = 2, EDGESDIAG_NONE_BOTH = 3, EDGESDIAG_VERT_NONE = 4, EDGESDIAG_VERT_VERT = 5, EDGESDIAG_VERT_HORZ = 6, EDGESDIAG_VERT_BOTH = 7, EDGESDIAG_HORZ_NONE = 8, EDGESDIAG_HORZ_VERT = 9, EDGESDIAG_HORZ_HORZ = 10, EDGESDIAG_HORZ_BOTH = 11, EDGESDIAG_BOTH_NONE = 12, EDGESDIAG_BOTH_VERT = 13, EDGESDIAG_BOTH_HORZ = 14, EDGESDIAG_BOTH_BOTH = 15, }; static const Int2 edgesdiag[16] = { {0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 0}, {1, 1}, {1, 2}, {1, 3}, {2, 0}, {2, 1}, {2, 2}, {2, 3}, {3, 0}, {3, 1}, {3, 2}, {3, 3} }; /*------------------------------------------------------------------------------*/ /* Miscellaneous Utility Functions */ /* Linear interpolation: */ static Dbl2 lerp(Dbl2 a, Dbl2 b, double p) { return a + (b - a) * Dbl2(p); } /* Saturates a value to [0..1] range: */ static double saturate(double x) { return 0.0 < x ? (x < 1.0 ? x : 1.0) : 0.0; } /*------------------------------------------------------------------------------*/ /* Horizontal/Vertical Areas */ class AreaOrtho { double m_data[SUBSAMPLES_ORTHO][TEX_SIZE_ORTHO][TEX_SIZE_ORTHO][2]; bool m_compat; bool m_orig_u; public: AreaOrtho(bool compat, bool orig_u) : m_compat(compat), m_orig_u(orig_u) {} double *getData() { return (double *)&m_data; } Dbl2 getPixel(int offset_index, Int2 coords) { return Dbl2(m_data[offset_index][coords.y][coords.x][0], m_data[offset_index][coords.y][coords.x][1]); } void areaTex(int offset_index); private: void putPixel(int offset_index, Int2 coords, Dbl2 pixel) { m_data[offset_index][coords.y][coords.x][0] = pixel.x; m_data[offset_index][coords.y][coords.x][1] = pixel.y; } Dbl2 smoothArea(double d, Dbl2 a1, Dbl2 a2); Dbl2 makeQuad(int x, double d, double o); Dbl2 area(Dbl2 p1, Dbl2 p2, int x); Dbl2 calculate(int pattern, int left, int right, double offset); }; /* Smoothing function for small u-patterns: */ Dbl2 AreaOrtho::smoothArea(double d, Dbl2 a1, Dbl2 a2) { Dbl2 b1 = (a1 * Dbl2(2.0)).apply(sqrt) * Dbl2(0.5); Dbl2 b2 = (a2 * Dbl2(2.0)).apply(sqrt) * Dbl2(0.5); double p = saturate(d / (double)SMOOTH_MAX_DISTANCE); return lerp(b1, a1, p) + lerp(b2, a2, p); } /* Smoothing u-patterns by quadratic function: */ Dbl2 AreaOrtho::makeQuad(int x, double d, double o) { double r = (double)x; /* fmin() below is a trick to smooth tiny u-patterns: */ return Dbl2(r, (1.0 - fmin(4.0, d) * r * (d - r) / (d * d)) * o); } /* Calculates the area under the line p1->p2, for the pixel x..x+1: */ Dbl2 AreaOrtho::area(Dbl2 p1, Dbl2 p2, int x) { Dbl2 d = p2 - p1; double x1 = (double)x; double x2 = x1 + 1.0; if ((x1 >= p1.x && x1 < p2.x) || (x2 > p1.x && x2 <= p2.x)) { /* inside? */ double y1 = p1.y + (x1 - p1.x) * d.y / d.x; double y2 = p1.y + (x2 - p1.x) * d.y / d.x; if ((copysign(1.0, y1) == copysign(1.0, y2) || fabs(y1) < 1e-4 || fabs(y2) < 1e-4)) { /* trapezoid? */ double a = (y1 + y2) / 2.0; if (a < 0.0) return Dbl2(fabs(a), 0.0); else return Dbl2(0.0, fabs(a)); } else { /* Then, we got two triangles: */ double x = p1.x - p1.y * d.x / d.y, xi; double a1 = x > p1.x ? y1 * modf(x, &xi) / 2.0 : 0.0; double a2 = x < p2.x ? y2 * (1.0 - modf(x, &xi)) / 2.0 : 0.0; double a = fabs(a1) > fabs(a2) ? a1 : -a2; if (a < 0.0) return Dbl2(fabs(a1), fabs(a2)); else return Dbl2(fabs(a2), fabs(a1)); } } else return Dbl2(0.0, 0.0); } /* Calculates the area for a given pattern and distances to the left and to the */ /* right, biased by an offset: */ Dbl2 AreaOrtho::calculate(int pattern, int left, int right, double offset) { Dbl2 a1, a2; /* * o1 | * .-------´ * o2 | * * <---d---> */ double d = (double)(left + right + 1); double o1 = 0.5 + offset; double o2 = 0.5 + offset - 1.0; switch (pattern) { case EDGESORTHO_NONE_NONE: { /* * * ------ * */ return Dbl2(0.0, 0.0); break; } case EDGESORTHO_POSI_NONE: { /* * * .------ * | * * We only offset L patterns in the crossing edge side, to make it * converge with the unfiltered pattern 0 (we don't want to filter the * pattern 0 to avoid artifacts). */ if (left <= right) return area(Dbl2(0.0, o2), Dbl2(d / 2.0, 0.0), left); else return Dbl2(0.0, 0.0); break; } case EDGESORTHO_NONE_POSI: { /* * * ------. * | */ if (left >= right) return area(Dbl2(d / 2.0, 0.0), Dbl2(d, o2), left); else return Dbl2(0.0, 0.0); break; } case EDGESORTHO_POSI_POSI: { /* * * .------. * | | */ if (m_orig_u) { a1 = area(Dbl2(0.0, o2), Dbl2(d / 2.0, 0.0), left); a2 = area(Dbl2(d / 2.0, 0.0), Dbl2(d, o2), left); return smoothArea(d, a1, a2); } else return area(makeQuad(left, d, o2), makeQuad(left + 1, d, o2), left); break; } case EDGESORTHO_NEGA_NONE: { /* * | * `------ * */ if (left <= right) return area(Dbl2(0.0, o1), Dbl2(d / 2.0, 0.0), left); else return Dbl2(0.0, 0.0); break; } case EDGESORTHO_BOTH_NONE: { /* * | * +------ * | */ return Dbl2(0.0, 0.0); break; } case EDGESORTHO_NEGA_POSI: { /* * | * `------. * | * * A problem of not offseting L patterns (see above), is that for certain * max search distances, the pixels in the center of a Z pattern will * detect the full Z pattern, while the pixels in the sides will detect a * L pattern. To avoid discontinuities, we blend the full offsetted Z * revectorization with partially offsetted L patterns. */ if (fabs(offset) > 0.0) { a1 = area(Dbl2(0.0, o1), Dbl2(d, o2), left); a2 = area(Dbl2(0.0, o1), Dbl2(d / 2.0, 0.0), left); a2 += area(Dbl2(d / 2.0, 0.0), Dbl2(d, o2), left); return (a1 + a2) / Dbl2(2.0); } else return area(Dbl2(0.0, o1), Dbl2(d, o2), left); break; } case EDGESORTHO_BOTH_POSI: { /* * | * +------. * | | */ return area(Dbl2(0.0, o1), Dbl2(d, o2), left); break; } case EDGESORTHO_NONE_NEGA: { /* * | * ------´ * */ if (left >= right) return area(Dbl2(d / 2.0, 0.0), Dbl2(d, o1), left); else return Dbl2(0.0, 0.0); break; } case EDGESORTHO_POSI_NEGA: { /* * | * .------´ * | */ if (fabs(offset) > 0.0) { a1 = area(Dbl2(0.0, o2), Dbl2(d, o1), left); a2 = area(Dbl2(0.0, o2), Dbl2(d / 2.0, 0.0), left); a2 += area(Dbl2(d / 2.0, 0.0), Dbl2(d, o1), left); return (a1 + a2) / Dbl2(2.0); } else return area(Dbl2(0.0, o2), Dbl2(d, o1), left); break; } case EDGESORTHO_NONE_BOTH: { /* * | * ------+ * | */ return Dbl2(0.0, 0.0); break; } case EDGESORTHO_POSI_BOTH: { /* * | * .------+ * | | */ return area(Dbl2(0.0, o2), Dbl2(d, o1), left); break; } case EDGESORTHO_NEGA_NEGA: { /* * | | * `------´ * */ if (m_orig_u) { a1 = area(Dbl2(0.0, o1), Dbl2(d / 2.0, 0.0), left); a2 = area(Dbl2(d / 2.0, 0.0), Dbl2(d, o1), left); return smoothArea(d, a1, a2); } else return area(makeQuad(left, d, o1), makeQuad(left + 1, d, o1), left); break; } case EDGESORTHO_BOTH_NEGA: { /* * | | * +------´ * | */ return area(Dbl2(0.0, o2), Dbl2(d, o1), left); break; } case EDGESORTHO_NEGA_BOTH: { /* * | | * `------+ * | */ return area(Dbl2(0.0, o1), Dbl2(d, o2), left); break; } case EDGESORTHO_BOTH_BOTH: { /* * | | * +------+ * | | */ return Dbl2(0.0, 0.0); break; } } return Dbl2(0.0, 0.0); } /*------------------------------------------------------------------------------*/ /* Diagonal Areas */ class AreaDiag { double m_data[SUBSAMPLES_DIAG][TEX_SIZE_DIAG][TEX_SIZE_DIAG][2]; bool m_numeric; bool m_orig_u; public: AreaDiag(bool numeric, bool orig_u) : m_numeric(numeric), m_orig_u(orig_u) {} double *getData() { return (double *)&m_data; } Dbl2 getPixel(int offset_index, Int2 coords) { return Dbl2(m_data[offset_index][coords.y][coords.x][0], m_data[offset_index][coords.y][coords.x][1]); } void areaTex(int offset_index); private: void putPixel(int offset_index, Int2 coords, Dbl2 pixel) { m_data[offset_index][coords.y][coords.x][0] = pixel.x; m_data[offset_index][coords.y][coords.x][1] = pixel.y; } double area1(Dbl2 p1, Dbl2 p2, Int2 p); Dbl2 area(Dbl2 p1, Dbl2 p2, int left); Dbl2 areaTriangle(Dbl2 p1L, Dbl2 p2L, Dbl2 p1R, Dbl2 p2R, int left); Dbl2 calculate(int pattern, int left, int right, Dbl2 offset); }; /* Calculates the area under the line p1->p2 for the pixel 'p' using brute */ /* force sampling: */ /* (quick and dirty solution, but it works) */ double AreaDiag::area1(Dbl2 p1, Dbl2 p2, Int2 p) { if (p1 == p2) return 1.0; double xm = (p1.x + p2.x) / 2.0, ym = (p1.y + p2.y) / 2.0; double a = p2.y - p1.y; double b = p1.x - p2.x; int count = 0; for (int ix = 0; ix < SAMPLES_DIAG; ix++) { double x = (double)p.x + (double)ix / (double)(SAMPLES_DIAG - 1); for (int iy = 0; iy < SAMPLES_DIAG; iy++) { double y = (double)p.y + (double)iy / (double)(SAMPLES_DIAG - 1); if (a * (x - xm) + b * (y - ym) > 0.0) /* inside? */ count++; } } return (double)count / (double)(SAMPLES_DIAG * SAMPLES_DIAG); } /* Calculates the area under the line p1->p2: */ /* (includes the pixel and its opposite) */ Dbl2 AreaDiag::area(Dbl2 p1, Dbl2 p2, int left) { if (m_numeric) { double a1 = area1(p1, p2, Int2(1, 0) + Int2(left)); double a2 = area1(p1, p2, Int2(1, 1) + Int2(left)); return Dbl2(1.0 - a1, a2); } /* Calculates the area under the line p1->p2 for the pixel 'p' analytically */ Dbl2 d = p2 - p1; if (d.x == 0.0) return Dbl2(0.0, 1.0); if (d.y == 0.0) return Dbl2(1.0, 0.0); double x1 = (double)(1 + left); double x2 = x1 + 1.0; double ymid = x1; double xtop = p1.x + (ymid + 1.0 - p1.y) * d.x / d.y; double xmid = p1.x + (ymid - p1.y) * d.x / d.y; double xbot = p1.x + (ymid - 1.0 - p1.y) * d.x / d.y; double y1 = p1.y + (x1 - p1.x) * d.y / d.x; double y2 = p1.y + (x2 - p1.x) * d.y / d.x; double fy1 = y1 - floor(y1); double fy2 = y2 - floor(y2); int iy1 = (int)floor(y1 - ymid); int iy2 = (int)floor(y2 - ymid); if (iy1 <= -2) { if (iy2 == -1) return Dbl2(1.0 - (x2 - xbot) * fy2 * 0.5, 0.0); else if (iy2 == 0) return Dbl2((xmid + xbot) * 0.5 - x1, (x2 - xmid) * fy2 * 0.5); else if (iy2 >= 1) return Dbl2((xmid + xbot) * 0.5 - x1, x2 - (xtop + xmid) * 0.5); else /* iy2 < -1 */ return Dbl2(1.0, 0.0); } else if (iy1 == -1) { if (iy2 == -1) return Dbl2(1.0 - (fy1 + fy2) * 0.5, 0.0); else if (iy2 == 0) return Dbl2((xmid - x1) * (1.0 - fy1) * 0.5, (x2 - xmid) * fy2 * 0.5); else if (iy2 >= 1) return Dbl2((xmid - x1) * (1.0 - fy1) * 0.5, x2 - (xtop + xmid) * 0.5); else /* iy2 < -1 */ return Dbl2(1.0 - (xbot - x1) * fy1 * 0.5, 0.0); } else if (iy1 == 0) { if (iy2 == -1) return Dbl2((x2 - xmid) * (1.0 - fy2) * 0.5, (xmid - x1) * fy1 * 0.5); else if (iy2 == 0) return Dbl2(0.0, (fy1 + fy2) * 0.5); else if (iy2 >= 1) return Dbl2(0.0, 1.0 - (xtop - x1) * (1.0 - fy1) * 0.5); else /* iy2 < -1 */ return Dbl2(x2 - (xmid + xbot) * 0.5, (xmid - x1) * fy1 * 0.5); } else { /* iy1 > 0 */ if (iy2 == -1) return Dbl2((x2 - xtop) * (1.0 - fy2) * 0.5, (xtop + xmid) * 0.5 - x1); else if (iy2 == 0) return Dbl2(0.0, 1.0 - (x1 - xtop) * (1.0 - fy2) * 0.5); else if (iy2 >= 1) return Dbl2(0.0, 1.0); else /* iy2 < -1 */ return Dbl2(x2 - (xmid + xbot) * 0.5, (xtop + xmid) * 0.5 - x1); } } /* Calculate u-patterns using a triangle: */ Dbl2 AreaDiag::areaTriangle(Dbl2 p1L, Dbl2 p2L, Dbl2 p1R, Dbl2 p2R, int left) { double x1 = (double)(1 + left); double x2 = x1 + 1.0; Dbl2 dL = p2L - p1L; Dbl2 dR = p2R - p1R; double xm = ((p1L.x * dL.y / dL.x - p1L.y) - (p1R.x * dR.y / dR.x - p1R.y)) / (dL.y / dL.x - dR.y / dR.x); double y1 = (x1 < xm) ? p1L.y + (x1 - p1L.x) * dL.y / dL.x : p1R.y + (x1 - p1R.x) * dR.y / dR.x; double y2 = (x2 < xm) ? p1L.y + (x2 - p1L.x) * dL.y / dL.x : p1R.y + (x2 - p1R.x) * dR.y / dR.x; return area(Dbl2(x1, y1), Dbl2(x2, y2), left); } /* Calculates the area for a given pattern and distances to the left and to the */ /* right, biased by an offset: */ Dbl2 AreaDiag::calculate(int pattern, int left, int right, Dbl2 offset) { Dbl2 a1, a2; double d = (double)(left + right + 1); /* * There is some Black Magic around diagonal area calculations. Unlike * orthogonal patterns, the 'null' pattern (one without crossing edges) must be * filtered, and the ends of both the 'null' and L patterns are not known: L * and U patterns have different endings, and we don't know what is the * adjacent pattern. So, what we do is calculate a blend of both possibilites. */ switch (pattern) { case EDGESDIAG_NONE_NONE: { /* * * .-´ * .-´ * .-´ * .-´ * ´ * */ a1 = area(Dbl2(1.0, 1.0), Dbl2(1.0, 1.0) + Dbl2(d), left); /* 1st possibility */ a2 = area(Dbl2(1.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d), left); /* 2nd possibility */ return (a1 + a2) / Dbl2(2.0); /* Blend them */ break; } case EDGESDIAG_VERT_NONE: { /* * * .-´ * .-´ * .-´ * .-´ * | * | */ a1 = area(Dbl2(1.0, 0.0) + offset, Dbl2(0.0, 0.0) + Dbl2(d), left); a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d), left); return (a1 + a2) / Dbl2(2.0); break; } case EDGESDIAG_NONE_HORZ: { /* * * .---- * .-´ * .-´ * .-´ * ´ * */ a1 = area(Dbl2(0.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); a2 = area(Dbl2(1.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); return (a1 + a2) / Dbl2(2.0); break; } case EDGESDIAG_VERT_HORZ: { /* * * .---- * .-´ * .-´ * .-´ * | * | */ if (m_orig_u) return area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); else return areaTriangle(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d), Dbl2(0.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); break; } case EDGESDIAG_HORZ_NONE: { /* * * .-´ * .-´ * .-´ * ----´ * * */ a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(0.0, 0.0) + Dbl2(d), left); a2 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d), left); return (a1 + a2) / Dbl2(2.0); break; } case EDGESDIAG_BOTH_NONE: { /* * * .-´ * .-´ * .-´ * --.-´ * | * | */ a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(0.0, 0.0) + Dbl2(d), left); a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d), left); return (a1 + a2) / Dbl2(2.0); break; } case EDGESDIAG_HORZ_HORZ: { /* * * .---- * .-´ * .-´ * ----´ * * */ return area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); break; } case EDGESDIAG_BOTH_HORZ: { /* * * .---- * .-´ * .-´ * --.-´ * | * | */ a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); return (a1 + a2) / Dbl2(2.0); break; } case EDGESDIAG_NONE_VERT: { /* * | * | * .-´ * .-´ * .-´ * ´ * */ a1 = area(Dbl2(0.0, 0.0), Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); a2 = area(Dbl2(1.0, 0.0), Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); return (a1 + a2) / Dbl2(2.0); break; } case EDGESDIAG_VERT_VERT: { /* * | * | * .-´ * .-´ * .-´ * | * | */ return area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); break; } case EDGESDIAG_NONE_BOTH: { /* * | * .---- * .-´ * .-´ * .-´ * ´ * */ a1 = area(Dbl2(0.0, 0.0), Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); a2 = area(Dbl2(1.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); return (a1 + a2) / Dbl2(2.0); break; } case EDGESDIAG_VERT_BOTH: { /* * | * .---- * .-´ * .-´ * .-´ * | * | */ a1 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); return (a1 + a2) / Dbl2(2.0); break; } case EDGESDIAG_HORZ_VERT: { /* * | * | * .-´ * .-´ * ----´ * * */ if (m_orig_u) return area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); else return areaTriangle(Dbl2(1.0, 1.0) + offset, Dbl2(2.0, 1.0) + Dbl2(d), Dbl2(1.0, 0.0), Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); break; } case EDGESDIAG_BOTH_VERT: { /* * | * | * .-´ * .-´ * --.-´ * | * | */ a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); return (a1 + a2) / Dbl2(2.0); break; } case EDGESDIAG_HORZ_BOTH: { /* * | * .---- * .-´ * .-´ * ----´ * * */ a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); a2 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); return (a1 + a2) / Dbl2(2.0); break; } case EDGESDIAG_BOTH_BOTH: { /* * | * .---- * .-´ * .-´ * --.-´ * | * | */ a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); return (a1 + a2) / Dbl2(2.0); break; } } return Dbl2(0.0, 0.0); } /*------------------------------------------------------------------------------*/ /* Main Loops */ void AreaOrtho::areaTex(int offset_index) { double offset = subsample_offsets_ortho[offset_index]; int max_dist = m_compat ? MAX_DIST_ORTHO_COMPAT : MAX_DIST_ORTHO; for (int pattern = 0; pattern < 16; pattern++) { Int2 e = Int2(max_dist) * (m_compat ? edgesortho_compat : edgesortho)[pattern]; for (int left = 0; left < max_dist; left++) { for (int right = 0; right < max_dist; right++) { Dbl2 p = calculate(pattern, left * left, right * right, offset); Int2 coords = e + Int2(left, right); putPixel(offset_index, coords, p); } } } return; } void AreaDiag::areaTex(int offset_index) { Dbl2 offset = subsample_offsets_diag[offset_index]; for (int pattern = 0; pattern < 16; pattern++) { Int2 e = Int2(MAX_DIST_DIAG) * edgesdiag[pattern]; for (int left = 0; left < MAX_DIST_DIAG; left++) { for (int right = 0; right < MAX_DIST_DIAG; right++) { Dbl2 p = calculate(pattern, left, right, offset); Int2 coords = e + Int2(left, right); putPixel(offset_index, coords, p); } } } return; } /*------------------------------------------------------------------------------*/ /* Write File to Specified Location on Disk */ /* C/C++ source code (arrays of floats) */ static void write_double_array(FILE *fp, const double *ptr, int length, const char *array_name, bool quantize) { fprintf(fp, "static const float %s[%d] = {", array_name, length); for (int n = 0; n < length; n++) { if (n > 0) fprintf(fp, ","); fprintf(fp, (n % 8 != 0) ? " " : "\n\t"); if (quantize) fprintf(fp, "%3d / 255.0", (int)(*(ptr++) * 255.0)); else fprintf(fp, "%1.8lf", *(ptr++)); } fprintf(fp, "\n};\n"); } static void write_csource(AreaOrtho *ortho, AreaDiag *diag, FILE *fp, bool subsampling, bool quantize) { fprintf(fp, "/* This file was generated by smaa_areatex.cpp */\n"); fprintf(fp, "\n/* Horizontal/Vertical Areas */\n"); write_double_array(fp, ortho->getData(), TEX_SIZE_ORTHO * TEX_SIZE_ORTHO * 2 * (subsampling ? SUBSAMPLES_ORTHO : 1), "areatex", quantize); fprintf(fp, "\n/* Diagonal Areas */\n"); write_double_array(fp, diag->getData(), TEX_SIZE_DIAG * TEX_SIZE_DIAG * 2 * (subsampling ? SUBSAMPLES_DIAG : 1), "areatex_diag", quantize); } /* .tga File (RGBA 32bit uncompressed) */ static void write_tga(AreaOrtho *ortho, AreaDiag *diag, FILE *fp, bool subsampling) { int subsamples = subsampling ? SUBSAMPLES_ORTHO : 1; unsigned char header[18] = {0, 0, 2, /* uncompressed RGB */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, /* 32bit */ 8}; /* 8bit alpha, left to right, bottom to top */ /* Set width and height */ header[12] = (TEX_SIZE_ORTHO + TEX_SIZE_DIAG) & 0xff; header[13] = ((TEX_SIZE_ORTHO + TEX_SIZE_DIAG) >> 8) & 0xff; header[14] = (subsamples * TEX_SIZE_ORTHO) & 0xff; header[15] = ((subsamples * TEX_SIZE_ORTHO) >> 8) & 0xff; /* Write .tga header */ fwrite(header, sizeof(unsigned char), sizeof(header) / sizeof(unsigned char), fp); /* Write pixel data */ for (int i = subsamples - 1; i >= 0; i--) { for (int y = TEX_SIZE_ORTHO - 1; y >= 0; y--) { for (int x = 0; x < TEX_SIZE_ORTHO; x++) { Dbl2 p = ortho->getPixel(i, Int2(x, y)); fputc(0, fp); /* B */ fputc((unsigned char)(p.y * 255.0), fp); /* G */ fputc((unsigned char)(p.x * 255.0), fp); /* R */ fputc(0, fp); /* A */ } for (int x = 0; x < TEX_SIZE_DIAG; x++) { if (i < SUBSAMPLES_DIAG) { Dbl2 p = diag->getPixel(i, Int2(x, y)); fputc(0, fp); /* B */ fputc((unsigned char)(p.y * 255.0), fp); /* G */ fputc((unsigned char)(p.x * 255.0), fp); /* R */ fputc(0, fp); /* A */ } else { fputc(0, fp); fputc(0, fp); fputc(0, fp); fputc(0, fp); } } } } } /* .raw File (R8G8 raw data) */ static void write_raw(AreaOrtho *ortho, AreaDiag *diag, FILE *fp, bool subsampling) { int subsamples = subsampling ? SUBSAMPLES_ORTHO : 1; /* Write pixel data */ for (int i = 0; i < subsamples; i++) { for (int y = 0; y < TEX_SIZE_ORTHO; y++) { for (int x = 0; x < TEX_SIZE_ORTHO; x++) { Dbl2 p = ortho->getPixel(i, Int2(x, y)); fputc((unsigned char)(p.x * 255.0), fp); /* R */ fputc((unsigned char)(p.y * 255.0), fp); /* G */ } for (int x = 0; x < TEX_SIZE_DIAG; x++) { if (i < SUBSAMPLES_DIAG) { Dbl2 p = diag->getPixel(i, Int2(x, y)); fputc((unsigned char)(p.x * 255.0), fp); /* R */ fputc((unsigned char)(p.y * 255.0), fp); /* G */ } else { fputc(0, fp); fputc(0, fp); } } } } } static int generate_file(AreaOrtho *ortho, AreaDiag *diag, const char *path, bool subsampling, bool quantize, bool tga, bool raw) { FILE *fp = fopen(path, tga ? "wb" : "w"); if (!fp) { fprintf(stderr, "Unable to open file: %s\n", path); return 1; } // fprintf(stderr, "Generating %s\n", path); if (tga) write_tga(ortho, diag, fp, subsampling); else if (raw) write_raw(ortho, diag, fp, subsampling); else write_csource(ortho, diag, fp, subsampling, quantize); fclose(fp); return 0; } int main(int argc, char **argv) { bool subsampling = false; bool quantize = false; bool tga = false; bool raw = false; bool compat = false; bool numeric = false; bool orig_u = false; bool help = false; char *outfile = NULL; int status = 0; for (int i = 1; i < argc; i++) { char *ptr = argv[i]; if (*ptr++ == '-' && *ptr != '\0') { char c; while ((c = *ptr++) != '\0') { if (c == 's') subsampling = true; else if (c == 'q') quantize = true; else if (c == 't') tga = true; else if (c == 'r') raw = true; else if (c == 'c') compat = true; else if (c == 'n') numeric = true; else if (c == 'u') orig_u = true; else if (c == 'h') help = true; else { fprintf(stderr, "Unknown option: -%c\n", c); status = 1; break; } } } else if (outfile) { fprintf(stderr, "Too much file names: %s, %s\n", outfile, argv[i]); status = 1; } else outfile = argv[i]; if (status != 0) break; } if (status == 0 && !help && !outfile) { fprintf(stderr, "File name was not specified.\n"); status = 1; } if (status != 0 || help) { fprintf(stderr, "Usage: %s [OPTION]... OUTFILE\n", argv[0]); fprintf(stderr, "Options:\n"); fprintf(stderr, " -s Calculate data for subpixel rendering\n"); fprintf(stderr, " -q Quantize data to 256 levels\n"); fprintf(stderr, " -t Write TGA image instead of C/C++ source\n"); fprintf(stderr, " -r Write R8G8 raw image instead of C/C++ source\n"); fprintf(stderr, " -c Generate compatible orthogonal data that subtexture size is 16\n"); fprintf(stderr, " -n Numerically calculate diagonal data using brute force sampling\n"); fprintf(stderr, " -u Process orthogonal / diagonal U patterns in older ways\n"); fprintf(stderr, " -h Print this help and exit\n"); fprintf(stderr, "File name OUTFILE usually should have an extension such as .c, .h, or .tga,\n"); fprintf(stderr, "except for a special name '-' that means standard output.\n\n"); fprintf(stderr, "Example:\n"); fprintf(stderr, " Generate TGA file exactly same as AreaTexDX10.tga bundled with the\n"); fprintf(stderr, " original implementation:\n\n"); fprintf(stderr, " $ smaa_areatex -stcnu AreaTexDX10.tga\n\n"); return status; } AreaOrtho *ortho = new AreaOrtho(compat, orig_u); AreaDiag *diag = new AreaDiag(numeric, orig_u); /* Calculate areatex data */ for (int i = 0; i < (subsampling ? SUBSAMPLES_ORTHO : 1); i++) ortho->areaTex(i); for (int i = 0; i < (subsampling ? SUBSAMPLES_DIAG : 1); i++) diag->areaTex(i); /* Generate .tga, .raw, or C/C++ source file, or write the data to stdout */ if (strcmp(outfile, "-") != 0) status = generate_file(ortho, diag, outfile, subsampling, quantize, tga, raw); else if (tga) write_tga(ortho, diag, stdout, subsampling); else if (raw) write_raw(ortho, diag, stdout, subsampling); else write_csource(ortho, diag, stdout, subsampling, quantize); delete ortho; delete diag; return status; } /* smaa_areatex.cpp ends here */