├── CMakeLists.txt ├── LICENSE ├── README.md ├── bin ├── CMakeLists.txt ├── smaa_areatex.cpp └── smaa_png.cpp ├── include ├── CMakeLists.txt ├── smaa.h ├── smaa_types.h └── smaa_version.h.in ├── lib ├── CMakeLists.txt └── smaa.cpp └── tests ├── CMakeLists.txt ├── circle.png ├── circle_aa.png ├── invader.png ├── invader_aa.png ├── mizuki.png ├── mizuki_aa.png ├── monkey.png ├── monkey_aa.png ├── pattern.png ├── pattern2.png ├── pattern2_aa.png ├── pattern_aa.png ├── square.png ├── square_aa.png ├── suzu.png └── suzu_aa.png /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2016-2021 IRIE Shinsuke 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | cmake_minimum_required(VERSION 3.1) 23 | 24 | project(smaa-cpp 25 | VERSION 0.4.1 26 | LANGUAGES CXX 27 | ) 28 | 29 | set(CMAKE_CXX_STANDARD 11) 30 | 31 | set(PROJECT_SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) 32 | 33 | # Options 34 | option(WITH_SUBPIXEL_RENDERING "Enable subpixel rendering" ON) 35 | option(WITH_LIBRARY_STATIC "Enable building static library" ON) 36 | option(WITH_LIBRARY_SHARED "Enable building shared library" OFF) 37 | option(WITH_EXAMPLE "Enable building sample program" ON) 38 | option(WITH_TESTS "Enable tests running sample program" ON) 39 | option(WITH_INSTALL_HEADERS "Force installation of library headers" OFF) 40 | option(WITH_INSTALL_BIN "Install command line programs" OFF) 41 | 42 | option(WITH_EXAMPLE_PREFER_SHLIB "Sample program prefers linking shared library" OFF) 43 | mark_as_advanced(WITH_EXAMPLE_PREFER_SHLIB) 44 | 45 | # Dependencies 46 | if(WITH_EXAMPLE) 47 | find_package(PNG) 48 | if(PNG_FOUND) 49 | if(NOT WITH_LIBRARY_SHARED AND NOT WITH_LIBRARY_STATIC) 50 | set(WITH_LIBRARY_STATIC ON) 51 | endif() 52 | else() 53 | message(WARNING "PNG library not found, disabling WITH_EXAMPLE") 54 | set(WITH_EXAMPLE OFF) 55 | endif() 56 | endif() 57 | 58 | if(WITH_TESTS AND NOT WITH_EXAMPLE) 59 | message(WARNING "Running tests needs WITH_EXAMPLE=ON, disabling WITH_TESTS") 60 | set(WITH_TESTS OFF) 61 | endif() 62 | 63 | if(WITH_LIBRARY_STATIC) 64 | set(WITH_INSTALL_HEADERS ON) 65 | endif() 66 | 67 | # Subdirectories 68 | add_subdirectory(bin) 69 | 70 | if(WITH_LIBRARY_STATIC OR WITH_LIBRARY_SHARED) 71 | add_subdirectory(lib) 72 | add_subdirectory(include) 73 | endif() 74 | 75 | # Tests 76 | if (WITH_TESTS AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tests) 77 | enable_testing() 78 | add_subdirectory(tests) 79 | set(WITH_EXAMPLE ON) 80 | endif() 81 | 82 | # Packaging 83 | # Don't use ZIP generator if including shared library for Linux, 84 | # otherwise symlinks will be lost. 85 | set(CPACK_PACKAGE_FILE_NAME ${PROJECT_NAME}_${PROJECT_VERSION}_${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}) 86 | include(CPack) 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (C) 2016-2021 IRIE Shinsuke 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- 23 | bin/smaa_png.cpp (a derivative of http://zarb.org/~gc/resource/libpng-short-example.c): 24 | 25 | Copyright 2002-2011 Guillaume Cottenceau and contributors. 26 | 2014-2017 IRIE Shinsuke 27 | 28 | This software may be freely redistributed under the terms 29 | of the X11 license. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smaa-cpp 2 | An implementation of Enhanced Subpixel Morphological Antialiasing (SMAA) written in C++ 3 | 4 | ## Requirements 5 | - CMake (>= 3.1) 6 | - libpng (optional, needed for tests and example) 7 | 8 | ## Building 9 | ``` 10 | mkdir build 11 | cd build 12 | cmake path/to/source 13 | make 14 | sudo make install 15 | ``` 16 | 17 | ## API Overview 18 | The folloing two classes are provided. See header files and example (bin/smaa_png.cpp) for more details. 19 | 20 | ### PixelShader class 21 | Pixel shaders similar to the original SMAA implementation: 22 | 23 | smaa-cpp | original HLSL 24 | ---------|-------------- 25 | SMAA::PixelShader::lumaEdgeDetection()|SMAALumaEdgeDetectionPS() 26 | SMAA::PixelShader::colorEdgeDetection()|SMAAColorEdgeDetectionPS() 27 | SMAA::PixelShader::depthEdgeDetection()|SMAADepthEdgeDetectionPS() 28 | SMAA::PixelShader::blendingWeightCalculation()|SMAABlendingWeightCalculationPS() 29 | SMAA::PixelShader::neighborhoodBlending()|SMAANeighborhoodBlendingPS() 30 | 31 | ### ImageReader class 32 | This is used for defining getPixel() member function as a callback. 33 | 34 | ## Platforms 35 | Tested only on Linux. 36 | 37 | ## License 38 | MIT license. 39 | -------------------------------------------------------------------------------- /bin/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2016-2021 IRIE Shinsuke 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | cmake_minimum_required(VERSION 3.1) 23 | 24 | set(INCDIR ../include) 25 | set(GENDIR ${CMAKE_CURRENT_BINARY_DIR}/../include) 26 | 27 | include_directories(${INCDIR} ${GENDIR}) 28 | 29 | add_executable(smaa_areatex smaa_areatex.cpp) 30 | 31 | if(WITH_INSTALL_BIN) 32 | install(TARGETS smaa_areatex RUNTIME DESTINATION bin) 33 | endif() 34 | 35 | if(WITH_EXAMPLE) 36 | set(SRC 37 | smaa_png.cpp 38 | ${INCDIR}/smaa.h 39 | ${INCDIR}/smaa_types.h 40 | ${GENDIR}/smaa_version.h 41 | ) 42 | 43 | add_executable(smaa_png ${SRC}) 44 | include_directories(${PNG_INCLUDE_DIR}) 45 | 46 | if ((WITH_LIBRARY_STATIC AND NOT WITH_LIBRARY_SHARED) OR 47 | (WITH_LIBRARY_STATIC AND WITH_LIBRARY_SHARED AND NOT WITH_EXAMPLE_PREFER_SHLIB)) 48 | target_link_libraries(smaa_png smaa-static ${PNG_LIBRARY}) 49 | else() 50 | target_link_libraries(smaa_png smaa-shared ${PNG_LIBRARY}) 51 | endif() 52 | 53 | if(WITH_INSTALL_BIN) 54 | install(TARGETS smaa_png DESTINATION bin) 55 | endif() 56 | endif() 57 | -------------------------------------------------------------------------------- /bin/smaa_areatex.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2021 IRIE Shinsuke 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | /* 24 | * smaa_areatex.cpp version 0.4.1 25 | * 26 | * This is a part of smaa-cpp that is an implementation of 27 | * Enhanced Subpixel Morphological Antialiasing (SMAA) written in C++. 28 | * 29 | * This program is C++ rewrite of AreaTex.py included in the original 30 | * SMAA ditribution: 31 | * 32 | * https://github.com/iryoku/smaa/tree/master/Scripts 33 | */ 34 | 35 | #include 36 | #include 37 | #include 38 | 39 | #include 40 | 41 | /*------------------------------------------------------------------------------*/ 42 | /* Type Definitions */ 43 | 44 | class Int2; 45 | class Dbl2; 46 | 47 | class Int2 { 48 | public: 49 | int x, y; 50 | 51 | Int2() { this->x = this->y = 0; } 52 | Int2(int x) { this->x = this->y = x; } 53 | Int2(int x, int y) { this->x = x; this->y = y; } 54 | 55 | operator Dbl2(); 56 | 57 | Int2 operator + (Int2 other) { return Int2(x + other.x, y + other.y); } 58 | Int2 operator * (Int2 other) { return Int2(x * other.x, y * other.y); } 59 | }; 60 | 61 | class Dbl2 { 62 | public: 63 | double x, y; 64 | 65 | Dbl2() { this->x = this->y = 0.0; } 66 | Dbl2(double x) { this->x = this->y = x; } 67 | Dbl2(double x, double y) { this->x = x; this->y = y; } 68 | 69 | Dbl2 apply(double (* func)(double)) { return Dbl2(func(x), func(y)); } 70 | 71 | operator Int2(); 72 | 73 | Dbl2 operator + (Dbl2 other) { return Dbl2(x + other.x, y + other.y); } 74 | Dbl2 operator - (Dbl2 other) { return Dbl2(x - other.x, y - other.y); } 75 | Dbl2 operator * (Dbl2 other) { return Dbl2(x * other.x, y * other.y); } 76 | Dbl2 operator / (Dbl2 other) { return Dbl2(x / other.x, y / other.y); } 77 | Dbl2 operator += (Dbl2 other) { return Dbl2(x += other.x, y += other.y); } 78 | bool operator == (Dbl2 other) { return (x == other.x && y == other.y); } 79 | }; 80 | 81 | Int2::operator Dbl2() { return Dbl2((double)x, (double)y); } 82 | Dbl2::operator Int2() { return Int2((int)x, (int)y); } 83 | 84 | /*------------------------------------------------------------------------------*/ 85 | /* Data to Calculate Areatex */ 86 | 87 | /* Texture sizes: */ 88 | /* (it's quite possible that this is not easily configurable) */ 89 | static const int SUBSAMPLES_ORTHO = 7; 90 | static const int SUBSAMPLES_DIAG = 5; 91 | static const int MAX_DIST_ORTHO_COMPAT = 16; 92 | static const int MAX_DIST_ORTHO = 20; 93 | static const int MAX_DIST_DIAG = 20; 94 | static const int TEX_SIZE_ORTHO = 80; /* 16 * 5 slots = 80 */ 95 | static const int TEX_SIZE_DIAG = 80; /* 20 * 4 slots = 80 */ 96 | 97 | /* Number of samples for calculating areas in the diagonal textures: */ 98 | /* (diagonal areas are calculated using brute force sampling) */ 99 | static const int SAMPLES_DIAG = 30; 100 | 101 | /* Maximum distance for smoothing u-shapes: */ 102 | static const int SMOOTH_MAX_DISTANCE = 32; 103 | 104 | /*------------------------------------------------------------------------------*/ 105 | /* Offset Tables */ 106 | 107 | /* Offsets for subsample rendering */ 108 | static const double subsample_offsets_ortho[SUBSAMPLES_ORTHO] = { 109 | 0.0, /* 0 */ 110 | -0.25, /* 1 */ 111 | 0.25, /* 2 */ 112 | -0.125, /* 3 */ 113 | 0.125, /* 4 */ 114 | -0.375, /* 5 */ 115 | 0.375 /* 6 */ 116 | }; 117 | 118 | static const Dbl2 subsample_offsets_diag[SUBSAMPLES_DIAG] = { 119 | { 0.00, 0.00}, /* 0 */ 120 | { 0.25, -0.25}, /* 1 */ 121 | {-0.25, 0.25}, /* 2 */ 122 | { 0.125, -0.125}, /* 3 */ 123 | {-0.125, 0.125} /* 4 */ 124 | }; 125 | 126 | /* Mapping offsets for placing each pattern subtexture into its place */ 127 | enum edgesorthoIndices 128 | { 129 | EDGESORTHO_NONE_NONE = 0, 130 | EDGESORTHO_NONE_NEGA = 1, 131 | EDGESORTHO_NONE_POSI = 2, 132 | EDGESORTHO_NONE_BOTH = 3, 133 | EDGESORTHO_NEGA_NONE = 4, 134 | EDGESORTHO_NEGA_NEGA = 5, 135 | EDGESORTHO_NEGA_POSI = 6, 136 | EDGESORTHO_NEGA_BOTH = 7, 137 | EDGESORTHO_POSI_NONE = 8, 138 | EDGESORTHO_POSI_NEGA = 9, 139 | EDGESORTHO_POSI_POSI = 10, 140 | EDGESORTHO_POSI_BOTH = 11, 141 | EDGESORTHO_BOTH_NONE = 12, 142 | EDGESORTHO_BOTH_NEGA = 13, 143 | EDGESORTHO_BOTH_POSI = 14, 144 | EDGESORTHO_BOTH_BOTH = 15, 145 | }; 146 | 147 | static const Int2 edgesortho_compat[16] = { 148 | {0, 0}, {0, 1}, {0, 3}, {0, 4}, {1, 0}, {1, 1}, {1, 3}, {1, 4}, 149 | {3, 0}, {3, 1}, {3, 3}, {3, 4}, {4, 0}, {4, 1}, {4, 3}, {4, 4} 150 | }; 151 | 152 | static const Int2 edgesortho[16] = { 153 | {0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 0}, {1, 1}, {1, 2}, {1, 3}, 154 | {2, 0}, {2, 1}, {2, 2}, {2, 3}, {3, 0}, {3, 1}, {3, 2}, {3, 3} 155 | }; 156 | 157 | enum edgesdiagIndices 158 | { 159 | EDGESDIAG_NONE_NONE = 0, 160 | EDGESDIAG_NONE_VERT = 1, 161 | EDGESDIAG_NONE_HORZ = 2, 162 | EDGESDIAG_NONE_BOTH = 3, 163 | EDGESDIAG_VERT_NONE = 4, 164 | EDGESDIAG_VERT_VERT = 5, 165 | EDGESDIAG_VERT_HORZ = 6, 166 | EDGESDIAG_VERT_BOTH = 7, 167 | EDGESDIAG_HORZ_NONE = 8, 168 | EDGESDIAG_HORZ_VERT = 9, 169 | EDGESDIAG_HORZ_HORZ = 10, 170 | EDGESDIAG_HORZ_BOTH = 11, 171 | EDGESDIAG_BOTH_NONE = 12, 172 | EDGESDIAG_BOTH_VERT = 13, 173 | EDGESDIAG_BOTH_HORZ = 14, 174 | EDGESDIAG_BOTH_BOTH = 15, 175 | }; 176 | 177 | static const Int2 edgesdiag[16] = { 178 | {0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 0}, {1, 1}, {1, 2}, {1, 3}, 179 | {2, 0}, {2, 1}, {2, 2}, {2, 3}, {3, 0}, {3, 1}, {3, 2}, {3, 3} 180 | }; 181 | 182 | /*------------------------------------------------------------------------------*/ 183 | /* Miscellaneous Utility Functions */ 184 | 185 | /* Linear interpolation: */ 186 | static Dbl2 lerp(Dbl2 a, Dbl2 b, double p) 187 | { 188 | return a + (b - a) * Dbl2(p); 189 | } 190 | 191 | /* Saturates a value to [0..1] range: */ 192 | static double saturate(double x) 193 | { 194 | return 0.0 < x ? (x < 1.0 ? x : 1.0) : 0.0; 195 | } 196 | 197 | /*------------------------------------------------------------------------------*/ 198 | /* Horizontal/Vertical Areas */ 199 | 200 | class AreaOrtho { 201 | double m_data[SUBSAMPLES_ORTHO][TEX_SIZE_ORTHO][TEX_SIZE_ORTHO][2]; 202 | bool m_compat; 203 | bool m_orig_u; 204 | public: 205 | AreaOrtho(bool compat, bool orig_u) : m_compat(compat), m_orig_u(orig_u) {} 206 | 207 | double *getData() { return (double *)&m_data; } 208 | Dbl2 getPixel(int offset_index, Int2 coords) { 209 | return Dbl2(m_data[offset_index][coords.y][coords.x][0], 210 | m_data[offset_index][coords.y][coords.x][1]); 211 | } 212 | 213 | void areaTex(int offset_index); 214 | private: 215 | void putPixel(int offset_index, Int2 coords, Dbl2 pixel) { 216 | m_data[offset_index][coords.y][coords.x][0] = pixel.x; 217 | m_data[offset_index][coords.y][coords.x][1] = pixel.y; 218 | } 219 | 220 | Dbl2 smoothArea(double d, Dbl2 a1, Dbl2 a2); 221 | Dbl2 makeQuad(int x, double d, double o); 222 | Dbl2 area(Dbl2 p1, Dbl2 p2, int x); 223 | Dbl2 calculate(int pattern, int left, int right, double offset); 224 | }; 225 | 226 | /* Smoothing function for small u-patterns: */ 227 | Dbl2 AreaOrtho::smoothArea(double d, Dbl2 a1, Dbl2 a2) 228 | { 229 | Dbl2 b1 = (a1 * Dbl2(2.0)).apply(sqrt) * Dbl2(0.5); 230 | Dbl2 b2 = (a2 * Dbl2(2.0)).apply(sqrt) * Dbl2(0.5); 231 | double p = saturate(d / (double)SMOOTH_MAX_DISTANCE); 232 | return lerp(b1, a1, p) + lerp(b2, a2, p); 233 | } 234 | 235 | /* Smoothing u-patterns by quadratic function: */ 236 | Dbl2 AreaOrtho::makeQuad(int x, double d, double o) 237 | { 238 | double r = (double)x; 239 | 240 | /* fmin() below is a trick to smooth tiny u-patterns: */ 241 | return Dbl2(r, (1.0 - fmin(4.0, d) * r * (d - r) / (d * d)) * o); 242 | } 243 | 244 | /* Calculates the area under the line p1->p2, for the pixel x..x+1: */ 245 | Dbl2 AreaOrtho::area(Dbl2 p1, Dbl2 p2, int x) 246 | { 247 | Dbl2 d = p2 - p1; 248 | double x1 = (double)x; 249 | double x2 = x1 + 1.0; 250 | 251 | if ((x1 >= p1.x && x1 < p2.x) || (x2 > p1.x && x2 <= p2.x)) { /* inside? */ 252 | double y1 = p1.y + (x1 - p1.x) * d.y / d.x; 253 | double y2 = p1.y + (x2 - p1.x) * d.y / d.x; 254 | 255 | if ((copysign(1.0, y1) == copysign(1.0, y2) || 256 | fabs(y1) < 1e-4 || fabs(y2) < 1e-4)) { /* trapezoid? */ 257 | double a = (y1 + y2) / 2.0; 258 | if (a < 0.0) 259 | return Dbl2(fabs(a), 0.0); 260 | else 261 | return Dbl2(0.0, fabs(a)); 262 | } 263 | else { /* Then, we got two triangles: */ 264 | double x = p1.x - p1.y * d.x / d.y, xi; 265 | double a1 = x > p1.x ? y1 * modf(x, &xi) / 2.0 : 0.0; 266 | double a2 = x < p2.x ? y2 * (1.0 - modf(x, &xi)) / 2.0 : 0.0; 267 | double a = fabs(a1) > fabs(a2) ? a1 : -a2; 268 | if (a < 0.0) 269 | return Dbl2(fabs(a1), fabs(a2)); 270 | else 271 | return Dbl2(fabs(a2), fabs(a1)); 272 | } 273 | } 274 | else 275 | return Dbl2(0.0, 0.0); 276 | } 277 | 278 | /* Calculates the area for a given pattern and distances to the left and to the */ 279 | /* right, biased by an offset: */ 280 | Dbl2 AreaOrtho::calculate(int pattern, int left, int right, double offset) 281 | { 282 | Dbl2 a1, a2; 283 | 284 | /* 285 | * o1 | 286 | * .-------´ 287 | * o2 | 288 | * 289 | * <---d---> 290 | */ 291 | double d = (double)(left + right + 1); 292 | 293 | double o1 = 0.5 + offset; 294 | double o2 = 0.5 + offset - 1.0; 295 | 296 | switch (pattern) { 297 | case EDGESORTHO_NONE_NONE: 298 | { 299 | /* 300 | * 301 | * ------ 302 | * 303 | */ 304 | return Dbl2(0.0, 0.0); 305 | break; 306 | } 307 | case EDGESORTHO_POSI_NONE: 308 | { 309 | /* 310 | * 311 | * .------ 312 | * | 313 | * 314 | * We only offset L patterns in the crossing edge side, to make it 315 | * converge with the unfiltered pattern 0 (we don't want to filter the 316 | * pattern 0 to avoid artifacts). 317 | */ 318 | if (left <= right) 319 | return area(Dbl2(0.0, o2), Dbl2(d / 2.0, 0.0), left); 320 | else 321 | return Dbl2(0.0, 0.0); 322 | break; 323 | } 324 | case EDGESORTHO_NONE_POSI: 325 | { 326 | /* 327 | * 328 | * ------. 329 | * | 330 | */ 331 | if (left >= right) 332 | return area(Dbl2(d / 2.0, 0.0), Dbl2(d, o2), left); 333 | else 334 | return Dbl2(0.0, 0.0); 335 | break; 336 | } 337 | case EDGESORTHO_POSI_POSI: 338 | { 339 | /* 340 | * 341 | * .------. 342 | * | | 343 | */ 344 | if (m_orig_u) { 345 | a1 = area(Dbl2(0.0, o2), Dbl2(d / 2.0, 0.0), left); 346 | a2 = area(Dbl2(d / 2.0, 0.0), Dbl2(d, o2), left); 347 | return smoothArea(d, a1, a2); 348 | } 349 | else 350 | return area(makeQuad(left, d, o2), makeQuad(left + 1, d, o2), left); 351 | break; 352 | } 353 | case EDGESORTHO_NEGA_NONE: 354 | { 355 | /* 356 | * | 357 | * `------ 358 | * 359 | */ 360 | if (left <= right) 361 | return area(Dbl2(0.0, o1), Dbl2(d / 2.0, 0.0), left); 362 | else 363 | return Dbl2(0.0, 0.0); 364 | break; 365 | } 366 | case EDGESORTHO_BOTH_NONE: 367 | { 368 | /* 369 | * | 370 | * +------ 371 | * | 372 | */ 373 | return Dbl2(0.0, 0.0); 374 | break; 375 | } 376 | case EDGESORTHO_NEGA_POSI: 377 | { 378 | /* 379 | * | 380 | * `------. 381 | * | 382 | * 383 | * A problem of not offseting L patterns (see above), is that for certain 384 | * max search distances, the pixels in the center of a Z pattern will 385 | * detect the full Z pattern, while the pixels in the sides will detect a 386 | * L pattern. To avoid discontinuities, we blend the full offsetted Z 387 | * revectorization with partially offsetted L patterns. 388 | */ 389 | if (fabs(offset) > 0.0) { 390 | a1 = area(Dbl2(0.0, o1), Dbl2(d, o2), left); 391 | a2 = area(Dbl2(0.0, o1), Dbl2(d / 2.0, 0.0), left); 392 | a2 += area(Dbl2(d / 2.0, 0.0), Dbl2(d, o2), left); 393 | return (a1 + a2) / Dbl2(2.0); 394 | } 395 | else 396 | return area(Dbl2(0.0, o1), Dbl2(d, o2), left); 397 | break; 398 | } 399 | case EDGESORTHO_BOTH_POSI: 400 | { 401 | /* 402 | * | 403 | * +------. 404 | * | | 405 | */ 406 | return area(Dbl2(0.0, o1), Dbl2(d, o2), left); 407 | break; 408 | } 409 | case EDGESORTHO_NONE_NEGA: 410 | { 411 | /* 412 | * | 413 | * ------´ 414 | * 415 | */ 416 | if (left >= right) 417 | return area(Dbl2(d / 2.0, 0.0), Dbl2(d, o1), left); 418 | else 419 | return Dbl2(0.0, 0.0); 420 | break; 421 | } 422 | case EDGESORTHO_POSI_NEGA: 423 | { 424 | /* 425 | * | 426 | * .------´ 427 | * | 428 | */ 429 | if (fabs(offset) > 0.0) { 430 | a1 = area(Dbl2(0.0, o2), Dbl2(d, o1), left); 431 | a2 = area(Dbl2(0.0, o2), Dbl2(d / 2.0, 0.0), left); 432 | a2 += area(Dbl2(d / 2.0, 0.0), Dbl2(d, o1), left); 433 | return (a1 + a2) / Dbl2(2.0); 434 | } 435 | else 436 | return area(Dbl2(0.0, o2), Dbl2(d, o1), left); 437 | break; 438 | } 439 | case EDGESORTHO_NONE_BOTH: 440 | { 441 | /* 442 | * | 443 | * ------+ 444 | * | 445 | */ 446 | return Dbl2(0.0, 0.0); 447 | break; 448 | } 449 | case EDGESORTHO_POSI_BOTH: 450 | { 451 | /* 452 | * | 453 | * .------+ 454 | * | | 455 | */ 456 | return area(Dbl2(0.0, o2), Dbl2(d, o1), left); 457 | break; 458 | } 459 | case EDGESORTHO_NEGA_NEGA: 460 | { 461 | /* 462 | * | | 463 | * `------´ 464 | * 465 | */ 466 | if (m_orig_u) { 467 | a1 = area(Dbl2(0.0, o1), Dbl2(d / 2.0, 0.0), left); 468 | a2 = area(Dbl2(d / 2.0, 0.0), Dbl2(d, o1), left); 469 | return smoothArea(d, a1, a2); 470 | } 471 | else 472 | return area(makeQuad(left, d, o1), makeQuad(left + 1, d, o1), left); 473 | break; 474 | } 475 | case EDGESORTHO_BOTH_NEGA: 476 | { 477 | /* 478 | * | | 479 | * +------´ 480 | * | 481 | */ 482 | return area(Dbl2(0.0, o2), Dbl2(d, o1), left); 483 | break; 484 | } 485 | case EDGESORTHO_NEGA_BOTH: 486 | { 487 | /* 488 | * | | 489 | * `------+ 490 | * | 491 | */ 492 | return area(Dbl2(0.0, o1), Dbl2(d, o2), left); 493 | break; 494 | } 495 | case EDGESORTHO_BOTH_BOTH: 496 | { 497 | /* 498 | * | | 499 | * +------+ 500 | * | | 501 | */ 502 | return Dbl2(0.0, 0.0); 503 | break; 504 | } 505 | } 506 | 507 | return Dbl2(0.0, 0.0); 508 | } 509 | 510 | /*------------------------------------------------------------------------------*/ 511 | /* Diagonal Areas */ 512 | 513 | class AreaDiag { 514 | double m_data[SUBSAMPLES_DIAG][TEX_SIZE_DIAG][TEX_SIZE_DIAG][2]; 515 | bool m_numeric; 516 | bool m_orig_u; 517 | public: 518 | AreaDiag(bool numeric, bool orig_u) : m_numeric(numeric), m_orig_u(orig_u) {} 519 | 520 | double *getData() { return (double *)&m_data; } 521 | Dbl2 getPixel(int offset_index, Int2 coords) { 522 | return Dbl2(m_data[offset_index][coords.y][coords.x][0], 523 | m_data[offset_index][coords.y][coords.x][1]); 524 | } 525 | 526 | void areaTex(int offset_index); 527 | private: 528 | void putPixel(int offset_index, Int2 coords, Dbl2 pixel) { 529 | m_data[offset_index][coords.y][coords.x][0] = pixel.x; 530 | m_data[offset_index][coords.y][coords.x][1] = pixel.y; 531 | } 532 | 533 | double area1(Dbl2 p1, Dbl2 p2, Int2 p); 534 | Dbl2 area(Dbl2 p1, Dbl2 p2, int left); 535 | Dbl2 areaTriangle(Dbl2 p1L, Dbl2 p2L, Dbl2 p1R, Dbl2 p2R, int left); 536 | Dbl2 calculate(int pattern, int left, int right, Dbl2 offset); 537 | }; 538 | 539 | /* Calculates the area under the line p1->p2 for the pixel 'p' using brute */ 540 | /* force sampling: */ 541 | /* (quick and dirty solution, but it works) */ 542 | double AreaDiag::area1(Dbl2 p1, Dbl2 p2, Int2 p) 543 | { 544 | if (p1 == p2) 545 | return 1.0; 546 | 547 | double xm = (p1.x + p2.x) / 2.0, ym = (p1.y + p2.y) / 2.0; 548 | double a = p2.y - p1.y; 549 | double b = p1.x - p2.x; 550 | int count = 0; 551 | 552 | for (int ix = 0; ix < SAMPLES_DIAG; ix++) { 553 | double x = (double)p.x + (double)ix / (double)(SAMPLES_DIAG - 1); 554 | for (int iy = 0; iy < SAMPLES_DIAG; iy++) { 555 | double y = (double)p.y + (double)iy / (double)(SAMPLES_DIAG - 1); 556 | if (a * (x - xm) + b * (y - ym) > 0.0) /* inside? */ 557 | count++; 558 | } 559 | } 560 | return (double)count / (double)(SAMPLES_DIAG * SAMPLES_DIAG); 561 | } 562 | 563 | /* Calculates the area under the line p1->p2: */ 564 | /* (includes the pixel and its opposite) */ 565 | Dbl2 AreaDiag::area(Dbl2 p1, Dbl2 p2, int left) 566 | { 567 | if (m_numeric) { 568 | double a1 = area1(p1, p2, Int2(1, 0) + Int2(left)); 569 | double a2 = area1(p1, p2, Int2(1, 1) + Int2(left)); 570 | return Dbl2(1.0 - a1, a2); 571 | } 572 | 573 | /* Calculates the area under the line p1->p2 for the pixel 'p' analytically */ 574 | Dbl2 d = p2 - p1; 575 | if (d.x == 0.0) 576 | return Dbl2(0.0, 1.0); 577 | 578 | double x1 = (double)(1 + left); 579 | double x2 = x1 + 1.0; 580 | double ymid = x1; 581 | double xtop = p1.x + (ymid + 1.0 - p1.y) * d.x / d.y; 582 | double xmid = p1.x + (ymid - p1.y) * d.x / d.y; 583 | double xbot = p1.x + (ymid - 1.0 - p1.y) * d.x / d.y; 584 | 585 | double y1 = p1.y + (x1 - p1.x) * d.y / d.x; 586 | double y2 = p1.y + (x2 - p1.x) * d.y / d.x; 587 | double fy1 = y1 - floor(y1); 588 | double fy2 = y2 - floor(y2); 589 | int iy1 = (int)floor(y1 - ymid); 590 | int iy2 = (int)floor(y2 - ymid); 591 | 592 | if (iy1 <= -2) { 593 | if (iy2 == -1) 594 | return Dbl2(1.0 - (x2 - xbot) * fy2 * 0.5, 0.0); 595 | else if (iy2 == 0) 596 | return Dbl2((xmid + xbot) * 0.5 - x1, (x2 - xmid) * fy2 * 0.5); 597 | else if (iy2 >= 1) 598 | return Dbl2((xmid + xbot) * 0.5 - x1, x2 - (xtop + xmid) * 0.5); 599 | else /* iy2 < -1 */ 600 | return Dbl2(1.0, 0.0); 601 | } 602 | else if (iy1 == -1) { 603 | if (iy2 == -1) 604 | return Dbl2(1.0 - (fy1 + fy2) * 0.5, 0.0); 605 | else if (iy2 == 0) 606 | return Dbl2((xmid - x1) * (1.0 - fy1) * 0.5, (x2 - xmid) * fy2 * 0.5); 607 | else if (iy2 >= 1) 608 | return Dbl2((xmid - x1) * (1.0 - fy1) * 0.5, x2 - (xtop + xmid) * 0.5); 609 | else /* iy2 < -1 */ 610 | return Dbl2(1.0 - (xbot - x1) * fy1 * 0.5, 0.0); 611 | } 612 | else if (iy1 == 0) { 613 | if (iy2 == -1) 614 | return Dbl2((x2 - xmid) * (1.0 - fy2) * 0.5, (xmid - x1) * fy1 * 0.5); 615 | else if (iy2 == 0) 616 | return Dbl2(0.0, (fy1 + fy2) * 0.5); 617 | else if (iy2 >= 1) 618 | return Dbl2(0.0, 1.0 - (xtop - x1) * (1.0 - fy1) * 0.5); 619 | else /* iy2 < -1 */ 620 | return Dbl2(x2 - (xmid + xbot) * 0.5, (xmid - x1) * fy1 * 0.5); 621 | } 622 | else { /* iy1 > 0 */ 623 | if (iy2 == -1) 624 | return Dbl2((x2 - xtop) * (1.0 - fy2) * 0.5, (xtop + xmid) * 0.5 - x1); 625 | else if (iy2 == 0) 626 | return Dbl2(0.0, 1.0 - (x1 - xtop) * (1.0 - fy2) * 0.5); 627 | else if (iy2 >= 1) 628 | return Dbl2(0.0, 1.0); 629 | else /* iy2 < -1 */ 630 | return Dbl2(x2 - (xmid + xbot) * 0.5, (xtop + xmid) * 0.5 - x1); 631 | } 632 | } 633 | 634 | /* Calculate u-patterns using a triangle: */ 635 | Dbl2 AreaDiag::areaTriangle(Dbl2 p1L, Dbl2 p2L, Dbl2 p1R, Dbl2 p2R, int left) 636 | { 637 | double x1 = (double)(1 + left); 638 | double x2 = x1 + 1.0; 639 | 640 | Dbl2 dL = p2L - p1L; 641 | Dbl2 dR = p2R - p1R; 642 | 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); 643 | 644 | double y1 = (x1 < xm) ? p1L.y + (x1 - p1L.x) * dL.y / dL.x : p1R.y + (x1 - p1R.x) * dR.y / dR.x; 645 | double y2 = (x2 < xm) ? p1L.y + (x2 - p1L.x) * dL.y / dL.x : p1R.y + (x2 - p1R.x) * dR.y / dR.x; 646 | 647 | return area(Dbl2(x1, y1), Dbl2(x2, y2), left); 648 | } 649 | 650 | /* Calculates the area for a given pattern and distances to the left and to the */ 651 | /* right, biased by an offset: */ 652 | Dbl2 AreaDiag::calculate(int pattern, int left, int right, Dbl2 offset) 653 | { 654 | Dbl2 a1, a2; 655 | 656 | double d = (double)(left + right + 1); 657 | 658 | /* 659 | * There is some Black Magic around diagonal area calculations. Unlike 660 | * orthogonal patterns, the 'null' pattern (one without crossing edges) must be 661 | * filtered, and the ends of both the 'null' and L patterns are not known: L 662 | * and U patterns have different endings, and we don't know what is the 663 | * adjacent pattern. So, what we do is calculate a blend of both possibilites. 664 | */ 665 | switch (pattern) { 666 | case EDGESDIAG_NONE_NONE: 667 | { 668 | /* 669 | * 670 | * .-´ 671 | * .-´ 672 | * .-´ 673 | * .-´ 674 | * ´ 675 | * 676 | */ 677 | a1 = area(Dbl2(1.0, 1.0), Dbl2(1.0, 1.0) + Dbl2(d), left); /* 1st possibility */ 678 | a2 = area(Dbl2(1.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d), left); /* 2nd possibility */ 679 | return (a1 + a2) / Dbl2(2.0); /* Blend them */ 680 | break; 681 | } 682 | case EDGESDIAG_VERT_NONE: 683 | { 684 | /* 685 | * 686 | * .-´ 687 | * .-´ 688 | * .-´ 689 | * .-´ 690 | * | 691 | * | 692 | */ 693 | a1 = area(Dbl2(1.0, 0.0) + offset, Dbl2(0.0, 0.0) + Dbl2(d), left); 694 | a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d), left); 695 | return (a1 + a2) / Dbl2(2.0); 696 | break; 697 | } 698 | case EDGESDIAG_NONE_HORZ: 699 | { 700 | /* 701 | * 702 | * .---- 703 | * .-´ 704 | * .-´ 705 | * .-´ 706 | * ´ 707 | * 708 | */ 709 | a1 = area(Dbl2(0.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); 710 | a2 = area(Dbl2(1.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); 711 | return (a1 + a2) / Dbl2(2.0); 712 | break; 713 | } 714 | case EDGESDIAG_VERT_HORZ: 715 | { 716 | /* 717 | * 718 | * .---- 719 | * .-´ 720 | * .-´ 721 | * .-´ 722 | * | 723 | * | 724 | */ 725 | if (m_orig_u) 726 | return area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); 727 | else 728 | return areaTriangle(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d), 729 | Dbl2(0.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); 730 | break; 731 | } 732 | case EDGESDIAG_HORZ_NONE: 733 | { 734 | /* 735 | * 736 | * .-´ 737 | * .-´ 738 | * .-´ 739 | * ----´ 740 | * 741 | * 742 | */ 743 | a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(0.0, 0.0) + Dbl2(d), left); 744 | a2 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d), left); 745 | return (a1 + a2) / Dbl2(2.0); 746 | break; 747 | } 748 | case EDGESDIAG_BOTH_NONE: 749 | { 750 | /* 751 | * 752 | * .-´ 753 | * .-´ 754 | * .-´ 755 | * --.-´ 756 | * | 757 | * | 758 | */ 759 | a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(0.0, 0.0) + Dbl2(d), left); 760 | a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d), left); 761 | return (a1 + a2) / Dbl2(2.0); 762 | break; 763 | } 764 | case EDGESDIAG_HORZ_HORZ: 765 | { 766 | /* 767 | * 768 | * .---- 769 | * .-´ 770 | * .-´ 771 | * ----´ 772 | * 773 | * 774 | */ 775 | return area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); 776 | break; 777 | } 778 | case EDGESDIAG_BOTH_HORZ: 779 | { 780 | /* 781 | * 782 | * .---- 783 | * .-´ 784 | * .-´ 785 | * --.-´ 786 | * | 787 | * | 788 | */ 789 | a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); 790 | a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); 791 | return (a1 + a2) / Dbl2(2.0); 792 | break; 793 | } 794 | case EDGESDIAG_NONE_VERT: 795 | { 796 | /* 797 | * | 798 | * | 799 | * .-´ 800 | * .-´ 801 | * .-´ 802 | * ´ 803 | * 804 | */ 805 | a1 = area(Dbl2(0.0, 0.0), Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); 806 | a2 = area(Dbl2(1.0, 0.0), Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); 807 | return (a1 + a2) / Dbl2(2.0); 808 | break; 809 | } 810 | case EDGESDIAG_VERT_VERT: 811 | { 812 | /* 813 | * | 814 | * | 815 | * .-´ 816 | * .-´ 817 | * .-´ 818 | * | 819 | * | 820 | */ 821 | return area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); 822 | break; 823 | } 824 | case EDGESDIAG_NONE_BOTH: 825 | { 826 | /* 827 | * | 828 | * .---- 829 | * .-´ 830 | * .-´ 831 | * .-´ 832 | * ´ 833 | * 834 | */ 835 | a1 = area(Dbl2(0.0, 0.0), Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); 836 | a2 = area(Dbl2(1.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); 837 | return (a1 + a2) / Dbl2(2.0); 838 | break; 839 | } 840 | case EDGESDIAG_VERT_BOTH: 841 | { 842 | /* 843 | * | 844 | * .---- 845 | * .-´ 846 | * .-´ 847 | * .-´ 848 | * | 849 | * | 850 | */ 851 | a1 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); 852 | a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); 853 | return (a1 + a2) / Dbl2(2.0); 854 | break; 855 | } 856 | case EDGESDIAG_HORZ_VERT: 857 | { 858 | /* 859 | * | 860 | * | 861 | * .-´ 862 | * .-´ 863 | * ----´ 864 | * 865 | * 866 | */ 867 | if (m_orig_u) 868 | return area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); 869 | else 870 | return areaTriangle(Dbl2(1.0, 1.0) + offset, Dbl2(2.0, 1.0) + Dbl2(d), 871 | Dbl2(1.0, 0.0), Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); 872 | break; 873 | } 874 | case EDGESDIAG_BOTH_VERT: 875 | { 876 | /* 877 | * | 878 | * | 879 | * .-´ 880 | * .-´ 881 | * --.-´ 882 | * | 883 | * | 884 | */ 885 | a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); 886 | a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); 887 | return (a1 + a2) / Dbl2(2.0); 888 | break; 889 | } 890 | case EDGESDIAG_HORZ_BOTH: 891 | { 892 | /* 893 | * | 894 | * .---- 895 | * .-´ 896 | * .-´ 897 | * ----´ 898 | * 899 | * 900 | */ 901 | a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); 902 | a2 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); 903 | return (a1 + a2) / Dbl2(2.0); 904 | break; 905 | } 906 | case EDGESDIAG_BOTH_BOTH: 907 | { 908 | /* 909 | * | 910 | * .---- 911 | * .-´ 912 | * .-´ 913 | * --.-´ 914 | * | 915 | * | 916 | */ 917 | a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left); 918 | a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left); 919 | return (a1 + a2) / Dbl2(2.0); 920 | break; 921 | } 922 | } 923 | 924 | return Dbl2(0.0, 0.0); 925 | } 926 | 927 | /*------------------------------------------------------------------------------*/ 928 | /* Main Loops */ 929 | 930 | void AreaOrtho::areaTex(int offset_index) 931 | { 932 | double offset = subsample_offsets_ortho[offset_index]; 933 | int max_dist = m_compat ? MAX_DIST_ORTHO_COMPAT : MAX_DIST_ORTHO; 934 | 935 | for (int pattern = 0; pattern < 16; pattern++) { 936 | Int2 e = Int2(max_dist) * (m_compat ? edgesortho_compat : edgesortho)[pattern]; 937 | for (int left = 0; left < max_dist; left++) { 938 | for (int right = 0; right < max_dist; right++) { 939 | Dbl2 p = calculate(pattern, left * left, right * right, offset); 940 | Int2 coords = e + Int2(left, right); 941 | 942 | putPixel(offset_index, coords, p); 943 | } 944 | } 945 | } 946 | return; 947 | } 948 | 949 | void AreaDiag::areaTex(int offset_index) 950 | { 951 | Dbl2 offset = subsample_offsets_diag[offset_index]; 952 | 953 | for (int pattern = 0; pattern < 16; pattern++) { 954 | Int2 e = Int2(MAX_DIST_DIAG) * edgesdiag[pattern]; 955 | for (int left = 0; left < MAX_DIST_DIAG; left++) { 956 | for (int right = 0; right < MAX_DIST_DIAG; right++) { 957 | Dbl2 p = calculate(pattern, left, right, offset); 958 | Int2 coords = e + Int2(left, right); 959 | 960 | putPixel(offset_index, coords, p); 961 | } 962 | } 963 | } 964 | return; 965 | } 966 | 967 | /*------------------------------------------------------------------------------*/ 968 | /* Write File to Specified Location on Disk */ 969 | 970 | /* C/C++ source code (arrays of floats) */ 971 | static void write_double_array(FILE *fp, const double *ptr, int length, const char *array_name, bool quantize, bool hex) 972 | { 973 | fprintf(fp, "static const %s %s[%d] = {", hex ? "unsigned char" : "float", array_name, length); 974 | 975 | for (int n = 0; n < length; n++) { 976 | if (n > 0) 977 | fprintf(fp, ","); 978 | 979 | if (hex) { 980 | fprintf(fp, (n % 12 != 0) ? " " : "\n\t"); 981 | fprintf(fp, "0x%02x", (int)(*(ptr++) * 255.0)); 982 | } 983 | else { 984 | fprintf(fp, (n % 8 != 0) ? " " : "\n\t"); 985 | 986 | if (quantize) 987 | fprintf(fp, "%3d / 255.0", (int)(*(ptr++) * 255.0)); 988 | else 989 | fprintf(fp, "%1.8lf", *(ptr++)); 990 | } 991 | } 992 | 993 | fprintf(fp, "\n};\n"); 994 | } 995 | 996 | static void write_csource(AreaOrtho *ortho, AreaDiag *diag, FILE *fp, bool subsamp, bool quantize, bool hex) 997 | { 998 | fprintf(fp, "/* This file was generated by smaa_areatex.cpp */\n"); 999 | 1000 | fprintf(fp, "\n/* Horizontal/Vertical Areas */\n"); 1001 | write_double_array(fp, ortho->getData(), 1002 | TEX_SIZE_ORTHO * TEX_SIZE_ORTHO * 2 * (subsamp ? SUBSAMPLES_ORTHO : 1), 1003 | "areatex", quantize, hex); 1004 | 1005 | fprintf(fp, "\n/* Diagonal Areas */\n"); 1006 | write_double_array(fp, diag->getData(), 1007 | TEX_SIZE_DIAG * TEX_SIZE_DIAG * 2 * (subsamp ? SUBSAMPLES_DIAG : 1), 1008 | "areatex_diag", quantize, hex); 1009 | } 1010 | 1011 | static int write_byte(int c, FILE *fp, bool hex) 1012 | { 1013 | static int n = 0; 1014 | 1015 | if (hex) { 1016 | if (n > 0) 1017 | fprintf(fp, ","); 1018 | fprintf(fp, (n++ % 12 != 0) ? " " : "\n\t"); 1019 | return fprintf(fp, "0x%02x", c); 1020 | } 1021 | else 1022 | return fputc(c, fp); 1023 | } 1024 | 1025 | /* .tga File (RGBA 32bit uncompressed) */ 1026 | static void write_tga(AreaOrtho *ortho, AreaDiag *diag, FILE *fp, bool subsamp, bool hex) 1027 | { 1028 | int subsamples = subsamp ? SUBSAMPLES_ORTHO : 1; 1029 | 1030 | if (hex) { 1031 | fprintf(fp, "/* This file was generated by smaa_areatex.cpp */\n\n"); 1032 | fprintf(fp, "static const unsigned char areatex_tga[%d] = {", 1033 | subsamples * TEX_SIZE_ORTHO * (TEX_SIZE_ORTHO + TEX_SIZE_DIAG) * 4); 1034 | } 1035 | 1036 | unsigned char header[18] = {0, 0, 1037 | 2, /* uncompressed RGB */ 1038 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1039 | 32, /* 32bit */ 1040 | 8}; /* 8bit alpha, left to right, bottom to top */ 1041 | 1042 | /* Set width and height */ 1043 | header[12] = (TEX_SIZE_ORTHO + TEX_SIZE_DIAG) & 0xff; 1044 | header[13] = ((TEX_SIZE_ORTHO + TEX_SIZE_DIAG) >> 8) & 0xff; 1045 | header[14] = (subsamples * TEX_SIZE_ORTHO) & 0xff; 1046 | header[15] = ((subsamples * TEX_SIZE_ORTHO) >> 8) & 0xff; 1047 | 1048 | /* Write .tga header */ 1049 | for (int i = 0; i < sizeof(header); i++) write_byte(header[i], fp, hex); 1050 | 1051 | /* Write pixel data */ 1052 | for (int i = subsamples - 1; i >= 0; i--) { 1053 | for (int y = TEX_SIZE_ORTHO - 1; y >= 0; y--) { 1054 | for (int x = 0; x < TEX_SIZE_ORTHO; x++) { 1055 | Dbl2 p = ortho->getPixel(i, Int2(x, y)); 1056 | write_byte(0, fp, hex); /* B */ 1057 | write_byte((int)(p.y * 255.0), fp, hex); /* G */ 1058 | write_byte((int)(p.x * 255.0), fp, hex); /* R */ 1059 | write_byte(0, fp, hex); /* A */ 1060 | } 1061 | 1062 | for (int x = 0; x < TEX_SIZE_DIAG; x++) { 1063 | if (i < SUBSAMPLES_DIAG) { 1064 | Dbl2 p = diag->getPixel(i, Int2(x, y)); 1065 | write_byte(0, fp, hex); /* B */ 1066 | write_byte((int)(p.y * 255.0), fp, hex); /* G */ 1067 | write_byte((int)(p.x * 255.0), fp, hex); /* R */ 1068 | write_byte(0, fp, hex); /* A */ 1069 | } 1070 | else { 1071 | write_byte(0, fp, hex); 1072 | write_byte(0, fp, hex); 1073 | write_byte(0, fp, hex); 1074 | write_byte(0, fp, hex); 1075 | } 1076 | } 1077 | } 1078 | } 1079 | 1080 | if (hex) 1081 | fprintf(fp, "\n};\n"); 1082 | } 1083 | 1084 | /* .raw File (R8G8 raw data) */ 1085 | static void write_raw(AreaOrtho *ortho, AreaDiag *diag, FILE *fp, bool subsamp, bool hex) 1086 | { 1087 | int subsamples = subsamp ? SUBSAMPLES_ORTHO : 1; 1088 | 1089 | if (hex) { 1090 | fprintf(fp, "/* This file was generated by smaa_areatex.cpp */\n\n"); 1091 | fprintf(fp, "static const unsigned char areatex_r8g8[%d] = {", 1092 | subsamples * TEX_SIZE_ORTHO * (TEX_SIZE_ORTHO + TEX_SIZE_DIAG) * 2); 1093 | } 1094 | 1095 | /* Write pixel data */ 1096 | for (int i = 0; i < subsamples; i++) { 1097 | for (int y = 0; y < TEX_SIZE_ORTHO; y++) { 1098 | for (int x = 0; x < TEX_SIZE_ORTHO; x++) { 1099 | Dbl2 p = ortho->getPixel(i, Int2(x, y)); 1100 | write_byte((int)(p.x * 255.0), fp, hex); /* R */ 1101 | write_byte((int)(p.y * 255.0), fp, hex); /* G */ 1102 | } 1103 | 1104 | for (int x = 0; x < TEX_SIZE_DIAG; x++) { 1105 | if (i < SUBSAMPLES_DIAG) { 1106 | Dbl2 p = diag->getPixel(i, Int2(x, y)); 1107 | write_byte((int)(p.x * 255.0), fp, hex); /* R */ 1108 | write_byte((int)(p.y * 255.0), fp, hex); /* G */ 1109 | } 1110 | else { 1111 | write_byte(0, fp, hex); 1112 | write_byte(0, fp, hex); 1113 | } 1114 | } 1115 | } 1116 | } 1117 | 1118 | if (hex) 1119 | fprintf(fp, "\n};\n"); 1120 | } 1121 | 1122 | static void write_file(AreaOrtho *ortho, AreaDiag *diag, FILE *fp, bool subsamp, bool quantize, bool tga, bool raw, bool hex) 1123 | { 1124 | if (tga) 1125 | write_tga(ortho, diag, fp, subsamp, hex); 1126 | else if (raw) 1127 | write_raw(ortho, diag, fp, subsamp, hex); 1128 | else 1129 | write_csource(ortho, diag, fp, subsamp, quantize, hex); 1130 | } 1131 | 1132 | int main(int argc, char **argv) 1133 | { 1134 | bool subsamp = false; 1135 | bool quantize = false; 1136 | bool tga = false; 1137 | bool raw = false; 1138 | bool hex = false; 1139 | bool compat = false; 1140 | bool numeric = false; 1141 | bool orig_u = false; 1142 | bool help = false; 1143 | char *outfile = NULL; 1144 | int status = 0; 1145 | 1146 | for (int i = 1; i < argc; i++) { 1147 | char *ptr = argv[i]; 1148 | if (*ptr++ == '-' && *ptr != '\0') { 1149 | char c; 1150 | while ((c = *ptr++) != '\0') { 1151 | if (c == 's') 1152 | subsamp = true; 1153 | else if (c == 'q') 1154 | quantize = true; 1155 | else if (c == 't') 1156 | tga = true; 1157 | else if (c == 'r') 1158 | raw = true; 1159 | else if (c == 'x') 1160 | hex = true; 1161 | else if (c == 'c') 1162 | compat = true; 1163 | else if (c == 'n') 1164 | numeric = true; 1165 | else if (c == 'u') 1166 | orig_u = true; 1167 | else if (c == 'h') 1168 | help = true; 1169 | else { 1170 | fprintf(stderr, "Unknown option: -%c\n", c); 1171 | status = 1; 1172 | break; 1173 | } 1174 | } 1175 | } 1176 | else if (outfile) { 1177 | fprintf(stderr, "Too much file names: %s, %s\n", outfile, argv[i]); 1178 | status = 1; 1179 | } 1180 | else 1181 | outfile = argv[i]; 1182 | 1183 | if (status != 0) 1184 | break; 1185 | } 1186 | 1187 | if (status == 0 && !help && !outfile) { 1188 | fprintf(stderr, "File name was not specified.\n"); 1189 | status = 1; 1190 | } 1191 | 1192 | if (status != 0 || help) { 1193 | fprintf(stderr, "Usage: %s [OPTION]... OUTFILE\n", argv[0]); 1194 | fprintf(stderr, "Options:\n"); 1195 | fprintf(stderr, " -s Calculate data for subpixel rendering\n"); 1196 | fprintf(stderr, " -q Quantize data to 256 levels\n"); 1197 | fprintf(stderr, " -t Write TGA image\n"); 1198 | fprintf(stderr, " -r Write R8G8 raw image\n"); 1199 | fprintf(stderr, " -x Write C/C++ source containing array(s) of unsigned char\n"); 1200 | fprintf(stderr, " -c Generate compatible orthogonal data that subtexture size is 16\n"); 1201 | fprintf(stderr, " -n Numerically calculate diagonal data using brute force sampling\n"); 1202 | fprintf(stderr, " -u Process orthogonal / diagonal U patterns in older ways\n"); 1203 | fprintf(stderr, " -h Print this help and exit\n"); 1204 | fprintf(stderr, "File name OUTFILE usually should have an extension such as .c, .h, or .tga,\n"); 1205 | fprintf(stderr, "except for a special name '-' that means standard output.\n\n"); 1206 | fprintf(stderr, "Example:\n"); 1207 | fprintf(stderr, " Generate TGA file exactly same as AreaTexDX10.tga bundled with the\n"); 1208 | fprintf(stderr, " original implementation:\n\n"); 1209 | fprintf(stderr, " $ smaa_areatex -stcnu AreaTexDX10.tga\n\n"); 1210 | return status; 1211 | } 1212 | 1213 | AreaOrtho *ortho = new AreaOrtho(compat, orig_u); 1214 | AreaDiag *diag = new AreaDiag(numeric, orig_u); 1215 | 1216 | /* Calculate areatex data */ 1217 | for (int i = 0; i < (subsamp ? SUBSAMPLES_ORTHO : 1); i++) 1218 | ortho->areaTex(i); 1219 | 1220 | for (int i = 0; i < (subsamp ? SUBSAMPLES_DIAG : 1); i++) 1221 | diag->areaTex(i); 1222 | 1223 | /* Generate .tga, .raw, or C/C++ source file, or write the data to stdout */ 1224 | if (strcmp(outfile, "-") != 0) { 1225 | FILE *fp = fopen(outfile, ((tga || raw) && !hex) ? "wb" : "w"); 1226 | 1227 | if (!fp) { 1228 | fprintf(stderr, "Unable to open file: %s\n", outfile); 1229 | status = 1; 1230 | } 1231 | else { 1232 | fprintf(stderr, "Generating %s\n", outfile); 1233 | write_file(ortho, diag, fp, subsamp, quantize, tga, raw, hex); 1234 | fclose(fp); 1235 | } 1236 | } 1237 | else 1238 | write_file(ortho, diag, stdout, subsamp, quantize, tga, raw, hex); 1239 | 1240 | delete ortho; 1241 | delete diag; 1242 | 1243 | return status; 1244 | } 1245 | 1246 | /* smaa_areatex.cpp ends here */ 1247 | -------------------------------------------------------------------------------- /bin/smaa_png.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2011 Guillaume Cottenceau and contributors. 3 | * 2014-2017 IRIE Shinsuke 4 | * 5 | * This software may be freely redistributed under the terms 6 | * of the X11 license. 7 | * 8 | */ 9 | 10 | /* smaa_png.cpp */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define PNG_DEBUG 3 19 | #include 20 | #include 21 | 22 | #include "smaa.h" 23 | 24 | static const float FLOAT_VAL_NOT_SPECIFIED = -1.0f; 25 | static const int INT_VAL_NOT_SPECIFIED = -2; 26 | static const int END_OF_LIST = -1; 27 | 28 | enum edge_detection { 29 | ED_LUMA, 30 | ED_COLOR, 31 | ED_DEPTH, 32 | }; 33 | 34 | static void abort_(const char * s, ...) 35 | { 36 | va_list args; 37 | va_start(args, s); 38 | vfprintf(stderr, s, args); 39 | fprintf(stderr, "\n"); 40 | va_end(args); 41 | abort(); 42 | } 43 | 44 | static int width, height, rowbytes; 45 | static png_byte color_type; 46 | static png_byte bit_depth; 47 | static bool has_alpha; 48 | 49 | static png_structp png_ptr; 50 | static png_infop info_ptr; 51 | static int number_of_passes; 52 | static png_bytep *row_pointers; 53 | 54 | 55 | struct item { 56 | int id; 57 | char name[12]; 58 | }; 59 | 60 | static const struct item color_types[6] = { 61 | {PNG_COLOR_TYPE_GRAY, "GRAY"}, 62 | {PNG_COLOR_TYPE_GRAY_ALPHA, "GRAY_ALPHA"}, 63 | {PNG_COLOR_TYPE_PALETTE, "PALETTE"}, 64 | {PNG_COLOR_TYPE_RGB, "RGB"}, 65 | {PNG_COLOR_TYPE_RGB_ALPHA, "RGB_ALPHA"}, 66 | {END_OF_LIST, ""} 67 | }; 68 | 69 | static const struct item edge_detection_types[4] = { 70 | {ED_LUMA, "luma"}, 71 | {ED_COLOR, "color"}, 72 | {ED_DEPTH, "depth"}, 73 | {END_OF_LIST, ""} 74 | }; 75 | 76 | static const struct item config_presets[6] = { 77 | {SMAA::CONFIG_PRESET_LOW, "low"}, 78 | {SMAA::CONFIG_PRESET_MEDIUM, "medium"}, 79 | {SMAA::CONFIG_PRESET_HIGH, "high"}, 80 | {SMAA::CONFIG_PRESET_ULTRA, "ultra"}, 81 | {SMAA::CONFIG_PRESET_EXTREME, "extreme"}, 82 | {END_OF_LIST, ""} 83 | }; 84 | 85 | static const char *assoc(int key, const struct item *list) 86 | { 87 | int i = 0; 88 | while(list[i].id != END_OF_LIST) { 89 | if (key == list[i].id) 90 | return list[i].name; 91 | i++; 92 | } 93 | return NULL; 94 | } 95 | 96 | static int rassoc(const char *key, const struct item *list) 97 | { 98 | int i = 0; 99 | while(list[i].id != END_OF_LIST) { 100 | if (strcmp(key, list[i].name) == 0) 101 | return list[i].id; 102 | i++; 103 | } 104 | return END_OF_LIST; 105 | } 106 | 107 | static int check_png_filename(const char *file_name) 108 | { 109 | const char *extension = strrchr(file_name, '.'); 110 | 111 | if (!extension) 112 | fprintf(stderr, "File name has no extension: %s\n", file_name); 113 | else if (strcmp(extension, ".png") != 0 && strcmp(extension, ".PNG") != 0) 114 | fprintf(stderr, "File extension is not \".png\": %s\n", file_name); 115 | else 116 | return 0; 117 | 118 | return 1; 119 | } 120 | 121 | static void print_png_info(const char *file_name, const char *inout_label) 122 | { 123 | const char *type_name; 124 | 125 | fprintf(stderr, "%s file: %s\n", inout_label, file_name); 126 | fprintf(stderr, " width x height: %d x %d\n", width, height); 127 | fprintf(stderr, " color type: %s\n", assoc(color_type, color_types)); 128 | fprintf(stderr, " alpha channel or tRNS chanks: %s\n", has_alpha ? "yes" : "no"); 129 | fprintf(stderr, " bit depth: %d%s\n", bit_depth, (bit_depth < 8) ? " (expanded to 8bit)" : ""); 130 | } 131 | 132 | static void read_png_file(const char *file_name, bool print_info) 133 | { 134 | unsigned char header[8]; // 8 is the maximum size that can be checked 135 | 136 | /* open file and test for it being a png */ 137 | FILE *fp = fopen(file_name, "rb"); 138 | if (!fp) 139 | abort_("[read_png_file] File %s could not be opened for reading", file_name); 140 | fread(header, 1, 8, fp); 141 | if (png_sig_cmp(header, 0, 8)) 142 | abort_("[read_png_file] File %s is not recognized as a PNG file", file_name); 143 | 144 | 145 | /* initialize stuff */ 146 | png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 147 | 148 | if (!png_ptr) 149 | abort_("[read_png_file] png_create_read_struct failed"); 150 | 151 | info_ptr = png_create_info_struct(png_ptr); 152 | if (!info_ptr) 153 | abort_("[read_png_file] png_create_info_struct failed"); 154 | 155 | if (setjmp(png_jmpbuf(png_ptr))) 156 | abort_("[read_png_file] Error during init_io"); 157 | 158 | png_init_io(png_ptr, fp); 159 | png_set_sig_bytes(png_ptr, 8); 160 | 161 | png_read_info(png_ptr, info_ptr); 162 | 163 | width = png_get_image_width(png_ptr, info_ptr); 164 | height = png_get_image_height(png_ptr, info_ptr); 165 | color_type = png_get_color_type(png_ptr, info_ptr); 166 | bit_depth = png_get_bit_depth(png_ptr, info_ptr); 167 | 168 | /* is there transparency data? */ 169 | if (color_type == PNG_COLOR_TYPE_RGBA || color_type == PNG_COLOR_TYPE_GA) 170 | has_alpha = true; 171 | else { 172 | png_bytep trans = NULL; 173 | int num_trans = 0; 174 | png_color_16p trans_values = NULL; 175 | 176 | png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); 177 | has_alpha = (trans != NULL && num_trans > 0 || trans_values != NULL); 178 | } 179 | 180 | /* print information of input image */ 181 | if (print_info) 182 | print_png_info(file_name, "input"); 183 | 184 | /* Expand any grayscale or palette images to RGB */ 185 | png_set_expand(png_ptr); 186 | 187 | number_of_passes = png_set_interlace_handling(png_ptr); 188 | png_read_update_info(png_ptr, info_ptr); 189 | 190 | color_type = has_alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB; 191 | bit_depth = (bit_depth < 8) ? 8 : bit_depth; 192 | 193 | /* read file */ 194 | if (setjmp(png_jmpbuf(png_ptr))) 195 | abort_("[read_png_file] Error during read_image"); 196 | 197 | row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height); 198 | 199 | if (bit_depth == 16) 200 | rowbytes = width * (has_alpha ? 8 : 6); 201 | else 202 | rowbytes = width * (has_alpha ? 4 : 3); 203 | 204 | for (int y=0; y> 8); 280 | *(*ptr)++ = (png_byte)(c & 0xff); 281 | } 282 | 283 | static void process_file(int preset, int detection_type, float threshold, float adaptation, 284 | int ortho_steps, int diag_steps, int rounding, bool print_info) 285 | { 286 | using namespace SMAA; 287 | using namespace std::chrono; 288 | 289 | Image *orignImage, *edgesImage, *blendImage, *finalImage, *depthImage; 290 | float color[4], edges[4], weights[4], depth[4] = {0.0f, 0.0f, 0.0f, 0.0f}; 291 | const char *type_name; 292 | steady_clock::time_point begin, end; 293 | 294 | /* setup SMAA pixel shader */ 295 | PixelShader ps(preset); 296 | if (threshold != FLOAT_VAL_NOT_SPECIFIED) 297 | ps.setThreshold(threshold); 298 | if (adaptation != FLOAT_VAL_NOT_SPECIFIED) 299 | ps.setLocalContrastAdaptationFactor(adaptation); 300 | if (ortho_steps != INT_VAL_NOT_SPECIFIED) 301 | ps.setMaxSearchSteps(ortho_steps); 302 | if (diag_steps != INT_VAL_NOT_SPECIFIED) { 303 | if (diag_steps != -1) { 304 | ps.setEnableDiagDetection(true); 305 | ps.setMaxSearchStepsDiag(diag_steps); 306 | } 307 | else 308 | ps.setEnableDiagDetection(false); 309 | } 310 | if (rounding != INT_VAL_NOT_SPECIFIED) { 311 | if (rounding != -1) { 312 | ps.setEnableCornerDetection(true); 313 | ps.setCornerRounding(rounding); 314 | } 315 | else 316 | ps.setEnableCornerDetection(false); 317 | } 318 | 319 | if (print_info) { 320 | fprintf(stderr, "\n"); 321 | fprintf(stderr, "edge detection type: %s\n", assoc(detection_type, edge_detection_types)); 322 | fprintf(stderr, " threshold: %f\n", 323 | (detection_type != ED_DEPTH) ? ps.getThreshold() : ps.getDepthThreshold()); 324 | fprintf(stderr, " predicated thresholding: off (not supported)\n"); 325 | fprintf(stderr, " local contrast adaptation factor: %f\n", ps.getLocalContrastAdaptationFactor()); 326 | fprintf(stderr, "\n"); 327 | fprintf(stderr, "maximum search steps: %d\n", ps.getMaxSearchSteps()); 328 | fprintf(stderr, "diagonal search: %s\n", ps.getEnableDiagDetection() ? "on" : "off"); 329 | if (ps.getEnableDiagDetection()) 330 | fprintf(stderr, " maximum diagonal search steps: %d\n", ps.getMaxSearchStepsDiag()); 331 | fprintf(stderr, "corner processing: %s\n", ps.getEnableCornerDetection() ? "on" : "off"); 332 | if (ps.getEnableCornerDetection()) 333 | fprintf(stderr, " corner rounding: %d\n", ps.getCornerRounding()); 334 | fprintf(stderr, "\n"); 335 | } 336 | 337 | /* prepare image buffers */ 338 | try { 339 | orignImage = new Image(width, height); 340 | edgesImage = new Image(width, height); 341 | blendImage = new Image(width, height); 342 | finalImage = new Image(width, height); 343 | if (detection_type == ED_DEPTH) 344 | depthImage = new Image(width, height); 345 | } 346 | catch (ERROR_TYPE e) { abort_("Memory allocation failed"); } 347 | 348 | /* read from png buffer */ 349 | for (int y = 0; y < height; y++) { 350 | png_byte* ptr = row_pointers[y]; 351 | for (int x = 0; x < width; x++) { 352 | if (bit_depth == 16) { 353 | color[0] = (float)((*ptr++ << 8) + *ptr++) / 65535.0f; 354 | color[1] = (float)((*ptr++ << 8) + *ptr++) / 65535.0f; 355 | color[2] = (float)((*ptr++ << 8) + *ptr++) / 65535.0f; 356 | color[3] = has_alpha ? (float)((*ptr++ << 8) + *ptr++) / 65535.0f : 1.0f; 357 | } 358 | else { 359 | color[0] = (float)*ptr++ / 255.0f; 360 | color[1] = (float)*ptr++ / 255.0f; 361 | color[2] = (float)*ptr++ / 255.0f; 362 | color[3] = has_alpha ? (float)*ptr++ / 255.0f : 1.0f; 363 | } 364 | 365 | if (detection_type == ED_DEPTH) { 366 | depth[0] = color[3]; 367 | depthImage->putPixel(x, y, depth); 368 | color[3] = 1.0f; 369 | } 370 | 371 | orignImage->putPixel(x, y, color); 372 | } 373 | } 374 | 375 | if (detection_type == ED_DEPTH) { 376 | /* alpha channel was consumed as depth */ 377 | color_type = PNG_COLOR_TYPE_RGB; 378 | has_alpha = false; 379 | } 380 | 381 | /* record starting time to calculate elapsed time */ 382 | if (print_info) 383 | begin = steady_clock::now(); 384 | 385 | /* do anti-aliasing (3 passes) */ 386 | /* 1. edge detection */ 387 | switch (detection_type) { 388 | case ED_LUMA: 389 | for (int y = 0; y < height; y++) { 390 | for (int x = 0; x < width; x++) { 391 | ps.lumaEdgeDetection(x, y, orignImage, NULL, edges); 392 | edgesImage->putPixel(x, y, edges); 393 | } 394 | } 395 | break; 396 | case ED_COLOR: 397 | for (int y = 0; y < height; y++) { 398 | for (int x = 0; x < width; x++) { 399 | ps.colorEdgeDetection(x, y, orignImage, NULL, edges); 400 | edgesImage->putPixel(x, y, edges); 401 | } 402 | } 403 | break; 404 | case ED_DEPTH: 405 | for (int y = 0; y < height; y++) { 406 | for (int x = 0; x < width; x++) { 407 | ps.depthEdgeDetection(x, y, depthImage, edges); 408 | edgesImage->putPixel(x, y, edges); 409 | } 410 | } 411 | break; 412 | } 413 | 414 | /* 2. calculate blending weights */ 415 | for (int y = 0; y < height; y++) { 416 | for (int x = 0; x < width; x++) { 417 | ps.blendingWeightCalculation(x, y, edgesImage, NULL, weights); 418 | blendImage->putPixel(x, y, weights); 419 | } 420 | } 421 | 422 | /* 3. blend color with neighboring pixels */ 423 | for (int y = 0; y < height; y++) { 424 | for (int x = 0; x < width; x++) { 425 | ps.neighborhoodBlending(x, y, orignImage, blendImage, NULL, color); 426 | finalImage->putPixel(x, y, color); 427 | } 428 | } 429 | 430 | /* print elapsed time */ 431 | if (print_info) { 432 | end = steady_clock::now(); 433 | long int elapsed_time = duration_cast(end - begin).count(); 434 | fprintf(stderr, "elapsed time: %ld ms\n\n", elapsed_time); 435 | } 436 | 437 | /* write back to png buffer */ 438 | for (int y = 0; y < height; y++) { 439 | png_byte* ptr = row_pointers[y]; 440 | for (int x = 0; x < width; x++) { 441 | //orignImage->getPixel(x, y, color); 442 | //edgesImage->getPixel(x, y, color); 443 | //blendImage->getPixel(x, y, color); 444 | finalImage->getPixel(x, y, color); 445 | if (bit_depth == 16) { 446 | write_pixel16(&ptr, color[0]); 447 | write_pixel16(&ptr, color[1]); 448 | write_pixel16(&ptr, color[2]); 449 | if (has_alpha) 450 | write_pixel16(&ptr, color[3]); 451 | } 452 | else { 453 | *ptr++ = (png_byte)roundf(color[0] * 255.0f); 454 | *ptr++ = (png_byte)roundf(color[1] * 255.0f); 455 | *ptr++ = (png_byte)roundf(color[2] * 255.0f); 456 | if (has_alpha) 457 | *ptr++ = (png_byte)roundf(color[3] * 255.0f); 458 | } 459 | } 460 | } 461 | 462 | /* delete image buffers */ 463 | delete orignImage; 464 | delete edgesImage; 465 | delete blendImage; 466 | delete finalImage; 467 | if (detection_type == ED_DEPTH) 468 | delete depthImage; 469 | } 470 | 471 | int main(int argc, char **argv) 472 | { 473 | int preset = SMAA::CONFIG_PRESET_EXTREME; 474 | int detection = ED_COLOR; 475 | float threshold = FLOAT_VAL_NOT_SPECIFIED; 476 | float adaptation = FLOAT_VAL_NOT_SPECIFIED; 477 | int ortho_steps = INT_VAL_NOT_SPECIFIED; 478 | int diag_steps = INT_VAL_NOT_SPECIFIED; 479 | int rounding = INT_VAL_NOT_SPECIFIED; 480 | bool verbose = false; 481 | bool help = false; 482 | char *infile = NULL; 483 | char *outfile = NULL; 484 | int status = 0; 485 | 486 | for (int i = 1; i < argc; i++) { 487 | char *ptr = argv[i]; 488 | if (*ptr++ == '-' && *ptr != '\0') { 489 | char c, *optarg, *endptr; 490 | while ((c = *ptr++) != '\0') { 491 | if (strchr("petasdc", c)) { 492 | if (*ptr != '\0') 493 | optarg = ptr; 494 | else if (++i < argc) 495 | optarg = argv[i]; 496 | else { 497 | fprintf(stderr, "Option -%c requires an argument.\n", c); 498 | status = 1; 499 | break; 500 | } 501 | 502 | if (c == 'p') { 503 | preset = rassoc(optarg, config_presets); 504 | if (preset == END_OF_LIST) { 505 | fprintf(stderr, "Unknown preset name: %s\n", optarg); 506 | status = 1; 507 | } 508 | } 509 | else if (c == 'e') { 510 | detection = rassoc(optarg, edge_detection_types); 511 | if (detection == END_OF_LIST) { 512 | fprintf(stderr, "Unknown detection type: %s\n", optarg); 513 | status = 1; 514 | } 515 | } 516 | else if (c == 't') { 517 | threshold = strtof(optarg, &endptr); 518 | if (threshold < 0.0f || *endptr != '\0') { 519 | fprintf(stderr, "Invalid threshold: %s\n", optarg); 520 | status = 1; 521 | } 522 | } 523 | else if (c == 'a') { 524 | adaptation = strtof(optarg, &endptr); 525 | if (adaptation < 0.0f || *endptr != '\0') { 526 | fprintf(stderr, "Invalid contrast adaptation factor: %s\n", optarg); 527 | status = 1; 528 | } 529 | } 530 | else if (c == 's') { 531 | ortho_steps = strtol(optarg, &endptr, 0); 532 | if (ortho_steps < 0 || *endptr != '\0') { 533 | fprintf(stderr, "Invalid maximum search steps: %s\n", optarg); 534 | status = 1; 535 | } 536 | } 537 | else if (c == 'd') { 538 | diag_steps = strtol(optarg, &endptr, 0); 539 | if (diag_steps < -1 || *endptr != '\0') { /* -1 means disable the processing */ 540 | fprintf(stderr, "Invalid maximum diagonal search steps: %s\n", optarg); 541 | status = 1; 542 | } 543 | } 544 | else if (c == 'c') { 545 | rounding = strtol(optarg, &endptr, 0); 546 | if (rounding < -1 || *endptr != '\0') { /* -1 means disable the processing */ 547 | fprintf(stderr, "Invalid corner rounding: %s\n", optarg); 548 | status = 1; 549 | } 550 | } 551 | 552 | break; 553 | } 554 | else if (c == 'v') 555 | verbose = true; 556 | else if (c == 'h') 557 | help = true; 558 | else { 559 | fprintf(stderr, "Unknown option: -%c\n", c); 560 | status = 1; 561 | break; 562 | } 563 | } 564 | } 565 | else if (outfile) { 566 | fprintf(stderr, "Too much file names: %s, %s, %s\n", infile, outfile, argv[i]); 567 | status = 1; 568 | } 569 | else if (infile) 570 | outfile = argv[i]; 571 | else 572 | infile = argv[i]; 573 | 574 | if (status != 0) 575 | break; 576 | } 577 | 578 | if (status == 0 && !help && !outfile) { 579 | fprintf(stderr, "Two file names are required.\n"); 580 | status = 1; 581 | } 582 | 583 | if (status != 0 || help) { 584 | if (status != 0) 585 | fprintf(stderr, "\n"); 586 | fprintf(stderr, "Usage: %s [OPTION]... INFILE OUTFILE\n", argv[0]); 587 | fprintf(stderr, "Remove jaggies from PNG image and write antialiased PNG image.\n\n"); 588 | fprintf(stderr, " -p PRESET Specify base configuration preset\n"); 589 | fprintf(stderr, " [low|medium|high|ultra|extreme]\n"); 590 | fprintf(stderr, " -e DETECTTYPE Specify edge detection type [luma|color|depth]\n"); 591 | fprintf(stderr, " (Depth edge detection uses alpha channel as depths)\n"); 592 | fprintf(stderr, " -t THRESHOLD Specify threshold of edge detection [0.0, 5.0]\n"); 593 | fprintf(stderr, " -a FACTOR Specify local contrast adaptation factor [1.0, inf]\n"); 594 | fprintf(stderr, " -s STEPS Specify maximum search steps [1, 362]\n"); 595 | fprintf(stderr, " -d STEPS Specify maximum diagonal search steps\n"); 596 | fprintf(stderr, " (-1 means disable diagonal processing) -1 or [1, 19]\n"); 597 | fprintf(stderr, " -c ROUNDING Specify corner rounding\n"); 598 | fprintf(stderr, " (-1 means disable corner processing) -1 or [0, 100]\n"); 599 | fprintf(stderr, " -v Print details of what is being done\n"); 600 | fprintf(stderr, " -h Print this help and exit\n"); 601 | return status; 602 | } 603 | 604 | if (verbose) 605 | fprintf(stderr, "smaa_png version %s\n\n", SMAA::VERSION); 606 | 607 | read_png_file(infile, verbose); 608 | process_file(preset, detection, threshold, adaptation, ortho_steps, diag_steps, rounding, verbose); 609 | write_png_file(outfile, verbose); 610 | 611 | if (verbose) 612 | fprintf(stderr, "\ndone.\n"); 613 | 614 | return 0; 615 | } 616 | 617 | /* smaa_png.cpp ends here */ 618 | -------------------------------------------------------------------------------- /include/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2016-2021 IRIE Shinsuke 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | cmake_minimum_required(VERSION 3.1) 23 | 24 | configure_file(smaa_version.h.in smaa_version.h) 25 | 26 | if(WITH_INSTALL_HEADERS) 27 | install(FILES smaa.h smaa_types.h ${CMAKE_CURRENT_BINARY_DIR}/smaa_version.h 28 | DESTINATION include/smaa-cpp) 29 | endif() 30 | -------------------------------------------------------------------------------- /include/smaa.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2017 IRIE Shinsuke 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | /* smaa.h */ 24 | 25 | #ifndef SMAA_H 26 | #define SMAA_H 27 | 28 | #include "smaa_types.h" 29 | #include "smaa_version.h" 30 | 31 | namespace SMAA { 32 | 33 | /*-----------------------------------------------------------------------------*/ 34 | /* SMAA Preset types */ 35 | 36 | enum CONFIG_PRESET { 37 | CONFIG_PRESET_LOW, 38 | CONFIG_PRESET_MEDIUM, 39 | CONFIG_PRESET_HIGH, 40 | CONFIG_PRESET_ULTRA, 41 | CONFIG_PRESET_EXTREME, 42 | }; 43 | 44 | /*-----------------------------------------------------------------------------*/ 45 | /* SMAA Pixel Shaders */ 46 | 47 | class PixelShader { 48 | 49 | private: 50 | float m_threshold; 51 | float m_depth_threshold; 52 | int m_max_search_steps; 53 | bool m_enable_diag_detection; 54 | int m_max_search_steps_diag; 55 | bool m_enable_corner_detection; 56 | int m_corner_rounding; 57 | float m_local_contrast_adaptation_factor; 58 | bool m_enable_predication; 59 | float m_predication_threshold; 60 | float m_predication_scale; 61 | float m_predication_strength; 62 | bool m_enable_reprojection; 63 | float m_reprojection_weight_scale; 64 | 65 | public: 66 | PixelShader() { setPresets(CONFIG_PRESET_HIGH); } 67 | PixelShader(int preset) { setPresets(preset); } 68 | 69 | /*-----------------------------------------------------------------------------*/ 70 | /* SMAA Presets */ 71 | 72 | void setPresets(int preset) 73 | { 74 | m_threshold = 0.1f; 75 | m_depth_threshold = 0.1f; 76 | m_max_search_steps = 34; 77 | m_enable_diag_detection = true; 78 | m_max_search_steps_diag = 8; 79 | m_enable_corner_detection = true; 80 | m_corner_rounding = 25; 81 | m_local_contrast_adaptation_factor = 2.0f; 82 | m_enable_predication = false; 83 | m_predication_threshold = 0.01f; 84 | m_predication_scale = 2.0f; 85 | m_predication_strength = 0.4f; 86 | m_enable_reprojection = false; 87 | m_reprojection_weight_scale = 30.0f; 88 | 89 | switch (preset) { 90 | case CONFIG_PRESET_LOW: 91 | m_threshold = 0.15f; 92 | m_max_search_steps = 10; /* 2 * 4 + 2 = 10 */ 93 | m_enable_diag_detection = false; 94 | m_enable_corner_detection = false; 95 | break; 96 | case CONFIG_PRESET_MEDIUM: 97 | m_threshold = 0.1f; 98 | m_max_search_steps = 18; /* 2 * 8 + 2 = 18 */ 99 | m_enable_diag_detection = false; 100 | m_enable_corner_detection = false; 101 | break; 102 | case CONFIG_PRESET_HIGH: 103 | m_threshold = 0.1f; 104 | m_max_search_steps = 34; /* 2 * 16 + 2 = 34 */ 105 | m_max_search_steps_diag = 8; 106 | m_corner_rounding = 25; 107 | break; 108 | case CONFIG_PRESET_ULTRA: 109 | m_threshold = 0.05f; 110 | m_max_search_steps = 66; /* 2 * 32 + 2 = 66 */ 111 | m_max_search_steps_diag = 16; 112 | m_corner_rounding = 25; 113 | break; 114 | case CONFIG_PRESET_EXTREME: 115 | m_threshold = 0.05f; 116 | m_max_search_steps = 362; /* 362 - 1 = 19^2 */ 117 | m_max_search_steps_diag = 19; 118 | m_corner_rounding = 25; 119 | break; 120 | } 121 | } 122 | 123 | /*-----------------------------------------------------------------------------*/ 124 | /* Set/get parameters */ 125 | 126 | /** 127 | * Specify the threshold or sensitivity to edges. 128 | * Lowering this value you will be able to detect more edges at the expense of 129 | * performance. 130 | * 131 | * Range: [0.0, 0.5] 132 | * 0.1 is a reasonable value, and allows to catch most visible edges. 133 | * 0.05 is a rather overkill value, that allows to catch 'em all. 134 | * 135 | * If temporal supersampling is used, 0.2 could be a reasonable value, as low 136 | * contrast edges are properly filtered by just 2x. 137 | */ 138 | inline void setThreshold(float threshold) { m_threshold = threshold; } 139 | inline float getThreshold() { return m_threshold; } 140 | 141 | /** 142 | * Specify the threshold for depth edge detection. 143 | * 144 | * Range: depends on the depth range of the scene. 145 | */ 146 | 147 | inline void setDepthThreshold(float threshold) { m_depth_threshold = threshold; } 148 | inline float getDepthThreshold() { return m_depth_threshold; } 149 | 150 | /** 151 | * Specify the maximum steps performed in the 152 | * horizontal/vertical pattern searches, at each side of the pixel. 153 | * 154 | * The maximum line length perfectly handled by, for example 16, is 32 155 | * (by perfectly, we meant that longer lines won't look as good, but 156 | * still antialiased). 157 | * 158 | * Range: [1, 362] 159 | */ 160 | inline void setMaxSearchSteps(int steps) { m_max_search_steps = steps; } 161 | inline int getMaxSearchSteps() { return m_max_search_steps; } 162 | 163 | /** 164 | * Specify whether to enable diagonal processing. 165 | */ 166 | inline void setEnableDiagDetection(bool enable) { m_enable_diag_detection = enable; } 167 | inline bool getEnableDiagDetection() { return m_enable_diag_detection; } 168 | 169 | /** 170 | * Specify the maximum steps performed in the 171 | * diagonal pattern searches, at each side of the pixel. In this case we jump 172 | * one pixel at time, instead of two. 173 | * 174 | * Range: [1, 19] 175 | * 176 | * setEnableDiagDetection() to disable diagonal processing. 177 | */ 178 | inline void setMaxSearchStepsDiag(int steps) { m_max_search_steps_diag = steps; } 179 | inline int getMaxSearchStepsDiag() { return m_max_search_steps_diag; } 180 | 181 | /** 182 | * Specify whether to enable corner processing. 183 | */ 184 | inline void setEnableCornerDetection(bool enable) { m_enable_corner_detection = enable; } 185 | inline bool getEnableCornerDetection() { return m_enable_corner_detection; } 186 | 187 | /** 188 | * Specify how much sharp corners will be rounded. 189 | * 190 | * Range: [0, 100] 191 | * 192 | * Use setEnableCornerDetection() to disable corner processing. 193 | */ 194 | inline void setCornerRounding(int rounding) { m_corner_rounding = rounding; } 195 | inline int getCornerRounding() { return m_corner_rounding; } 196 | 197 | /** 198 | * Specify the local contrast adaptation factor. 199 | * 200 | * If there is an neighbor edge that has this factor times 201 | * bigger contrast than current edge, current edge will be discarded. 202 | * 203 | * This allows to eliminate spurious crossing edges, and is based on the fact 204 | * that, if there is too much contrast in a direction, that will hide 205 | * perceptually contrast in the other neighbors. 206 | * 207 | * Range: [1.0, inf] 208 | */ 209 | inline void setLocalContrastAdaptationFactor(float factor) { m_local_contrast_adaptation_factor = factor; } 210 | inline float getLocalContrastAdaptationFactor() { return m_local_contrast_adaptation_factor; } 211 | 212 | /** 213 | * Specify whether to enable predicated thresholding. 214 | * 215 | * Predicated thresholding allows to better preserve texture details and to 216 | * improve performance, by decreasing the number of detected edges using an 217 | * additional buffer like the light accumulation buffer, object ids or even the 218 | * depth buffer (the depth buffer usage may be limited to indoor or short range 219 | * scenes). 220 | * 221 | * It locally decreases the luma or color threshold if an edge is found in an 222 | * additional buffer (so the global threshold can be higher). 223 | * 224 | * This method was developed by Playstation EDGE MLAA team, and used in 225 | * Killzone 3, by using the light accumulation buffer. More information here: 226 | * http://iryoku.com/aacourse/downloads/06-MLAA-on-PS3.pptx 227 | */ 228 | inline void setEnablePredication(bool enable) { m_enable_predication = enable; } 229 | inline bool getEnablePredication() { return m_enable_predication; } 230 | 231 | /** 232 | * Specify threshold to be used in the additional predication buffer. 233 | * 234 | * Range: depends on the input, so you'll have to find the magic number that 235 | * works for you. 236 | */ 237 | inline void setPredicationThreshold(float threshold) { m_predication_threshold = threshold; } 238 | inline float getPredicationThreshold() { return m_predication_threshold; } 239 | 240 | /** 241 | * Specify how much to scale the global threshold used for luma or color edge 242 | * detection when using predication. 243 | * 244 | * Range: [1.0, 5.0] 245 | */ 246 | inline void setPredicationScale(float scale) { m_predication_scale = scale; } 247 | inline float getPredicationScale() { return m_predication_scale; } 248 | 249 | /** 250 | * Specify how much to locally decrease the threshold. 251 | * 252 | * Range: [0.0, 1.0] 253 | */ 254 | inline void setPredicationStrength(float strength) { m_predication_strength = strength; } 255 | inline float getPredicationStrength() { return m_predication_strength; } 256 | 257 | /** 258 | * Specify whether to enable temporal reprojection. 259 | * 260 | * Temporal reprojection allows to remove ghosting artifacts when using 261 | * temporal supersampling. We use the CryEngine 3 method which also introduces 262 | * velocity weighting. This feature is of extreme importance for totally 263 | * removing ghosting. More information here: 264 | * http://iryoku.com/aacourse/downloads/13-Anti-Aliasing-Methods-in-CryENGINE-3.pdf 265 | * 266 | * Note that you'll need to setup a velocity buffer for enabling reprojection. 267 | * For static geometry, saving the previous depth buffer is a viable 268 | * alternative. 269 | */ 270 | inline void setEnableReprojection(bool enable) { m_enable_reprojection = enable; } 271 | inline bool getEnableReprojection() { return m_enable_reprojection; } 272 | 273 | /** 274 | * Specify scale that controls the velocity weighting. It allows to 275 | * remove ghosting trails behind the moving object, which are not removed by 276 | * just using reprojection. Using low values will exhibit ghosting, while using 277 | * high values will disable temporal supersampling under motion. 278 | * 279 | * Behind the scenes, velocity weighting removes temporal supersampling when 280 | * the velocity of the subsamples differs (meaning they are different objects). 281 | * 282 | * Range: [0.0, 80.0] 283 | */ 284 | inline void setReprojectionWeightScale(float scale) { m_reprojection_weight_scale = scale; } 285 | inline float getReprojectionWeightScale() { return m_reprojection_weight_scale; } 286 | 287 | /*-----------------------------------------------------------------------------*/ 288 | /* Edge Detection Pixel Shaders (First Pass) */ 289 | 290 | /** 291 | * Luma Edge Detection 292 | * 293 | * IMPORTANT NOTICE: luma edge detection requires gamma-corrected colors, and 294 | * thus 'colorImage' should be a non-sRGB texture. 295 | */ 296 | void lumaEdgeDetection(int x, int y, 297 | ImageReader *colorImage, 298 | ImageReader *predicationImage, 299 | /* out */ float edges[4]); 300 | 301 | /** 302 | * Determine possible depending area needed for rendering results of the 303 | * luma edge detection in specified rectangle, and modify the minimum and 304 | * maximum coordinates given by pointers. 305 | * 306 | * *xmin -= 2; 307 | * *xmax += 1; 308 | * *ymin -= 2; 309 | * *ymax += 1; 310 | */ 311 | void getAreaLumaEdgeDetection(int *xmin, int *xmax, int *ymin, int *ymax); 312 | 313 | /** 314 | * Color Edge Detection 315 | * 316 | * IMPORTANT NOTICE: color edge detection requires gamma-corrected colors, and 317 | * thus 'colorImage' should be a non-sRGB texture. 318 | */ 319 | void colorEdgeDetection(int x, int y, 320 | ImageReader *colorImage, 321 | ImageReader *predicationImage, 322 | /* out */ float edges[4]); 323 | 324 | /** 325 | * Determine possible depending area needed for rendering results of the 326 | * color edge detection in specified rectangle, and modify the minimum and 327 | * maximum coordinates given by pointers. 328 | * 329 | * *xmin -= 2; 330 | * *xmax += 1; 331 | * *ymin -= 2; 332 | * *ymax += 1; 333 | */ 334 | void getAreaColorEdgeDetection(int *xmin, int *xmax, int *ymin, int *ymax); 335 | 336 | /** 337 | * Depth Edge Detection 338 | */ 339 | void depthEdgeDetection(int x, int y, 340 | ImageReader *depthImage, 341 | /* out */ float edges[4]); 342 | 343 | /** 344 | * Determine possible depending area needed for rendering results of the 345 | * depth edge detection in specified rectangle, and modify the minimum and 346 | * maximum coordinates given by pointers. 347 | * 348 | * *xmin -= 1; 349 | * *ymin -= 1; 350 | */ 351 | void getAreaDepthEdgeDetection(int *xmin, int *xmax, int *ymin, int *ymax); 352 | 353 | /*-----------------------------------------------------------------------------*/ 354 | /* Blending Weight Calculation Pixel Shader (Second Pass) */ 355 | 356 | /** 357 | * Blending Weight Calculation Pixel Shader (Second Pass) 358 | * Just pass zero to subsampleIndices for SMAA 1x, see @SUBSAMPLE_INDICES. 359 | */ 360 | void blendingWeightCalculation(int x, int y, 361 | ImageReader *edgesImage, 362 | const int subsampleIndices[4], 363 | /* out */ float weights[4]); 364 | 365 | /** 366 | * Determine possible depending area needed for rendering results of the 367 | * blending weight calculation in specified rectangle, and modify the minimum 368 | * and maximum coordinates given by pointers. 369 | * 370 | * *xmin -= max(max(m_max_search_steps - 1, 1), 371 | * m_enable_diag_detection ? m_max_search_steps_diag + 1 : 0); 372 | * *xmax += max(m_max_search_steps, 373 | * m_enable_diag_detection ? m_max_search_steps_diag + 1 : 0); 374 | * *ymin -= max(max(m_max_search_steps - 1, 1), 375 | * m_enable_diag_detection ? m_max_search_steps_diag : 0); 376 | * *ymax += max(m_max_search_steps, 377 | * m_enable_diag_detection ? m_max_search_steps_diag : 0); 378 | */ 379 | void getAreaBlendingWeightCalculation(int *xmin, int *xmax, int *ymin, int *ymax); 380 | 381 | /*-----------------------------------------------------------------------------*/ 382 | /* Neighborhood Blending Pixel Shader (Third Pass) */ 383 | 384 | /** 385 | * Neighborhood Blending Pixel Shader (Third Pass) 386 | */ 387 | void neighborhoodBlending(int x, int y, 388 | ImageReader *colorImage, 389 | ImageReader *blendImage, 390 | ImageReader *velocityImage, 391 | /* out */ float color[4]); 392 | 393 | /** 394 | * Determine possible depending area needed for rendering results of the 395 | * neighborhood blending in specified rectangle, and modify the minimum and 396 | * maximum coordinates given by pointers. 397 | * 398 | * *xmin -= 1; 399 | * *xmax += 1; 400 | * *ymin -= 1; 401 | * *ymax += 1; 402 | */ 403 | void getAreaNeighborhoodBlending(int *xmin, int *xmax, int *ymin, int *ymax); 404 | 405 | /*-----------------------------------------------------------------------------*/ 406 | /* Temporal Resolve Pixel Shader (Optional Pass) -- untested yet! */ 407 | 408 | /** 409 | * Temporal Resolve Pixel Shader (Optional Pass) 410 | */ 411 | void resolve(int x, int y, 412 | ImageReader *currentColorImage, 413 | ImageReader *previousColorImage, 414 | ImageReader *velocityImage, 415 | /* out */ float color[4]); 416 | 417 | /* 418 | * There is no getAreaResolve() function because the depending area cannot 419 | * be determined simply. It depends on the maximum velocity and user needs 420 | * to calculate it himself like this: 421 | * 422 | * *xmin -= maxVelocity; 423 | * *xmax += maxVelocity; 424 | * *ymin -= maxVelocity; 425 | * *ymax += maxVelocity; 426 | */ 427 | 428 | private: 429 | /* Internal */ 430 | void calculatePredicatedThreshold(int x, int y, ImageReader *predicationImage, float threshold[2]); 431 | int searchDiag1(ImageReader *edgesImage, int x, int y, int dir, bool *found); 432 | int searchDiag2(ImageReader *edgesImage, int x, int y, int dir, bool *found); 433 | void calculateDiagWeights(ImageReader *edgesImage, int x, int y, const float edges[2], 434 | const int subsampleIndices[4], float weights[2]); 435 | bool isVerticalSearchUnneeded(ImageReader *edgesImage, int x, int y); 436 | int searchXLeft(ImageReader *edgesImage, int x, int y); 437 | int searchXRight(ImageReader *edgesImage, int x, int y); 438 | int searchYUp(ImageReader *edgesImage, int x, int y); 439 | int searchYDown(ImageReader *edgesImage, int x, int y); 440 | void detectHorizontalCornerPattern(ImageReader *edgesImage, float weights[4], 441 | int left, int right, int y, int d1, int d2); 442 | void detectVerticalCornerPattern(ImageReader *edgesImage, float weights[4], 443 | int top, int bottom, int x, int d1, int d2); 444 | }; 445 | 446 | } 447 | #endif /* SMAA_H */ 448 | /* smaa.h ends here */ 449 | -------------------------------------------------------------------------------- /include/smaa_types.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2017 IRIE Shinsuke 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | /* smaa_types.h */ 24 | 25 | #ifndef SMAA_TYPES_H 26 | #define SMAA_TYPES_H 27 | 28 | namespace SMAA { 29 | 30 | /*-----------------------------------------------------------------------------*/ 31 | /* Error types */ 32 | 33 | enum ERROR_TYPE { 34 | ERROR_NONE = 0, 35 | ERROR_IMAGE_SIZE_INVALID, 36 | ERROR_IMAGE_MEMORY_ALLOCATION_FAILED, 37 | ERROR_IMAGE_BROKEN, 38 | ERROR_IMAGE_PUT_PIXEL_COORDS_OUT_OF_RANGE, 39 | }; 40 | 41 | /*-----------------------------------------------------------------------------*/ 42 | /* Base class to define getPixel() member function as a callback */ 43 | 44 | class ImageReader { 45 | 46 | protected: 47 | int m_width, m_height; 48 | 49 | inline bool isOutOfRange(int x, int y) { return (x < 0 || x >= m_width || y < 0 || y >= m_height); } 50 | 51 | public: 52 | ImageReader(int width, int height) : m_width(width), m_height(height) {} 53 | 54 | inline int getWidth() { return m_width; } 55 | inline int getHeight() { return m_height; } 56 | 57 | /* getPixel() must return (0.0, 0.0, 0.0, 0.0) if (x, y) is out of range */ 58 | virtual void getPixel(int x, int y, float color[4]) {} 59 | }; 60 | 61 | /*-----------------------------------------------------------------------------*/ 62 | /* Simple image buffer based on ImageReader */ 63 | 64 | class Image : public ImageReader { 65 | 66 | private: 67 | float *m_data; 68 | 69 | public: 70 | Image(int width, int height) : 71 | ImageReader(width, height), 72 | m_data(NULL) 73 | { 74 | if (m_width <= 0 || m_height <= 0) 75 | throw ERROR_IMAGE_SIZE_INVALID; 76 | 77 | m_data = (float *) calloc(m_width * m_height * 4, sizeof(float)); 78 | 79 | if (!m_data) 80 | throw ERROR_IMAGE_MEMORY_ALLOCATION_FAILED; 81 | } 82 | 83 | ~Image() 84 | { 85 | if (m_data) 86 | free(m_data); 87 | } 88 | 89 | void putPixel(int x, int y, float color[4]) 90 | { 91 | if (!m_data) 92 | throw ERROR_IMAGE_BROKEN; 93 | 94 | if (isOutOfRange(x, y)) 95 | throw ERROR_IMAGE_PUT_PIXEL_COORDS_OUT_OF_RANGE; 96 | 97 | float *ptr = &m_data[(x + y * m_width) * 4]; 98 | *ptr++ = *color++; 99 | *ptr++ = *color++; 100 | *ptr++ = *color++; 101 | *ptr = *color; 102 | } 103 | 104 | void getPixel(int x, int y, float color[4]) 105 | { 106 | if (!m_data) 107 | throw ERROR_IMAGE_BROKEN; 108 | 109 | if (isOutOfRange(x, y)) { 110 | color[0] = color[1] = color[2] = color[3] = 0.0; 111 | return; 112 | } 113 | 114 | float *ptr = &m_data[(x + y * m_width) * 4]; 115 | *color++ = *ptr++; 116 | *color++ = *ptr++; 117 | *color++ = *ptr++; 118 | *color = *ptr; 119 | } 120 | }; 121 | 122 | } 123 | #endif /* SMAA_TYPES_H */ 124 | /* smaa_types.h ends here */ 125 | -------------------------------------------------------------------------------- /include/smaa_version.h.in: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2017 IRIE Shinsuke 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | /* smaa_version.h */ 24 | 25 | #ifndef SMAA_VERSION_H 26 | #define SMAA_VERSION_H 27 | 28 | namespace SMAA { 29 | 30 | static const char *VERSION = "${PROJECT_VERSION}"; 31 | static const int VERSION_MAJOR = ${PROJECT_VERSION_MAJOR}; 32 | static const int VERSION_MINOR = ${PROJECT_VERSION_MINOR}; 33 | static const int VERSION_PATCH = ${PROJECT_VERSION_PATCH}; 34 | 35 | } 36 | #endif /* SMAA_VERSION_H */ 37 | /* smaa_version.h ends here */ 38 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2016-2021 IRIE Shinsuke 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | cmake_minimum_required(VERSION 3.1) 23 | 24 | set(INCDIR ../include) 25 | set(GENDIR ${CMAKE_CURRENT_BINARY_DIR}/../include) 26 | 27 | include_directories(${INCDIR} ${GENDIR}) 28 | 29 | set(GENSRC ${GENDIR}/smaa_areatex.h) 30 | set(SRC 31 | smaa.cpp 32 | ${INCDIR}/smaa.h 33 | ${INCDIR}/smaa_types.h 34 | ${GENSRC} 35 | ) 36 | 37 | if(WITH_SUBPIXEL_RENDERING) 38 | set(AREATEX_OPTIONS -s) 39 | add_definitions(-DWITH_SUBPIXEL_RENDERING) 40 | endif() 41 | 42 | add_custom_command( 43 | OUTPUT ${GENSRC} 44 | COMMAND "$" ${AREATEX_OPTIONS} ${GENSRC} 45 | DEPENDS smaa_areatex 46 | ) 47 | add_custom_target(smaa_areatex_header SOURCES ${GENSRC}) 48 | 49 | if(WITH_LIBRARY_STATIC) 50 | add_library(smaa-static STATIC ${SRC}) 51 | set_target_properties(smaa-static 52 | PROPERTIES 53 | OUTPUT_NAME ${PROJECT_NAME} 54 | CLEAN_DIRECT_OUTPUT 1 55 | PREFIX "lib" 56 | ) 57 | add_dependencies(smaa-static smaa_areatex_header) 58 | install(TARGETS smaa-static DESTINATION lib) 59 | endif() 60 | 61 | if(WITH_LIBRARY_SHARED) 62 | add_library(smaa-shared SHARED ${SRC}) 63 | set_target_properties(smaa-shared 64 | PROPERTIES 65 | OUTPUT_NAME ${PROJECT_NAME} 66 | CLEAN_DIRECT_OUTPUT 1 67 | VERSION ${PROJECT_VERSION} 68 | SOVERSION ${PROJECT_SOVERSION} 69 | ) 70 | add_dependencies(smaa-shared smaa_areatex_header) 71 | install(TARGETS smaa-shared DESTINATION lib) 72 | endif() 73 | -------------------------------------------------------------------------------- /lib/smaa.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016-2017 IRIE Shinsuke 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | /* smaa.cpp */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include "smaa.h" 29 | #include "smaa_areatex.h" 30 | 31 | namespace SMAA { 32 | 33 | /*-----------------------------------------------------------------------------*/ 34 | /* Non-Configurable Defines */ 35 | 36 | static const int AREATEX_SIZE = 80; /* 20 * 4 = 80 */ 37 | static const int AREATEX_MAX_DISTANCE = 20; 38 | static const int AREATEX_MAX_DISTANCE_DIAG = 20; 39 | static const float RGB_WEIGHTS[3] = {0.2126f, 0.7152f, 0.0722f}; 40 | 41 | /*-----------------------------------------------------------------------------*/ 42 | /* Misc functions */ 43 | 44 | static inline float saturate(float x) 45 | { 46 | return 0.0f < x ? (x < 1.0f ? x : 1.0f) : 0.0f; 47 | } 48 | 49 | static inline float lerp(float a, float b, float p) 50 | { 51 | return a + (b - a) * p; 52 | } 53 | 54 | static inline float bilinear(float c00, float c10, float c01, float c11, float x, float y) 55 | { 56 | return (c00 * (1.0f - x) + c10 * x) * (1.0f - y) + (c01 * (1.0f - x) + c11 * x) * y; 57 | } 58 | 59 | static inline float rgb2bw(const float color[4]) 60 | { 61 | return RGB_WEIGHTS[0] * color[0] + RGB_WEIGHTS[1] * color[1] + RGB_WEIGHTS[2] * color[2]; 62 | } 63 | 64 | static inline float color_delta(const float color1[4], const float color2[4]) 65 | { 66 | return fmaxf(fmaxf(fabsf(color1[0] - color2[0]), fabsf(color1[1] - color2[1])), fabsf(color1[2] - color2[2])); 67 | } 68 | 69 | /*-----------------------------------------------------------------------------*/ 70 | /* Internal Functions to Sample Pixel Color from Image with Bilinear Filtering */ 71 | 72 | static void sample_bilinear(ImageReader *image, float x, float y, float output[4]) 73 | { 74 | float ix = floorf(x), iy = floorf(y); 75 | float fx = x - ix, fy = y - iy; 76 | int X = (int)ix, Y = (int)iy; 77 | 78 | float color00[4], color10[4], color01[4], color11[4]; 79 | 80 | image->getPixel(X + 0, Y + 0, color00); 81 | image->getPixel(X + 1, Y + 0, color10); 82 | image->getPixel(X + 0, Y + 1, color01); 83 | image->getPixel(X + 1, Y + 1, color11); 84 | 85 | output[0] = bilinear(color00[0], color10[0], color01[0], color11[0], fx, fy); 86 | output[1] = bilinear(color00[1], color10[1], color01[1], color11[1], fx, fy); 87 | output[2] = bilinear(color00[2], color10[2], color01[2], color11[2], fx, fy); 88 | output[3] = bilinear(color00[3], color10[3], color01[3], color11[3], fx, fy); 89 | } 90 | 91 | static void sample_bilinear_vertical(ImageReader *image, int x, int y, float yoffset, float output[4]) 92 | { 93 | float iy = floorf(yoffset); 94 | float fy = yoffset - iy; 95 | y += (int)iy; 96 | 97 | float color00[4], color01[4]; 98 | 99 | image->getPixel(x + 0, y + 0, color00); 100 | image->getPixel(x + 0, y + 1, color01); 101 | 102 | output[0] = lerp(color00[0], color01[0], fy); 103 | output[1] = lerp(color00[1], color01[1], fy); 104 | output[2] = lerp(color00[2], color01[2], fy); 105 | output[3] = lerp(color00[3], color01[3], fy); 106 | } 107 | 108 | static void sample_bilinear_horizontal(ImageReader *image, int x, int y, float xoffset, float output[4]) 109 | { 110 | float ix = floorf(xoffset); 111 | float fx = xoffset - ix; 112 | x += (int)ix; 113 | 114 | float color00[4], color10[4]; 115 | 116 | image->getPixel(x + 0, y + 0, color00); 117 | image->getPixel(x + 1, y + 0, color10); 118 | 119 | output[0] = lerp(color00[0], color10[0], fx); 120 | output[1] = lerp(color00[1], color10[1], fx); 121 | output[2] = lerp(color00[2], color10[2], fx); 122 | output[3] = lerp(color00[3], color10[3], fx); 123 | } 124 | 125 | /*-----------------------------------------------------------------------------*/ 126 | /* Internal Functions to Sample Blending Weights from AreaTex */ 127 | 128 | static inline int clamp_areatex_coord(int x) 129 | { 130 | return 0 < x ? (x < AREATEX_SIZE ? x : AREATEX_SIZE - 1) : 0; 131 | } 132 | 133 | static inline const float* areatex_sample_internal(const float *areatex, int x, int y) 134 | { 135 | return &areatex[(clamp_areatex_coord(x) + clamp_areatex_coord(y) * AREATEX_SIZE) * 2]; 136 | } 137 | 138 | /** 139 | * We have the distance and both crossing edges. So, what are the areas 140 | * at each side of current edge? 141 | */ 142 | static void area(int d1, int d2, int e1, int e2, int offset, 143 | /* out */ float weights[2]) 144 | { 145 | /* The areas texture is compressed quadratically: */ 146 | float x = (float)(AREATEX_MAX_DISTANCE * e1) + sqrtf((float)d1); 147 | float y = (float)(AREATEX_MAX_DISTANCE * e2) + sqrtf((float)d2); 148 | 149 | #ifdef WITH_SUBPIXEL_RENDERING 150 | /* Move to proper place, according to the subpixel offset: */ 151 | y += (float)(AREATEX_SIZE * offset); 152 | #endif 153 | 154 | /* Do it! */ 155 | float ix = floorf(x), iy = floorf(y); 156 | float fx = x - ix, fy = y - iy; 157 | int X = (int)ix, Y = (int)iy; 158 | 159 | const float *weights00 = areatex_sample_internal(areatex, X + 0, Y + 0); 160 | const float *weights10 = areatex_sample_internal(areatex, X + 1, Y + 0); 161 | const float *weights01 = areatex_sample_internal(areatex, X + 0, Y + 1); 162 | const float *weights11 = areatex_sample_internal(areatex, X + 1, Y + 1); 163 | 164 | weights[0] = bilinear(weights00[0], weights10[0], weights01[0], weights11[0], fx, fy); 165 | weights[1] = bilinear(weights00[1], weights10[1], weights01[1], weights11[1], fx, fy); 166 | } 167 | 168 | /** 169 | * Similar to area(), this calculates the area corresponding to a certain 170 | * diagonal distance and crossing edges 'e'. 171 | */ 172 | static void area_diag(int d1, int d2, int e1, int e2, int offset, 173 | /* out */ float weights[2]) 174 | { 175 | int x = AREATEX_MAX_DISTANCE_DIAG * e1 + d1; 176 | int y = AREATEX_MAX_DISTANCE_DIAG * e2 + d2; 177 | 178 | #ifdef WITH_SUBPIXEL_RENDERING 179 | /* Move to proper place, according to the subpixel offset: */ 180 | y += AREATEX_SIZE * offset; 181 | #endif 182 | 183 | /* Do it! */ 184 | const float *w = areatex_sample_internal(areatex_diag, x, y); 185 | weights[0] = w[0]; 186 | weights[1] = w[1]; 187 | } 188 | 189 | /*-----------------------------------------------------------------------------*/ 190 | /* Predicated Thresholding Used for Edge Detection */ 191 | 192 | /** 193 | * Adjusts the threshold by means of predication. 194 | */ 195 | void PixelShader::calculatePredicatedThreshold(int x, int y, ImageReader *predicationImage, float threshold[2]) 196 | { 197 | float here[4], left[4], top[4]; 198 | 199 | predicationImage->getPixel(x, y, here); 200 | predicationImage->getPixel(x - 1, y, left); 201 | predicationImage->getPixel(x, y - 1, top); 202 | 203 | float edges[2] = {(fabsf(here[0] - left[0]) >= m_predication_threshold) ? 1.0f : 0.0f, 204 | (fabsf(here[0] - top[0]) >= m_predication_threshold) ? 1.0f : 0.0f}; 205 | 206 | float scaled = m_predication_scale * m_threshold; 207 | 208 | threshold[0] = scaled * (1.0f - m_predication_strength * edges[0]); 209 | threshold[1] = scaled * (1.0f - m_predication_strength * edges[1]); 210 | } 211 | 212 | /*-----------------------------------------------------------------------------*/ 213 | /* Edge Detection Pixel Shaders (First Pass) */ 214 | 215 | /** 216 | * Luma Edge Detection 217 | * 218 | * IMPORTANT NOTICE: luma edge detection requires gamma-corrected colors, and 219 | * thus 'colorImage' should be a non-sRGB image. 220 | */ 221 | void PixelShader::lumaEdgeDetection(int x, int y, 222 | ImageReader *colorImage, 223 | ImageReader *predicationImage, 224 | /* out */ float edges[4]) 225 | { 226 | float threshold[2], color[4]; 227 | 228 | /* Calculate the threshold: */ 229 | if (m_enable_predication && predicationImage) 230 | calculatePredicatedThreshold(x, y, predicationImage, threshold); 231 | else 232 | threshold[0] = threshold[1] = m_threshold; 233 | 234 | /* Calculate lumas and deltas: */ 235 | colorImage->getPixel(x, y, color); 236 | float L = rgb2bw(color); 237 | colorImage->getPixel(x - 1, y, color); 238 | float Lleft = rgb2bw(color); 239 | colorImage->getPixel(x, y - 1, color); 240 | float Ltop = rgb2bw(color); 241 | float Dleft = fabsf(L - Lleft); 242 | float Dtop = fabsf(L - Ltop); 243 | 244 | /* We do the usual threshold: */ 245 | edges[0] = (x > 0 && Dleft >= threshold[0]) ? 1.0f : 0.0f; 246 | edges[1] = (y > 0 && Dtop >= threshold[1]) ? 1.0f : 0.0f; 247 | edges[2] = 0.0f; 248 | edges[3] = 1.0f; 249 | 250 | /* Then discard if there is no edge: */ 251 | if (edges[0] == 0.0f && edges[1] == 0.0f) 252 | return; 253 | 254 | /* Calculate right and bottom deltas: */ 255 | colorImage->getPixel(x + 1, y, color); 256 | float Lright = rgb2bw(color); 257 | colorImage->getPixel(x, y + 1, color); 258 | float Lbottom = rgb2bw(color); 259 | float Dright = fabsf(L - Lright); 260 | float Dbottom = fabsf(L - Lbottom); 261 | 262 | /* Calculate the maximum delta in the direct neighborhood: */ 263 | float maxDelta = fmaxf(fmaxf(Dleft, Dright), fmaxf(Dtop, Dbottom)); 264 | 265 | /* Calculate luma used for both left and top edges: */ 266 | colorImage->getPixel(x - 1, y - 1, color); 267 | float Llefttop = rgb2bw(color); 268 | 269 | /* Left edge */ 270 | if (edges[0] != 0.0f) { 271 | /* Calculate deltas around the left pixel: */ 272 | colorImage->getPixel(x - 2, y, color); 273 | float Lleftleft = rgb2bw(color); 274 | colorImage->getPixel(x - 1, y + 1, color); 275 | float Lleftbottom = rgb2bw(color); 276 | float Dleftleft = fabsf(Lleft - Lleftleft); 277 | float Dlefttop = fabsf(Lleft - Llefttop); 278 | float Dleftbottom = fabsf(Lleft - Lleftbottom); 279 | 280 | /* Calculate the final maximum delta: */ 281 | maxDelta = fmaxf(maxDelta, fmaxf(Dleftleft, fmaxf(Dlefttop, Dleftbottom))); 282 | 283 | /* Local contrast adaptation: */ 284 | if (maxDelta > m_local_contrast_adaptation_factor * Dleft) 285 | edges[0] = 0.0f; 286 | } 287 | 288 | /* Top edge */ 289 | if (edges[1] != 0.0f) { 290 | /* Calculate deltas around the top pixel: */ 291 | colorImage->getPixel(x, y - 2, color); 292 | float Ltoptop = rgb2bw(color); 293 | colorImage->getPixel(x + 1, y - 1, color); 294 | float Ltopright = rgb2bw(color); 295 | float Dtoptop = fabsf(Ltop - Ltoptop); 296 | float Dtopleft = fabsf(Ltop - Llefttop); 297 | float Dtopright = fabsf(Ltop - Ltopright); 298 | 299 | /* Calculate the final maximum delta: */ 300 | maxDelta = fmaxf(maxDelta, fmaxf(Dtoptop, fmaxf(Dtopleft, Dtopright))); 301 | 302 | /* Local contrast adaptation: */ 303 | if (maxDelta > m_local_contrast_adaptation_factor * Dtop) 304 | edges[1] = 0.0f; 305 | } 306 | } 307 | 308 | void PixelShader::getAreaLumaEdgeDetection(int *xmin, int *xmax, int *ymin, int *ymax) 309 | { 310 | *xmin -= 2; 311 | *xmax += 1; 312 | *ymin -= 2; 313 | *ymax += 1; 314 | } 315 | 316 | /** 317 | * Color Edge Detection 318 | * 319 | * IMPORTANT NOTICE: color edge detection requires gamma-corrected colors, and 320 | * thus 'colorImage' should be a non-sRGB image. 321 | */ 322 | void PixelShader::colorEdgeDetection(int x, int y, 323 | ImageReader *colorImage, 324 | ImageReader *predicationImage, 325 | /* out */ float edges[4]) 326 | { 327 | float threshold[2]; 328 | 329 | /* Calculate the threshold: */ 330 | if (m_enable_predication && predicationImage) 331 | calculatePredicatedThreshold(x, y, predicationImage, threshold); 332 | else 333 | threshold[0] = threshold[1] = m_threshold; 334 | 335 | /* Calculate color deltas: */ 336 | float C[4], Cleft[4], Ctop[4]; 337 | colorImage->getPixel(x, y, C); 338 | colorImage->getPixel(x - 1, y, Cleft); 339 | colorImage->getPixel(x, y - 1, Ctop); 340 | float Dleft = color_delta(C, Cleft); 341 | float Dtop = color_delta(C, Ctop); 342 | 343 | /* We do the usual threshold: */ 344 | edges[0] = (x > 0 && Dleft >= threshold[0]) ? 1.0f : 0.0f; 345 | edges[1] = (y > 0 && Dtop >= threshold[1]) ? 1.0f : 0.0f; 346 | edges[2] = 0.0f; 347 | edges[3] = 1.0f; 348 | 349 | /* Then discard if there is no edge: */ 350 | if (edges[0] == 0.0f && edges[1] == 0.0f) 351 | return; 352 | 353 | /* Calculate right and bottom deltas: */ 354 | float Cright[4], Cbottom[4]; 355 | colorImage->getPixel(x + 1, y, Cright); 356 | colorImage->getPixel(x, y + 1, Cbottom); 357 | float Dright = color_delta(C, Cright); 358 | float Dbottom = color_delta(C, Cbottom); 359 | 360 | /* Calculate the maximum delta in the direct neighborhood: */ 361 | float maxDelta = fmaxf(fmaxf(Dleft, Dright), fmaxf(Dtop, Dbottom)); 362 | 363 | /* Get color used for both left and top edges: */ 364 | float Clefttop[4]; 365 | colorImage->getPixel(x - 1, y - 1, Clefttop); 366 | 367 | /* Left edge */ 368 | if (edges[0] != 0.0f) { 369 | /* Calculate deltas around the left pixel: */ 370 | float Cleftleft[4], Cleftbottom[4]; 371 | colorImage->getPixel(x - 2, y, Cleftleft); 372 | colorImage->getPixel(x - 1, y + 1, Cleftbottom); 373 | float Dleftleft = color_delta(Cleft, Cleftleft); 374 | float Dlefttop = color_delta(Cleft, Clefttop); 375 | float Dleftbottom = color_delta(Cleft, Cleftbottom); 376 | 377 | /* Calculate the final maximum delta: */ 378 | maxDelta = fmaxf(maxDelta, fmaxf(Dleftleft, fmaxf(Dlefttop, Dleftbottom))); 379 | 380 | /* Local contrast adaptation: */ 381 | if (maxDelta > m_local_contrast_adaptation_factor * Dleft) 382 | edges[0] = 0.0f; 383 | } 384 | 385 | /* Top edge */ 386 | if (edges[1] != 0.0f) { 387 | /* Calculate deltas around the top pixel: */ 388 | float Ctoptop[4], Ctopright[4]; 389 | colorImage->getPixel(x, y - 2, Ctoptop); 390 | colorImage->getPixel(x + 1, y - 1, Ctopright); 391 | float Dtoptop = color_delta(Ctop, Ctoptop); 392 | float Dtopleft = color_delta(Ctop, Clefttop); 393 | float Dtopright = color_delta(Ctop, Ctopright); 394 | 395 | /* Calculate the final maximum delta: */ 396 | maxDelta = fmaxf(maxDelta, fmaxf(Dtoptop, fmaxf(Dtopleft, Dtopright))); 397 | 398 | /* Local contrast adaptation: */ 399 | if (maxDelta > m_local_contrast_adaptation_factor * Dtop) 400 | edges[1] = 0.0f; 401 | } 402 | } 403 | 404 | void PixelShader::getAreaColorEdgeDetection(int *xmin, int *xmax, int *ymin, int *ymax) 405 | { 406 | *xmin -= 2; 407 | *xmax += 1; 408 | *ymin -= 2; 409 | *ymax += 1; 410 | } 411 | 412 | /** 413 | * Depth Edge Detection 414 | */ 415 | void PixelShader::depthEdgeDetection(int x, int y, 416 | ImageReader *depthImage, 417 | /* out */ float edges[4]) 418 | { 419 | float here[4], left[4], top[4]; 420 | 421 | depthImage->getPixel(x, y, here); 422 | depthImage->getPixel(x - 1, y, left); 423 | depthImage->getPixel(x, y - 1, top); 424 | 425 | edges[0] = (x > 0 && fabsf(here[0] - left[0]) >= m_depth_threshold) ? 1.0f : 0.0f; 426 | edges[1] = (y > 0 && fabsf(here[0] - top[0]) >= m_depth_threshold) ? 1.0f : 0.0f; 427 | edges[2] = 0.0f; 428 | edges[3] = 1.0f; 429 | } 430 | 431 | void PixelShader::getAreaDepthEdgeDetection(int *xmin, int *xmax, int *ymin, int *ymax) 432 | { 433 | *xmin -= 1; 434 | *ymin -= 1; 435 | } 436 | 437 | /*-----------------------------------------------------------------------------*/ 438 | /* Diagonal Search Functions */ 439 | 440 | /* 441 | * Note: Edges around a pixel (x, y) 442 | * 443 | * - west (left) : R in (x, y) 444 | * - north (top) : G in (x, y) 445 | * - east (right) : R in (x + 1, y) 446 | * - south (bottom): G in (x, y +1) 447 | */ 448 | 449 | /** 450 | * These functions allows to perform diagonal pattern searches. 451 | */ 452 | int PixelShader::searchDiag1(ImageReader *edgesImage, int x, int y, int dir, 453 | /* out */ bool *found) 454 | { 455 | float edges[4]; 456 | int end = x + m_max_search_steps_diag * dir; 457 | *found = false; 458 | 459 | while (x != end) { 460 | x += dir; 461 | y -= dir; /* Search in direction to bottom-left or top-right */ 462 | edgesImage->getPixel(x, y, edges); 463 | if (edges[1] == 0.0f) { /* north */ 464 | *found = true; 465 | break; 466 | } 467 | if (edges[0] == 0.0f) { /* west */ 468 | *found = true; 469 | /* Ended with north edge if dy > 0 (i.e. dir < 0) */ 470 | return (dir < 0) ? x : x - dir; 471 | } 472 | } 473 | 474 | return x - dir; 475 | } 476 | 477 | int PixelShader::searchDiag2(ImageReader *edgesImage, int x, int y, int dir, 478 | /* out */ bool *found) 479 | { 480 | float edges[4]; 481 | int end = x + m_max_search_steps_diag * dir; 482 | *found = false; 483 | 484 | while (x != end) { 485 | x += dir; 486 | y += dir; /* Search in direction to top-left or bottom-right */ 487 | edgesImage->getPixel(x, y, edges); 488 | if (edges[1] == 0.0f) { /* north */ 489 | *found = true; 490 | break; 491 | } 492 | edgesImage->getPixel(x + 1, y, edges); 493 | if (edges[0] == 0.0f) { /* east */ 494 | *found = true; 495 | /* Ended with north edge if dy > 0 (i.e. dir > 0) */ 496 | return (dir > 0) ? x : x - dir; 497 | } 498 | } 499 | 500 | return x - dir; 501 | } 502 | 503 | /** 504 | * This searches for diagonal patterns and returns the corresponding weights. 505 | */ 506 | void PixelShader::calculateDiagWeights(ImageReader *edgesImage, int x, int y, const float edges[2], 507 | const int subsampleIndices[4], 508 | /* out */ float weights[2]) 509 | { 510 | int d1, d2; 511 | bool found1, found2; 512 | float e[4], c[4]; 513 | 514 | weights[0] = weights[1] = 0.0f; 515 | 516 | if (m_max_search_steps_diag <= 0) 517 | return; 518 | 519 | /* Search for the line ends: */ 520 | /* 521 | * | 522 | * 2--3 523 | * | 524 | * 1--2 525 | * | d2 526 | * 0--1 527 | * | 528 | * 0==0 Start from both ends of (x, y)'s north edge 529 | * |xy 530 | * 1--0 531 | * d1 | 532 | * 2--1 533 | * | 534 | * 3--2 535 | * | 536 | * 537 | */ 538 | if (edges[0] > 0.0f) { /* west of (x, y) */ 539 | d1 = x - searchDiag1(edgesImage, x, y, -1, &found1); 540 | } 541 | else { 542 | d1 = 0; 543 | found1 = true; 544 | } 545 | d2 = searchDiag1(edgesImage, x, y, 1, &found2) - x; 546 | 547 | if (d1 + d2 > 2) { /* d1 + d2 + 1 > 3 */ 548 | /* Fetch the crossing edges: */ 549 | int e1 = 0, e2 = 0; 550 | /* e1, e2 551 | * 0: none 552 | * 1: vertical (e1: down, e2: up) 553 | * 2: horizontal (e1: left, e2: right) 554 | * 3: both 555 | * 556 | * Possible depending area: 557 | * max distances are: d1=N, d2=N-1 558 | * x range [x-N-1, x+(N-1)+1] = [x-N-1, x+N] ... (1) 559 | * y range [y-(N-1)-1, y+N] = [y-N, y+N] ... (2) 560 | * 561 | * where N is max search distance 562 | */ 563 | if (found1) { 564 | int left = x - d1, bottom = y + d1; 565 | 566 | edgesImage->getPixel(left - 1, bottom, c); 567 | if (c[1] > 0.0f) 568 | e1 += 2; /* ...->left->left */ 569 | edgesImage->getPixel(left, bottom, c); 570 | if (c[0] > 0.0f) 571 | e1 += 1; /* ...->left->down->down */ 572 | } 573 | if (found2) { 574 | int right = x + d2, top = y - d2; 575 | 576 | edgesImage->getPixel(right + 1, top, c); 577 | if (c[1] > 0.0f) 578 | e2 += 2; /* ...->right->right */ 579 | edgesImage->getPixel(right + 1, top - 1, c); 580 | if (c[0] > 0.0f) 581 | e2 += 1; /* ...->right->up->up */ 582 | } 583 | 584 | /* Fetch the areas for this line: */ 585 | area_diag(d1, d2, e1, e2, (subsampleIndices ? subsampleIndices[2] : 0), weights); 586 | } 587 | 588 | /* Search for the line ends: */ 589 | /* 590 | * | 591 | * 3--2 592 | * | 593 | * 2--1 594 | * d1 | 595 | * 1--0 596 | * | 597 | * 0==0 Start from both ends of (x, y)'s north edge 598 | * xy| 599 | * 0--1 600 | * | d2 601 | * 1--2 602 | * | 603 | * 2--3 604 | * | 605 | * 606 | */ 607 | d1 = x - searchDiag2(edgesImage, x, y, -1, &found1); 608 | edgesImage->getPixel(x + 1, y, e); 609 | if (e[0] > 0.0f) { /* east of (x, y) */ 610 | d2 = searchDiag2(edgesImage, x, y, 1, &found2) - x; 611 | } 612 | else { 613 | d2 = 0; 614 | found2 = true; 615 | } 616 | 617 | if (d1 + d2 > 2) { /* d1 + d2 + 1 > 3 */ 618 | /* Fetch the crossing edges: */ 619 | int e1 = 0, e2 = 0; 620 | /* e1, e2 621 | * 0: none 622 | * 1: vertical (e1: up, e2: down) 623 | * 2: horizontal (e1: left, e2: right) 624 | * 3: both 625 | * 626 | * Possible depending area: 627 | * max distances are: d1=N-1, d2=N 628 | * x range [x-(N-1)-1, x+N+1] = [x-N, x+N+1] ... (3) 629 | * y range [y-(N-1)-1, y+N] = [y-N, y+N] ... (4) 630 | * 631 | * where N is max search distance 632 | */ 633 | if (found1) { 634 | int left = x - d1, top = y - d1; 635 | 636 | edgesImage->getPixel(left - 1, top, c); 637 | if (c[1] > 0.0f) 638 | e1 += 2; /* ...->left->left */ 639 | edgesImage->getPixel(left, top - 1, c); 640 | if (c[0] > 0.0f) 641 | e1 += 1; /* ...->left->up->up */ 642 | } 643 | if (found2) { 644 | int right = x + d2, bottom = y + d2; 645 | 646 | edgesImage->getPixel(right + 1, bottom, c); 647 | if (c[1] > 0.0f) 648 | e2 += 2; /* ...->right->right */ 649 | if (c[0] > 0.0f) 650 | e2 += 1; /* ...->right->down->down */ 651 | } 652 | 653 | /* Fetch the areas for this line: */ 654 | float w[2]; 655 | area_diag(d1, d2, e1, e2, (subsampleIndices ? subsampleIndices[3] : 0), w); 656 | weights[0] += w[1]; 657 | weights[1] += w[0]; 658 | } 659 | } 660 | 661 | bool PixelShader::isVerticalSearchUnneeded(ImageReader *edgesImage, int x, int y) 662 | { 663 | int d1, d2; 664 | bool found; 665 | float e[4]; 666 | 667 | if (m_max_search_steps_diag <= 0) 668 | return false; 669 | 670 | /* Search for the line ends: */ 671 | /* 672 | * | 673 | * 3--2 674 | * | 675 | * 2--1 676 | * d1 | 677 | * 1--0 678 | * | 679 | * 0==0 Start from both ends of (x-1, y)'s north edge 680 | * |xy 681 | * 0--1 682 | * | d2 683 | * 1--2 684 | * | 685 | * 2--3 686 | * | 687 | * 688 | * We've already done diagonal search and weight calculation in this direction, 689 | * so want to know only whether there is diagonal edge in order to avoid 690 | * performing unneeded vertical search and weight calculations. 691 | */ 692 | edgesImage->getPixel(x - 1, y, e); 693 | if (e[1] > 0.0f) /* north of (x-1, y) */ 694 | d1 = x - searchDiag2(edgesImage, x - 1, y, -1, &found); 695 | else 696 | d1 = 0; 697 | d2 = searchDiag2(edgesImage, x - 1, y, 1, &found) - x; 698 | /* 699 | * Possible depending area: 700 | * x range [(x-1)-(N-1), (x-1)+(N-1)+1] = [x-N, x+N-1] ... (5) 701 | * y range [y-(N-1), y+(N-1)] = [y-N+1, y+N-1] ... (6) 702 | * 703 | * where N is max search distance 704 | */ 705 | 706 | return (d1 + d2 > 2); /* d1 + d2 + 1 > 3 */ 707 | } 708 | 709 | /* 710 | * Final depending area considering all diagonal searches: 711 | * x range: (1)(3)(5) -> [x-N-1, x+N+1] = [x-(N+1), x+(N+1)] 712 | * y range: (2)(4)(6) -> [y-N, y+N] 713 | */ 714 | 715 | /*-----------------------------------------------------------------------------*/ 716 | /* Horizontal/Vertical Search Functions */ 717 | 718 | int PixelShader::searchXLeft(ImageReader *edgesImage, int x, int y) 719 | { 720 | int end = x - m_max_search_steps; 721 | float edges[4]; 722 | 723 | while (x > end) { 724 | edgesImage->getPixel(x, y, edges); 725 | if (edges[1] == 0.0f) /* Is the north edge not activated? */ 726 | break; /* x + 1 */ 727 | if (edges[0] != 0.0f) /* Or is there a bottom crossing edge that breaks the line? */ 728 | return x; 729 | edgesImage->getPixel(x, y - 1, edges); 730 | if (edges[0] != 0.0f) /* Or is there a top crossing edge that breaks the line? */ 731 | return x; 732 | x--; 733 | } 734 | 735 | return x + 1; 736 | } 737 | 738 | int PixelShader::searchXRight(ImageReader *edgesImage, int x, int y) 739 | { 740 | int end = x + m_max_search_steps; 741 | float edges[4]; 742 | 743 | while (x < end) { 744 | x++; 745 | edgesImage->getPixel(x, y, edges); 746 | if (edges[1] == 0.0f || /* Is the north edge not activated? */ 747 | edges[0] != 0.0f) /* Or is there a bottom crossing edge that breaks the line? */ 748 | break; 749 | edgesImage->getPixel(x, y - 1, edges); 750 | if (edges[0] != 0.0f) /* Or is there a top crossing edge that breaks the line? */ 751 | break; 752 | } 753 | 754 | return x - 1; 755 | } 756 | 757 | int PixelShader::searchYUp(ImageReader *edgesImage, int x, int y) 758 | { 759 | int end = y - m_max_search_steps; 760 | float edges[4]; 761 | 762 | while (y > end) { 763 | edgesImage->getPixel(x, y, edges); 764 | if (edges[0] == 0.0f) /* Is the west edge not activated? */ 765 | break; /* y + 1 */ 766 | if (edges[1] != 0.0f) /* Or is there a right crossing edge that breaks the line? */ 767 | return y; 768 | edgesImage->getPixel(x - 1, y, edges); 769 | if (edges[1] != 0.0f) /* Or is there a left crossing edge that breaks the line? */ 770 | return y; 771 | y--; 772 | } 773 | 774 | return y + 1; 775 | } 776 | 777 | int PixelShader::searchYDown(ImageReader *edgesImage, int x, int y) 778 | { 779 | int end = y + m_max_search_steps; 780 | float edges[4]; 781 | 782 | while (y < end) { 783 | y++; 784 | edgesImage->getPixel(x, y, edges); 785 | if (edges[0] == 0.0f || /* Is the west edge not activated? */ 786 | edges[1] != 0.0f) /* Or is there a right crossing edge that breaks the line? */ 787 | break; 788 | edgesImage->getPixel(x - 1, y, edges); 789 | if (edges[1] != 0.0f) /* Or is there a left crossing edge that breaks the line? */ 790 | break; 791 | } 792 | 793 | return y - 1; 794 | } 795 | 796 | /*-----------------------------------------------------------------------------*/ 797 | /* Corner Detection Functions */ 798 | 799 | void PixelShader::detectHorizontalCornerPattern(ImageReader *edgesImage, 800 | /* inout */ float weights[4], 801 | int left, int right, int y, int d1, int d2) 802 | { 803 | float factor[2] = {1.0f, 1.0f}; 804 | float rounding = 1.0f - (float)m_corner_rounding / 100.0f; 805 | float edges[4]; 806 | 807 | /* Reduce blending for pixels in the center of a line. */ 808 | rounding *= (d1 == d2) ? 0.5f : 1.0f; 809 | 810 | /* Near the left corner */ 811 | if (d1 <= d2) { 812 | edgesImage->getPixel(left, y + 1, edges); 813 | factor[0] -= rounding * edges[0]; 814 | edgesImage->getPixel(left, y - 2, edges); 815 | factor[1] -= rounding * edges[0]; 816 | } 817 | /* Near the right corner */ 818 | if (d1 >= d2) { 819 | edgesImage->getPixel(right + 1, y + 1, edges); 820 | factor[0] -= rounding * edges[0]; 821 | edgesImage->getPixel(right + 1, y - 2, edges); 822 | factor[1] -= rounding * edges[0]; 823 | } 824 | 825 | weights[0] *= saturate(factor[0]); 826 | weights[1] *= saturate(factor[1]); 827 | } 828 | 829 | void PixelShader::detectVerticalCornerPattern(ImageReader *edgesImage, 830 | /* inout */ float weights[4], 831 | int top, int bottom, int x, int d1, int d2) 832 | { 833 | float factor[2] = {1.0f, 1.0f}; 834 | float rounding = 1.0f - (float)m_corner_rounding / 100.0f; 835 | float edges[4]; 836 | 837 | /* Reduce blending for pixels in the center of a line. */ 838 | rounding *= (d1 == d2) ? 0.5f : 1.0f; 839 | 840 | /* Near the top corner */ 841 | if (d1 <= d2) { 842 | edgesImage->getPixel(x + 1, top, edges); 843 | factor[0] -= rounding * edges[1]; 844 | edgesImage->getPixel(x - 2, top, edges); 845 | factor[1] -= rounding * edges[1]; 846 | } 847 | /* Near the bottom corner */ 848 | if (d1 >= d2) { 849 | edgesImage->getPixel(x + 1, bottom + 1, edges); 850 | factor[0] -= rounding * edges[1]; 851 | edgesImage->getPixel(x - 2, bottom + 1, edges); 852 | factor[1] -= rounding * edges[1]; 853 | } 854 | 855 | weights[2] *= saturate(factor[0]); 856 | weights[3] *= saturate(factor[1]); 857 | } 858 | 859 | /*-----------------------------------------------------------------------------*/ 860 | /* Blending Weight Calculation Pixel Shader (Second Pass) */ 861 | /* Just pass zero to subsampleIndices for SMAA 1x, see @SUBSAMPLE_INDICES. */ 862 | 863 | void PixelShader::blendingWeightCalculation(int x, int y, 864 | ImageReader *edgesImage, 865 | const int subsampleIndices[4], 866 | /* out */ float weights[4]) 867 | { 868 | float edges[4], c[4]; 869 | 870 | weights[0] = weights[1] = weights[2] = weights[3] = 0.0f; 871 | edgesImage->getPixel(x, y, edges); 872 | 873 | if (edges[1] > 0.0f) { /* Edge at north */ 874 | if (m_enable_diag_detection) { 875 | /* Diagonals have both north and west edges, so calculating weights for them */ 876 | /* in one of the boundaries is enough. */ 877 | calculateDiagWeights(edgesImage, x, y, edges, subsampleIndices, weights); 878 | 879 | /* We give priority to diagonals, so if we find a diagonal we skip */ 880 | /* horizontal/vertical processing. */ 881 | if (weights[0] + weights[1] != 0.0f) 882 | return; 883 | } 884 | 885 | /* Find the distance to the left and the right: */ 886 | /* 887 | * <- left right -> 888 | * 2 1 0 0 1 2 889 | * | | | | | | 890 | * --2--1--0==0--1--2-- 891 | * | | |xy| | | 892 | * 2 1 0 0 1 2 893 | */ 894 | int left = searchXLeft(edgesImage, x, y); 895 | int right = searchXRight(edgesImage, x, y); 896 | int d1 = x - left, d2 = right - x; 897 | 898 | /* Now fetch the left and right crossing edges: */ 899 | int e1 = 0, e2 = 0; 900 | /* e1, e2 901 | * 0: none 902 | * 1: top 903 | * 2: bottom 904 | * 3: both 905 | * 906 | * Possible depending area: 907 | * max distances are: d1=N-1, d2=N-1 908 | * x range [x-(N-1), x+(N-1)+1] = [x-N+1, x+N] ... (1) 909 | * y range [y-1, y] ... (2) 910 | * 911 | * where N is max search distance 912 | */ 913 | edgesImage->getPixel(left, y - 1, c); 914 | if (c[0] > 0.0f) 915 | e1 += 1; 916 | edgesImage->getPixel(left, y, c); 917 | if (c[0] > 0.0f) 918 | e1 += 2; 919 | edgesImage->getPixel(right + 1, y - 1, c); 920 | if (c[0] > 0.0f) 921 | e2 += 1; 922 | edgesImage->getPixel(right + 1, y, c); 923 | if (c[0] > 0.0f) 924 | e2 += 2; 925 | 926 | /* Ok, we know how this pattern looks like, now it is time for getting */ 927 | /* the actual area: */ 928 | area(d1, d2, e1, e2, (subsampleIndices ? subsampleIndices[1] : 0), weights); 929 | 930 | /* Fix corners: */ 931 | if (m_enable_corner_detection) 932 | detectHorizontalCornerPattern(edgesImage, weights, left, right, y, d1, d2); 933 | } 934 | 935 | if (edges[0] > 0.0f) { /* Edge at west */ 936 | /* Did we already do diagonal search for this west edge from the left neighboring pixel? */ 937 | if (m_enable_diag_detection && isVerticalSearchUnneeded(edgesImage, x, y)) 938 | return; 939 | 940 | /* Find the distance to the top and the bottom: */ 941 | /* | 942 | * 2--2--2 943 | * | 944 | * 1--1--1 A 945 | * | | 946 | * 0--0--0 top 947 | * ||xy 948 | * 0--0--0 bottom 949 | * | | 950 | * 1--1--1 V 951 | * | 952 | * 2--2--2 953 | * | */ 954 | int top = searchYUp(edgesImage, x, y); 955 | int bottom = searchYDown(edgesImage, x, y); 956 | int d1 = y - top, d2 = bottom - y; 957 | 958 | /* Fetch the top ang bottom crossing edges: */ 959 | int e1 = 0, e2 = 0; 960 | /* e1, e2 961 | * 0: none 962 | * 1: left 963 | * 2: right 964 | * 3: both 965 | * 966 | * Possible depending area: 967 | * max distances are: d1=N-1, d2=N-1 968 | * x range [x-1, x] ... (3) 969 | * y range [y-(N-1), y+(N-1)+1] = [y-N+1, y+N] ... (4) 970 | * 971 | * where N is max search distance 972 | */ 973 | edgesImage->getPixel(x - 1, top, c); 974 | if (c[1] > 0.0f) 975 | e1 += 1; 976 | edgesImage->getPixel(x, top, c); 977 | if (c[1] > 0.0f) 978 | e1 += 2; 979 | edgesImage->getPixel(x - 1, bottom + 1, c); 980 | if (c[1] > 0.0f) 981 | e2 += 1; 982 | edgesImage->getPixel(x, bottom + 1, c); 983 | if (c[1] > 0.0f) 984 | e2 += 2; 985 | 986 | /* Get the area for this direction: */ 987 | area(d1, d2, e1, e2, (subsampleIndices ? subsampleIndices[0] : 0), weights + 2); 988 | 989 | /* Fix corners: */ 990 | if (m_enable_corner_detection) 991 | detectVerticalCornerPattern(edgesImage, weights, top, bottom, x, d1, d2); 992 | } 993 | /* 994 | * Final depending area considering all orthogonal searches: 995 | * x range: (1),(3) -> [min(x-N+1, x-1), x+N] = [x-max(N-1, 1), x+N] 996 | * y range: (2),(4) -> [min(x-N+1, y-1), y+N] = [y-max(N-1, 1), y+N] 997 | */ 998 | } 999 | 1000 | void PixelShader::getAreaBlendingWeightCalculation(int *xmin, int *xmax, int *ymin, int *ymax) 1001 | { 1002 | using std::max; 1003 | 1004 | *xmin -= max(max(m_max_search_steps - 1, 1), 1005 | m_enable_diag_detection ? m_max_search_steps_diag + 1 : 0); 1006 | *xmax += max(m_max_search_steps, 1007 | m_enable_diag_detection ? m_max_search_steps_diag + 1 : 0); 1008 | *ymin -= max(max(m_max_search_steps - 1, 1), 1009 | m_enable_diag_detection ? m_max_search_steps_diag : 0); 1010 | *ymax += max(m_max_search_steps, 1011 | m_enable_diag_detection ? m_max_search_steps_diag : 0); 1012 | } 1013 | 1014 | /*-----------------------------------------------------------------------------*/ 1015 | /* Neighborhood Blending Pixel Shader (Third Pass) */ 1016 | 1017 | void PixelShader::neighborhoodBlending(int x, int y, 1018 | ImageReader *colorImage, 1019 | ImageReader *blendImage, 1020 | ImageReader *velocityImage, 1021 | /* out */ float color[4]) 1022 | { 1023 | float w[4]; 1024 | 1025 | /* Fetch the blending weights for current pixel: */ 1026 | blendImage->getPixel(x, y, w); 1027 | float left = w[2], top = w[0]; 1028 | blendImage->getPixel(x + 1, y , w); 1029 | float right = w[3]; 1030 | blendImage->getPixel(x, y + 1, w); 1031 | float bottom = w[1]; 1032 | 1033 | /* Is there any blending weight with a value greater than 0.0? */ 1034 | if (right + bottom + left + top < 1e-5) { 1035 | colorImage->getPixel(x, y, color); 1036 | 1037 | if (m_enable_reprojection && velocityImage) { 1038 | float velocity[4]; 1039 | velocityImage->getPixel(x, y, velocity); 1040 | 1041 | /* Pack velocity into the alpha channel: */ 1042 | color[3] = sqrtf(5.0f * sqrtf(velocity[0] * velocity[0] + velocity[1] * velocity[1])); 1043 | } 1044 | 1045 | return; 1046 | } 1047 | 1048 | /* Calculate the blending offsets: */ 1049 | void (*samplefunc)(ImageReader *image, int x, int y, float offset, float color[4]); 1050 | float offset1, offset2, weight1, weight2; 1051 | 1052 | if (fmaxf(right, left) > fmaxf(bottom, top)) { /* max(horizontal) > max(vertical) */ 1053 | samplefunc = sample_bilinear_horizontal; 1054 | offset1 = right; 1055 | offset2 = -left; 1056 | weight1 = right / (right + left); 1057 | weight2 = left / (right + left); 1058 | } 1059 | else { 1060 | samplefunc = sample_bilinear_vertical; 1061 | offset1 = bottom; 1062 | offset2 = -top; 1063 | weight1 = bottom / (bottom + top); 1064 | weight2 = top / (bottom + top); 1065 | } 1066 | 1067 | /* We exploit bilinear filtering to mix current pixel with the chosen neighbor: */ 1068 | float color1[4], color2[4]; 1069 | samplefunc(colorImage, x, y, offset1, color1); 1070 | samplefunc(colorImage, x, y, offset2, color2); 1071 | 1072 | color[0] = weight1 * color1[0] + weight2 * color2[0]; 1073 | color[1] = weight1 * color1[1] + weight2 * color2[1]; 1074 | color[2] = weight1 * color1[2] + weight2 * color2[2]; 1075 | color[3] = weight1 * color1[3] + weight2 * color2[3]; 1076 | 1077 | if (m_enable_reprojection && velocityImage) { 1078 | /* Antialias velocity for proper reprojection in a later stage: */ 1079 | float velocity1[4], velocity2[4]; 1080 | samplefunc(velocityImage, x, y, offset1, velocity1); 1081 | samplefunc(velocityImage, x, y, offset2, velocity2); 1082 | float velocity_x = weight1 * velocity1[0] + weight2 * velocity2[0]; 1083 | float velocity_y = weight1 * velocity1[1] + weight2 * velocity2[1]; 1084 | 1085 | /* Pack velocity into the alpha channel: */ 1086 | color[3] = sqrtf(5.0f * sqrtf(velocity_x * velocity_x + velocity_y * velocity_y)); 1087 | } 1088 | } 1089 | 1090 | void PixelShader::getAreaNeighborhoodBlending(int *xmin, int *xmax, int *ymin, int *ymax) 1091 | { 1092 | *xmin -= 1; 1093 | *xmax += 1; 1094 | *ymin -= 1; 1095 | *ymax += 1; 1096 | } 1097 | 1098 | /*-----------------------------------------------------------------------------*/ 1099 | /* Temporal Resolve Pixel Shader (Optional Pass) -- untested yet! */ 1100 | 1101 | void PixelShader::resolve(int x, int y, 1102 | ImageReader *currentColorImage, 1103 | ImageReader *previousColorImage, 1104 | ImageReader *velocityImage, 1105 | /* out */ float color[4]) 1106 | { 1107 | if (m_enable_predication && velocityImage) { 1108 | /* Velocity is assumed to be calculated for motion blur, so we need to */ 1109 | /* inverse it for reprojection: */ 1110 | float velocity[4]; 1111 | velocityImage->getPixel(x, y, velocity); 1112 | 1113 | /* Fetch current pixel: */ 1114 | float current[4]; 1115 | currentColorImage->getPixel(x, y, current); 1116 | 1117 | /* Reproject current coordinates and fetch previous pixel: */ 1118 | float previous[4]; 1119 | sample_bilinear(previousColorImage, x - velocity[0], y - velocity[1], previous); 1120 | 1121 | /* Attenuate the previous pixel if the velocity is different: */ 1122 | float delta = fabsf(current[3] * current[3] - previous[3] * previous[3]) / 5.0f; 1123 | float weight = 0.5f * saturate(1.0f - sqrtf(delta) * m_reprojection_weight_scale); 1124 | 1125 | /* Blend the pixels according to the calculated weight: */ 1126 | color[0] = lerp(current[0], previous[0], weight); 1127 | color[1] = lerp(current[1], previous[1], weight); 1128 | color[2] = lerp(current[2], previous[2], weight); 1129 | color[3] = lerp(current[3], previous[3], weight); 1130 | } 1131 | else { 1132 | /* Just blend the pixels: */ 1133 | float current[4], previous[4]; 1134 | currentColorImage->getPixel(x, y, current); 1135 | previousColorImage->getPixel(x, y, previous); 1136 | color[0] = (current[0] + previous[0]) * 0.5f; 1137 | color[1] = (current[1] + previous[1]) * 0.5f; 1138 | color[2] = (current[2] + previous[2]) * 0.5f; 1139 | color[3] = (current[3] + previous[3]) * 0.5f; 1140 | } 1141 | } 1142 | 1143 | /*-----------------------------------------------------------------------------*/ 1144 | 1145 | } 1146 | /* smaa.cpp ends here */ 1147 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2016-2021 IRIE Shinsuke 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | # 22 | cmake_minimum_required(VERSION 3.1) 23 | 24 | file(GLOB FILES *_aa.png) 25 | unset(IMAGES) 26 | foreach(FILE IN LISTS FILES) 27 | get_filename_component(FILE ${FILE} NAME) 28 | string(REGEX REPLACE "_aa\\.png$" "" IMAGE ${FILE}) 29 | list(APPEND IMAGES ${IMAGE}) 30 | endforeach() 31 | 32 | foreach(IMAGE IN LISTS IMAGES) 33 | add_test( 34 | NAME filter_${IMAGE} 35 | COMMAND "$" ${CMAKE_CURRENT_SOURCE_DIR}/${IMAGE}.png ${IMAGE}_result.png 36 | ) 37 | endforeach() 38 | 39 | foreach(IMAGE IN LISTS IMAGES) 40 | add_test( 41 | NAME compare_${IMAGE} 42 | COMMAND diff -s ${CMAKE_CURRENT_SOURCE_DIR}/${IMAGE}_aa.png ${IMAGE}_result.png 43 | ) 44 | endforeach() 45 | -------------------------------------------------------------------------------- /tests/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/circle.png -------------------------------------------------------------------------------- /tests/circle_aa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/circle_aa.png -------------------------------------------------------------------------------- /tests/invader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/invader.png -------------------------------------------------------------------------------- /tests/invader_aa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/invader_aa.png -------------------------------------------------------------------------------- /tests/mizuki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/mizuki.png -------------------------------------------------------------------------------- /tests/mizuki_aa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/mizuki_aa.png -------------------------------------------------------------------------------- /tests/monkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/monkey.png -------------------------------------------------------------------------------- /tests/monkey_aa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/monkey_aa.png -------------------------------------------------------------------------------- /tests/pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/pattern.png -------------------------------------------------------------------------------- /tests/pattern2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/pattern2.png -------------------------------------------------------------------------------- /tests/pattern2_aa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/pattern2_aa.png -------------------------------------------------------------------------------- /tests/pattern_aa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/pattern_aa.png -------------------------------------------------------------------------------- /tests/square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/square.png -------------------------------------------------------------------------------- /tests/square_aa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/square_aa.png -------------------------------------------------------------------------------- /tests/suzu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/suzu.png -------------------------------------------------------------------------------- /tests/suzu_aa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRi-E/smaa-cpp/82394ad14b081ad5d61003489ee33b257706d090/tests/suzu_aa.png --------------------------------------------------------------------------------