| 1 | /* |
| 2 | * Texture Filtering |
| 3 | * Version: 1.0 |
| 4 | * |
| 5 | * Copyright (C) 2007 Hiroshi Morii All Rights Reserved. |
| 6 | * Email koolsmoky(at)users.sourceforge.net |
| 7 | * Web http://www.3dfxzone.it/koolsmoky |
| 8 | * |
| 9 | * this is free software; you can redistribute it and/or modify |
| 10 | * it under the terms of the GNU General Public License as published by |
| 11 | * the Free Software Foundation; either version 2, or (at your option) |
| 12 | * any later version. |
| 13 | * |
| 14 | * this is distributed in the hope that it will be useful, |
| 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 17 | * GNU General Public License for more details. |
| 18 | * |
| 19 | * You should have received a copy of the GNU General Public License |
| 20 | * along with GNU Make; see the file COPYING. If not, write to |
| 21 | * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. |
| 22 | */ |
| 23 | |
| 24 | #include "TxReSample.h" |
| 25 | #include "TxDbg.h" |
| 26 | #include <stdlib.h> |
| 27 | #include <memory.h> |
| 28 | |
| 29 | #define _USE_MATH_DEFINES |
| 30 | #include <math.h> |
| 31 | |
| 32 | #ifndef M_PI |
| 33 | #define M_PI 3.14159265358979323846 |
| 34 | #endif |
| 35 | |
| 36 | int |
| 37 | TxReSample::nextPow2(int num) |
| 38 | { |
| 39 | num = num - 1; |
| 40 | num = num | (num >> 1); |
| 41 | num = num | (num >> 2); |
| 42 | num = num | (num >> 4); |
| 43 | num = num | (num >> 8); |
| 44 | num = num | (num >> 16); |
| 45 | /*num = num | (num >> 32);*//* for 64bit architecture */ |
| 46 | num = num + 1; |
| 47 | |
| 48 | return num; |
| 49 | } |
| 50 | |
| 51 | boolean |
| 52 | TxReSample::nextPow2(uint8** image, int* width, int* height, int bpp, boolean use_3dfx = 0) |
| 53 | { |
| 54 | /* NOTE: bpp must be one of the follwing: 8, 16, 24, 32 bits per pixel */ |
| 55 | |
| 56 | if (!*image || !*width || !*height || !bpp) |
| 57 | return 0; |
| 58 | |
| 59 | int row_bytes = ((*width * bpp) >> 3); |
| 60 | int o_row_bytes = row_bytes; |
| 61 | int o_width = *width; |
| 62 | int n_width = *width; |
| 63 | int o_height = *height; |
| 64 | int n_height = *height; |
| 65 | |
| 66 | /* HACKALERT: I have explicitly subtracted (n) from width/height to |
| 67 | * adjust textures that have (n) pixel larger width/height than |
| 68 | * power of 2 size. This is a dirty hack for textures that have |
| 69 | * munged aspect ratio by (n) pixel to the original. |
| 70 | */ |
| 71 | if (n_width > 64) n_width -= 4; |
| 72 | else if (n_width > 16) n_width -= 2; |
| 73 | else if (n_width > 4) n_width -= 1; |
| 74 | |
| 75 | if (n_height > 64) n_height -= 4; |
| 76 | else if (n_height > 16) n_height -= 2; |
| 77 | else if (n_height > 4) n_height -= 1; |
| 78 | |
| 79 | n_width = nextPow2(n_width); |
| 80 | n_height = nextPow2(n_height); |
| 81 | row_bytes = (n_width * bpp) >> 3; |
| 82 | |
| 83 | /* 3dfx Glide3 format, W:H aspect ratio range (8:1 - 1:8) */ |
| 84 | if (use_3dfx) { |
| 85 | if (n_width > n_height) { |
| 86 | if (n_width > (n_height << 3)) |
| 87 | n_height = n_width >> 3; |
| 88 | } else { |
| 89 | if (n_height > (n_width << 3)) { |
| 90 | n_width = n_height >> 3; |
| 91 | row_bytes = (n_width * bpp) >> 3; |
| 92 | } |
| 93 | } |
| 94 | DBG_INFO(80, L"using 3dfx W:H aspect ratio range (8:1 - 1:8).\n"); |
| 95 | } |
| 96 | |
| 97 | /* do we really need to do this ? */ |
| 98 | if (o_width == n_width && o_height == n_height) |
| 99 | return 1; /* nope */ |
| 100 | |
| 101 | DBG_INFO(80, L"expand image to next power of 2 dimensions. %d x %d -> %d x %d\n", |
| 102 | o_width, o_height, n_width, n_height); |
| 103 | |
| 104 | if (o_width > n_width) |
| 105 | o_width = n_width; |
| 106 | |
| 107 | if (o_height > n_height) |
| 108 | o_height = n_height; |
| 109 | |
| 110 | /* allocate memory to read in image */ |
| 111 | uint8 *pow2image = (uint8*)malloc(row_bytes * n_height); |
| 112 | |
| 113 | /* read in image */ |
| 114 | if (pow2image) { |
| 115 | int i, j; |
| 116 | uint8 *tmpimage = *image, *tmppow2image = pow2image; |
| 117 | |
| 118 | for (i = 0; i < o_height; i++) { |
| 119 | /* copy row */ |
| 120 | memcpy(tmppow2image, tmpimage, ((o_width * bpp) >> 3)); |
| 121 | |
| 122 | /* expand to pow2 size by replication */ |
| 123 | for(j = ((o_width * bpp) >> 3); j < row_bytes; j++) |
| 124 | tmppow2image[j] = tmppow2image[j - (bpp >> 3)]; |
| 125 | |
| 126 | tmppow2image += row_bytes; |
| 127 | tmpimage += o_row_bytes; |
| 128 | } |
| 129 | /* expand to pow2 size by replication */ |
| 130 | for (i = o_height; i < n_height; i++) |
| 131 | memcpy(&pow2image[row_bytes * i], &pow2image[row_bytes * (i - 1)], row_bytes); |
| 132 | |
| 133 | free(*image); |
| 134 | |
| 135 | *image = pow2image; |
| 136 | *height = n_height; |
| 137 | *width = n_width; |
| 138 | |
| 139 | return 1; |
| 140 | } |
| 141 | |
| 142 | return 0; |
| 143 | } |
| 144 | |
| 145 | /* Ken Turkowski |
| 146 | * Filters for Common Resampling Tasks |
| 147 | * Apple Computer 1990 |
| 148 | */ |
| 149 | double |
| 150 | TxReSample::tent(double x) |
| 151 | { |
| 152 | if (x < 0.0) x = -x; |
| 153 | if (x < 1.0) return (1.0 - x); |
| 154 | return 0.0; |
| 155 | } |
| 156 | |
| 157 | double |
| 158 | TxReSample::gaussian(double x) |
| 159 | { |
| 160 | if (x < 0) x = -x; |
| 161 | if (x < 2.0) return pow(2.0, -2.0 * x * x); |
| 162 | return 0.0; |
| 163 | } |
| 164 | |
| 165 | double |
| 166 | TxReSample::sinc(double x) |
| 167 | { |
| 168 | if (x == 0) return 1.0; |
| 169 | x *= M_PI; |
| 170 | return (sin(x) / x); |
| 171 | } |
| 172 | |
| 173 | double |
| 174 | TxReSample::lanczos3(double x) |
| 175 | { |
| 176 | if (x < 0) x = -x; |
| 177 | if (x < 3.0) return (sinc(x) * sinc(x/3.0)); |
| 178 | return 0.0; |
| 179 | } |
| 180 | |
| 181 | /* Don P. Mitchell and Arun N. Netravali |
| 182 | * Reconstruction Filters in Computer Graphics |
| 183 | * SIGGRAPH '88 |
| 184 | * Proceedings of the 15th annual conference on Computer |
| 185 | * graphics and interactive techniques, pp221-228, 1988 |
| 186 | */ |
| 187 | double |
| 188 | TxReSample::mitchell(double x) |
| 189 | { |
| 190 | if (x < 0) x = -x; |
| 191 | if (x < 2.0) { |
| 192 | const double B = 1.0 / 3.0; |
| 193 | const double C = 1.0 / 3.0; |
| 194 | if (x < 1.0) { |
| 195 | x = (((12.0 - 9.0 * B - 6.0 * C) * (x * x * x)) |
| 196 | + ((-18.0 + 12.0 * B + 6.0 * C) * (x * x)) |
| 197 | + (6.0 - 2.0 * B)); |
| 198 | } else { |
| 199 | x = (((-1.0 * B - 6.0 * C) * (x * x * x)) |
| 200 | + ((6.0 * B + 30.0 * C) * (x * x)) |
| 201 | + ((-12.0 * B - 48.0 * C) * x) |
| 202 | + (8.0 * B + 24.0 * C)); |
| 203 | } |
| 204 | return (x / 6.0); |
| 205 | } |
| 206 | return 0.0; |
| 207 | } |
| 208 | |
| 209 | /* J. F. Kaiser and W. A. Reed |
| 210 | * Data smoothing using low-pass digital filters |
| 211 | * Rev. Sci. instrum. 48 (11), pp1447-1457, 1977 |
| 212 | */ |
| 213 | double |
| 214 | TxReSample::besselI0(double x) |
| 215 | { |
| 216 | /* zero-order modified bessel function of the first kind */ |
| 217 | const double eps_coeff = 1E-16; /* small enough */ |
| 218 | double xh, sum, pow, ds; |
| 219 | xh = 0.5 * x; |
| 220 | sum = 1.0; |
| 221 | pow = 1.0; |
| 222 | ds = 1.0; |
| 223 | int k = 0; |
| 224 | while (ds > sum * eps_coeff) { |
| 225 | k++; |
| 226 | pow *= (xh / k); |
| 227 | ds = pow * pow; |
| 228 | sum = sum + ds; |
| 229 | } |
| 230 | return sum; |
| 231 | } |
| 232 | |
| 233 | double |
| 234 | TxReSample::kaiser(double x) |
| 235 | { |
| 236 | const double alpha = 4.0; |
| 237 | const double half_window = 5.0; |
| 238 | const double ratio = x / half_window; |
| 239 | return sinc(x) * besselI0(alpha * sqrt(1 - ratio * ratio)) / besselI0(alpha); |
| 240 | } |
| 241 | |
| 242 | boolean |
| 243 | TxReSample::minify(uint8 **src, int *width, int *height, int ratio) |
| 244 | { |
| 245 | /* NOTE: src must be ARGB8888, ratio is the inverse representation */ |
| 246 | |
| 247 | #if 0 |
| 248 | if (!*src || ratio < 2) return 0; |
| 249 | |
| 250 | /* Box filtering. |
| 251 | * It would be nice to do Kaiser filtering. |
| 252 | * N64 uses narrow strip textures which makes it hard to filter effectively. |
| 253 | */ |
| 254 | |
| 255 | int x, y, x2, y2, offset, numtexel; |
| 256 | uint32 A, R, G, B, texel; |
| 257 | |
| 258 | int tmpwidth = *width / ratio; |
| 259 | int tmpheight = *height / ratio; |
| 260 | |
| 261 | uint8 *tmptex = (uint8*)malloc((tmpwidth * tmpheight) << 2); |
| 262 | |
| 263 | if (tmptex) { |
| 264 | numtexel = ratio * ratio; |
| 265 | for (y = 0; y < tmpheight; y++) { |
| 266 | offset = ratio * y * *width; |
| 267 | for (x = 0; x < tmpwidth; x++) { |
| 268 | A = R = G = B = 0; |
| 269 | for (y2 = 0; y2 < ratio; y2++) { |
| 270 | for (x2 = 0; x2 < ratio; x2++) { |
| 271 | texel = ((uint32*)*src)[offset + *width * y2 + x2]; |
| 272 | A += (texel >> 24); |
| 273 | R += ((texel >> 16) & 0x000000ff); |
| 274 | G += ((texel >> 8) & 0x000000ff); |
| 275 | B += (texel & 0x000000ff); |
| 276 | } |
| 277 | } |
| 278 | A = (A + ratio) / numtexel; |
| 279 | R = (R + ratio) / numtexel; |
| 280 | G = (G + ratio) / numtexel; |
| 281 | B = (B + ratio) / numtexel; |
| 282 | ((uint32*)tmptex)[y * tmpwidth + x] = ((A << 24) | (R << 16) | (G << 8) | B); |
| 283 | offset += ratio; |
| 284 | } |
| 285 | } |
| 286 | free(*src); |
| 287 | *src = tmptex; |
| 288 | *width = tmpwidth; |
| 289 | *height = tmpheight; |
| 290 | |
| 291 | DBG_INFO(80, L"minification ratio:%d -> %d x %d\n", ratio, *width, *height); |
| 292 | |
| 293 | return 1; |
| 294 | } |
| 295 | |
| 296 | DBG_INFO(80, L"Error: failed minification!\n"); |
| 297 | |
| 298 | return 0; |
| 299 | |
| 300 | #else |
| 301 | |
| 302 | if (!*src || ratio < 2) return 0; |
| 303 | |
| 304 | /* Image Resampling */ |
| 305 | |
| 306 | /* half width of filter window. |
| 307 | * NOTE: must be 1.0 or larger. |
| 308 | * |
| 309 | * kaiser-bessel 5, lanczos3 3, mitchell 2, gaussian 1.5, tent 1 |
| 310 | */ |
| 311 | double half_window = 5.0; |
| 312 | |
| 313 | int x, y, x2, y2, z; |
| 314 | double A, R, G, B; |
| 315 | uint32 texel; |
| 316 | |
| 317 | int tmpwidth = *width / ratio; |
| 318 | int tmpheight = *height / ratio; |
| 319 | |
| 320 | /* resampled destination */ |
| 321 | uint8 *tmptex = (uint8*)malloc((tmpwidth * tmpheight) << 2); |
| 322 | if (!tmptex) return 0; |
| 323 | |
| 324 | /* work buffer. single row */ |
| 325 | uint8 *workbuf = (uint8*)malloc(*width << 2); |
| 326 | if (!workbuf) { |
| 327 | free(tmptex); |
| 328 | return 0; |
| 329 | } |
| 330 | |
| 331 | /* prepare filter lookup table. only half width required for symetric filters. */ |
| 332 | double *weight = (double*)malloc((int)((half_window * ratio) * sizeof(double))); |
| 333 | if (!weight) { |
| 334 | free(tmptex); |
| 335 | free(workbuf); |
| 336 | return 0; |
| 337 | } |
| 338 | for (x = 0; x < half_window * ratio; x++) { |
| 339 | //weight[x] = tent((double)x / ratio) / ratio; |
| 340 | //weight[x] = gaussian((double)x / ratio) / ratio; |
| 341 | //weight[x] = lanczos3((double)x / ratio) / ratio; |
| 342 | //weight[x] = mitchell((double)x / ratio) / ratio; |
| 343 | weight[x] = kaiser((double)x / ratio) / ratio; |
| 344 | } |
| 345 | |
| 346 | /* linear convolution */ |
| 347 | for (y = 0; y < tmpheight; y++) { |
| 348 | for (x = 0; x < *width; x++) { |
| 349 | texel = ((uint32*)*src)[y * ratio * *width + x]; |
| 350 | A = (double)(texel >> 24) * weight[0]; |
| 351 | R = (double)((texel >> 16) & 0xff) * weight[0]; |
| 352 | G = (double)((texel >> 8) & 0xff) * weight[0]; |
| 353 | B = (double)((texel ) & 0xff) * weight[0]; |
| 354 | for (y2 = 1; y2 < half_window * ratio; y2++) { |
| 355 | z = y * ratio + y2; |
| 356 | if (z >= *height) z = *height - 1; |
| 357 | texel = ((uint32*)*src)[z * *width + x]; |
| 358 | A += (double)(texel >> 24) * weight[y2]; |
| 359 | R += (double)((texel >> 16) & 0xff) * weight[y2]; |
| 360 | G += (double)((texel >> 8) & 0xff) * weight[y2]; |
| 361 | B += (double)((texel ) & 0xff) * weight[y2]; |
| 362 | z = y * ratio - y2; |
| 363 | if (z < 0) z = 0; |
| 364 | texel = ((uint32*)*src)[z * *width + x]; |
| 365 | A += (double)(texel >> 24) * weight[y2]; |
| 366 | R += (double)((texel >> 16) & 0xff) * weight[y2]; |
| 367 | G += (double)((texel >> 8) & 0xff) * weight[y2]; |
| 368 | B += (double)((texel ) & 0xff) * weight[y2]; |
| 369 | } |
| 370 | if (A < 0) A = 0; else if (A > 255) A = 255; |
| 371 | if (R < 0) R = 0; else if (R > 255) R = 255; |
| 372 | if (G < 0) G = 0; else if (G > 255) G = 255; |
| 373 | if (B < 0) B = 0; else if (B > 255) B = 255; |
| 374 | ((uint32*)workbuf)[x] = (((uint32)A << 24) | ((uint32)R << 16) | ((uint32)G << 8) | (uint32)B); |
| 375 | } |
| 376 | for (x = 0; x < tmpwidth; x++) { |
| 377 | texel = ((uint32*)workbuf)[x * ratio]; |
| 378 | A = (double)(texel >> 24) * weight[0]; |
| 379 | R = (double)((texel >> 16) & 0xff) * weight[0]; |
| 380 | G = (double)((texel >> 8) & 0xff) * weight[0]; |
| 381 | B = (double)((texel ) & 0xff) * weight[0]; |
| 382 | for (x2 = 1; x2 < half_window * ratio; x2++) { |
| 383 | z = x * ratio + x2; |
| 384 | if (z >= *width) z = *width - 1; |
| 385 | texel = ((uint32*)workbuf)[z]; |
| 386 | A += (double)(texel >> 24) * weight[x2]; |
| 387 | R += (double)((texel >> 16) & 0xff) * weight[x2]; |
| 388 | G += (double)((texel >> 8) & 0xff) * weight[x2]; |
| 389 | B += (double)((texel ) & 0xff) * weight[x2]; |
| 390 | z = x * ratio - x2; |
| 391 | if (z < 0) z = 0; |
| 392 | texel = ((uint32*)workbuf)[z]; |
| 393 | A += (double)(texel >> 24) * weight[x2]; |
| 394 | R += (double)((texel >> 16) & 0xff) * weight[x2]; |
| 395 | G += (double)((texel >> 8) & 0xff) * weight[x2]; |
| 396 | B += (double)((texel ) & 0xff) * weight[x2]; |
| 397 | } |
| 398 | if (A < 0) A = 0; else if (A > 255) A = 255; |
| 399 | if (R < 0) R = 0; else if (R > 255) R = 255; |
| 400 | if (G < 0) G = 0; else if (G > 255) G = 255; |
| 401 | if (B < 0) B = 0; else if (B > 255) B = 255; |
| 402 | ((uint32*)tmptex)[y * tmpwidth + x] = (((uint32)A << 24) | ((uint32)R << 16) | ((uint32)G << 8) | (uint32)B); |
| 403 | } |
| 404 | } |
| 405 | |
| 406 | free(*src); |
| 407 | *src = tmptex; |
| 408 | free(weight); |
| 409 | free(workbuf); |
| 410 | *width = tmpwidth; |
| 411 | *height = tmpheight; |
| 412 | |
| 413 | DBG_INFO(80, L"minification ratio:%d -> %d x %d\n", ratio, *width, *height); |
| 414 | |
| 415 | return 1; |
| 416 | #endif |
| 417 | } |