├── LICENSE ├── README.md ├── base_resample.h ├── build ├── image-resampler.sln ├── image-resampler.vcxproj ├── image-resampler.vcxproj.filters └── image-resampler.vcxproj.user └── examples ├── Tutorial1.cpp └── bitmap.h /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 1998-2019, Joe Bertolami 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramenhut/single-header-image-resampler/8d86b191a28b2f8332d1b9f2817db74deaf14e44/README.md -------------------------------------------------------------------------------- /base_resample.h: -------------------------------------------------------------------------------- 1 | /* 2 | // 3 | // Copyright (c) 1998-2019 Joe Bertolami. All Right Reserved. 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are met: 7 | // 8 | // * Redistributions of source code must retain the above copyright notice, 9 | // this list of conditions and the following disclaimer. 10 | // 11 | // * Redistributions in binary form must reproduce the above copyright notice, 12 | // this list of conditions and the following disclaimer in the documentation 13 | // and/or other materials provided with the distribution. 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, CLUDG, BUT NOT LIMITED TO, THE 17 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | // ARE DISCLAIMED. NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | // LIABLE FOR ANY DIRECT, DIRECT, CIDENTAL, SPECIAL, EXEMPLARY, OR 20 | // CONSEQUENTIAL DAMAGES (CLUDG, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 21 | // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSESS TERRUPTION) 22 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER CONTRACT, STRICT 23 | // LIABILITY, OR TORT (CLUDG NEGLIGENCE OR OTHERWISE) ARISG ANY WAY OF THE 24 | // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | // 26 | // Additional Information: 27 | // 28 | // For more information, visit http://www.bertolami.com. 29 | // 30 | */ 31 | 32 | #ifndef __BASE_RESAMPLE_H__ 33 | #define __BASE_RESAMPLE_H__ 34 | 35 | #include 36 | #include 37 | #include 38 | 39 | #ifndef __BASE_TYPES_H__ 40 | #define __BASE_TYPES_H__ 41 | 42 | #if defined(WIN32) || defined(_WIN64) 43 | #define BASE_PLATFORM_WINDOWS 44 | #include "windows.h" 45 | #elif defined(__APPLE__) 46 | #define BASE_PLATFORM_MACOS 47 | #include "TargetConditionals.h" 48 | #include "ctype.h" 49 | #include "sys/types.h" 50 | #include "unistd.h" 51 | #else 52 | #error "Unsupported target platform detected." 53 | #endif 54 | 55 | namespace base { 56 | 57 | typedef int64_t int64; 58 | typedef int32_t int32; 59 | typedef int16_t int16; 60 | typedef int8_t int8; 61 | typedef uint64_t uint64; 62 | typedef uint32_t uint32; 63 | typedef uint16_t uint16; 64 | typedef uint8_t uint8; 65 | typedef float float32; 66 | typedef double float64; 67 | 68 | } // namespace base 69 | 70 | #endif // __BASE_TYPES_H__ 71 | 72 | namespace base { 73 | 74 | enum KernelType : uint8 { 75 | KernelTypeUnknown, 76 | KernelTypeNearest, 77 | KernelTypeAverage, 78 | KernelTypeBilinear, 79 | KernelTypeBicubic, 80 | KernelTypeMitchell, 81 | KernelTypeCardinal, 82 | KernelTypeBSpline, 83 | KernelTypeLanczos, 84 | KernelTypeLanczos2, 85 | KernelTypeLanczos3, 86 | KernelTypeLanczos4, 87 | KernelTypeLanczos5, 88 | KernelTypeCatmull, 89 | KernelTypeGaussian, 90 | }; 91 | 92 | enum KernelDirection : uint8 { 93 | KernelDirectionUnknown, 94 | KernelDirectionHorizontal, 95 | KernelDirectionVertical, 96 | }; 97 | 98 | #define BASE_PI (3.14159265359f) 99 | 100 | #define BLOCK_OFFSET_RGB24(ptr, width, x, y) (ptr + (3 * width) * y + 3 * x) 101 | 102 | inline int32 clip_range(int32 input, int32 low, int32 high) { 103 | return (input < low) ? low : (input > high) ? high : input; 104 | } 105 | 106 | /* Cubic weighing function 107 | 108 | Source: Mitchell, Netravali, "Reconstruction Filters in Computer Graphics" 109 | 1988 110 | 111 | Several of the popular cubic functions used for bi-directional image 112 | filtering can be generated as a simple weight function with two parameters. 113 | Thus, we use a weight function to generate the majority of our bicubic 114 | kernels. */ 115 | inline float32 bicubic_weight(float32 f_b, float32 f_c, float32 distance) { 116 | /* Our bicubic function is designed to provide feedback over a radius of 2.0 117 | * pixels. */ 118 | float32 distance2 = distance * distance; 119 | float32 distance3 = distance * distance * distance; 120 | float32 result = 0.0; 121 | 122 | if (distance < 1.0) { 123 | float32 cubic_term = (12.0 - 9.0 * f_b - 6.0 * f_c) * distance3; 124 | float32 quad_term = (-18.0 + 12.0 * f_b + 6.0 * f_c) * distance2; 125 | float32 const_term = (6.0 - 2.0 * f_b); 126 | result = (1.0f / 6.0f) * (cubic_term + quad_term + const_term); 127 | } 128 | 129 | else if (distance >= 1.0 && distance < 2.0) { 130 | float32 cubic_term = (-f_b - 6.0 * f_c) * distance3; 131 | float32 quad_term = (6.0 * f_b + 30.0 * f_c) * distance2; 132 | float32 lin_term = (-12.0 * f_b - 48.0 * f_c) * distance; 133 | float32 const_term = (8.0 * f_b + 24.0 * f_c); 134 | result = (1.0f / 6.0f) * (cubic_term + quad_term + lin_term + const_term); 135 | } 136 | 137 | if (result < 0) { 138 | result = 0.0; 139 | } 140 | 141 | return result; 142 | } 143 | 144 | /* Gaussian weighting function. Our simple gaussian distribution function with 145 | mean of zero and std-dev (d): 146 | 147 | 1.0 -(x^2 / (2 * d * d)) 148 | f(x) = --------------- * e 149 | 0.5 150 | d * (2 * Pi) 151 | */ 152 | 153 | inline float32 gaussian_weight(float32 distance, float32 radius) { 154 | float32 range = distance / radius; 155 | 156 | /* Gaussian function with mean = 0 and variance = 0.1. */ 157 | static const float32 variance = 0.1f; 158 | static const float32 stddev = sqrt(variance); 159 | static const float32 coeff = 1.0f / (stddev * sqrt(2.0 * BASE_PI)); 160 | return coeff * exp(-1.0f * (range * range) / (2.0 * variance)); 161 | } 162 | 163 | inline float32 sinc(float32 f_x) { 164 | if (0.0 == f_x) return 1.0; 165 | return sin(BASE_PI * f_x) / (BASE_PI * f_x); 166 | } 167 | 168 | inline float32 lanczos_weight(float32 f_n, float32 distance) { 169 | if (distance <= f_n) { 170 | return sinc(distance) * sinc(distance / f_n); 171 | } 172 | return 0.0f; 173 | } 174 | 175 | bool SampleKernelBilinearH(uint8* src, uint32 src_width, uint32 src_height, 176 | float32 f_x, float32 f_y, uint8* output) { 177 | if (!src || !src_width || !src_height || f_x < 0 || f_y < 0 || !output) { 178 | return false; 179 | } 180 | 181 | /* We do not bias our float coordinate by 0.5 because we wish 182 | to sample using the nearest 2 pixels to our coordinate. */ 183 | int32 sample_x = f_x; 184 | int32 sample_y = f_y; 185 | uint8* pixels[2] = {nullptr}; 186 | float32 f_delta = (float32)f_x - sample_x; 187 | 188 | /* compute our two pixels that will be interpolated together. */ 189 | for (uint32 i = 0; i < 2; i++) { 190 | int32 src_x = clip_range(sample_x + i, 0, src_width - 1); 191 | int32 src_y = clip_range(sample_y, 0, src_height - 1); 192 | 193 | pixels[i] = BLOCK_OFFSET_RGB24(src, src_width, src_x, src_y); 194 | } 195 | 196 | /* perform the interpolation of our lerp_pixels. */ 197 | output[0] = pixels[0][0] * (1.0f - f_delta) + pixels[1][0] * f_delta; 198 | output[1] = pixels[0][1] * (1.0f - f_delta) + pixels[1][1] * f_delta; 199 | output[2] = pixels[0][2] * (1.0f - f_delta) + pixels[1][2] * f_delta; 200 | 201 | return true; 202 | } 203 | 204 | bool SampleKernelBilinearV(uint8* src, uint32 src_width, uint32 src_height, 205 | float32 f_x, float32 f_y, uint8* output) { 206 | if (!src || !src_width || !src_height || f_x < 0 || f_y < 0 || !output) { 207 | return false; 208 | } 209 | 210 | /* We do not bias our float coordinate by 0.5 because we wish 211 | to sample using the nearest 2 pixels to our coordinate. */ 212 | int32 sample_x = f_x; 213 | int32 sample_y = f_y; 214 | uint8* pixels[2] = {nullptr}; 215 | float32 f_delta = (float32)f_y - sample_y; 216 | 217 | /* compute our two pixels that will be interpolated together. */ 218 | for (uint32 i = 0; i < 2; i++) { 219 | int32 src_x = clip_range(sample_x, 0, src_width - 1); 220 | int32 src_y = clip_range(sample_y + i, 0, src_height - 1); 221 | 222 | pixels[i] = BLOCK_OFFSET_RGB24(src, src_width, src_x, src_y); 223 | } 224 | 225 | /* perform the interpolation of our lerp_pixels. */ 226 | output[0] = pixels[0][0] * (1.0f - f_delta) + pixels[1][0] * f_delta; 227 | output[1] = pixels[0][1] * (1.0f - f_delta) + pixels[1][1] * f_delta; 228 | output[2] = pixels[0][2] * (1.0f - f_delta) + pixels[1][2] * f_delta; 229 | 230 | return true; 231 | } 232 | 233 | bool SampleKernelBilinear(uint8* src, uint32 src_width, uint32 src_height, 234 | KernelDirection direction, float32 f_x, float32 f_y, 235 | uint8* output) { 236 | switch (direction) { 237 | case KernelDirectionHorizontal: 238 | return SampleKernelBilinearH(src, src_width, src_height, f_x, f_y, 239 | output); 240 | case KernelDirectionVertical: 241 | return SampleKernelBilinearV(src, src_width, src_height, f_x, f_y, 242 | output); 243 | } 244 | 245 | return false; 246 | } 247 | 248 | bool SampleKernelBicubicH(uint8* src, uint32 src_width, uint32 src_height, 249 | float32 f_x, float32 f_y, float32 coeff_b, 250 | float32 coeff_c, uint8* output) { 251 | if (!src || !src_width || !src_height || f_x < 0 || f_y < 0 || !output) { 252 | return false; 253 | } 254 | 255 | float32 sample_count = 0; 256 | float32 total_samples[3] = {0}; 257 | 258 | /* Scan the kernel space adding up the bicubic weights and pixel values. */ 259 | for (int32 i = -2; i < 2; i++) { 260 | int32 i_x = (int32)f_x + i; 261 | int32 i_y = (int32)f_y; 262 | 263 | if (i_x < 0 || i_y < 0 || i_x > src_width - 1 || i_y > src_height - 1) { 264 | continue; 265 | } 266 | 267 | float32 x_delta = (float32)f_x - i_x; 268 | float32 distance = fabs(x_delta); 269 | float32 weight = bicubic_weight(coeff_b, coeff_c, distance); 270 | 271 | uint8* src_pixel = BLOCK_OFFSET_RGB24(src, src_width, i_x, i_y); 272 | 273 | /* accumulate bicubic weighted samples from the source. */ 274 | total_samples[0] += src_pixel[0] * weight; 275 | total_samples[1] += src_pixel[1] * weight; 276 | total_samples[2] += src_pixel[2] * weight; 277 | 278 | /* record the total weights of the sample for later normalization. */ 279 | sample_count += weight; 280 | } 281 | 282 | /* Normalize our bicubic sum back to the valid pixel range. */ 283 | float32 scale_factor = 1.0f / sample_count; 284 | output[0] = clip_range(scale_factor * total_samples[0], 0, 255); 285 | output[1] = clip_range(scale_factor * total_samples[1], 0, 255); 286 | output[2] = clip_range(scale_factor * total_samples[2], 0, 255); 287 | 288 | return true; 289 | } 290 | 291 | bool SampleKernelBicubicV(uint8* src, uint32 src_width, uint32 src_height, 292 | float32 f_x, float32 f_y, float32 coeff_b, 293 | float32 coeff_c, uint8* output) { 294 | if (!src || !src_width || !src_height || f_x < 0 || f_y < 0 || !output) { 295 | return false; 296 | } 297 | 298 | float32 sample_count = 0; 299 | float32 total_samples[3] = {0}; 300 | 301 | /* Scan the kernel space adding up the bicubic weights and pixel values. */ 302 | for (int32 i = -2; i < 2; i++) { 303 | int32 i_x = (int32)f_x; 304 | int32 i_y = (int32)f_y + i; 305 | 306 | if (i_x < 0 || i_y < 0 || i_x > src_width - 1 || i_y > src_height - 1) { 307 | continue; 308 | } 309 | 310 | float32 y_delta = (float32)f_y - i_y; 311 | float32 distance = fabs(y_delta); 312 | float32 weight = bicubic_weight(coeff_b, coeff_c, distance); 313 | uint8* src_pixel = BLOCK_OFFSET_RGB24(src, src_width, i_x, i_y); 314 | 315 | /* accumulate bicubic weighted samples from the source. */ 316 | total_samples[0] += src_pixel[0] * weight; 317 | total_samples[1] += src_pixel[1] * weight; 318 | total_samples[2] += src_pixel[2] * weight; 319 | 320 | /* record the total weights of the sample for later normalization. */ 321 | sample_count += weight; 322 | } 323 | 324 | /* Normalize our bicubic sum back to the valid pixel range. */ 325 | float32 scale_factor = 1.0f / sample_count; 326 | output[0] = clip_range(scale_factor * total_samples[0], 0, 255); 327 | output[1] = clip_range(scale_factor * total_samples[1], 0, 255); 328 | output[2] = clip_range(scale_factor * total_samples[2], 0, 255); 329 | 330 | return true; 331 | } 332 | 333 | bool SampleKernelBicubic(uint8* src, uint32 src_width, uint32 src_height, 334 | KernelDirection direction, float32 f_x, float32 f_y, 335 | float32 coeff_b, float32 coeff_c, uint8* output) { 336 | switch (direction) { 337 | case KernelDirectionHorizontal: 338 | return SampleKernelBicubicH(src, src_width, src_height, f_x, f_y, coeff_b, 339 | coeff_c, output); 340 | case KernelDirectionVertical: 341 | return SampleKernelBicubicV(src, src_width, src_height, f_x, f_y, coeff_b, 342 | coeff_c, output); 343 | } 344 | 345 | return false; 346 | } 347 | 348 | bool SampleKernelLanczosH(uint8* src, uint32 src_width, uint32 src_height, 349 | float32 f_x, float32 f_y, float32 coeff_a, 350 | uint8* output) { 351 | if (!src || !src_width || !src_height || f_x < 0 || f_y < 0 || !output) { 352 | return false; 353 | } 354 | 355 | int32 radius = coeff_a; 356 | float32 sample_count = 0; 357 | float32 total_samples[3] = {0}; 358 | 359 | /* Scan the kernel space adding up the bicubic weights and pixel values. */ 360 | for (int32 i = -radius; i < radius; i++) { 361 | int32 i_x = (int32)f_x + i; 362 | int32 i_y = (int32)f_y; 363 | 364 | if (i_x < 0 || i_y < 0 || i_x > src_width - 1 || i_y > src_height - 1) { 365 | continue; 366 | } 367 | 368 | float32 x_delta = (float32)f_x - i_x; 369 | float32 distance = fabs(x_delta); 370 | float32 weight = lanczos_weight(coeff_a, distance); 371 | 372 | uint8* src_pixel = BLOCK_OFFSET_RGB24(src, src_width, i_x, i_y); 373 | 374 | /* accumulate bicubic weighted samples from the source. */ 375 | total_samples[0] += src_pixel[0] * weight; 376 | total_samples[1] += src_pixel[1] * weight; 377 | total_samples[2] += src_pixel[2] * weight; 378 | 379 | /* record the total weights of the sample for later normalization. */ 380 | sample_count += weight; 381 | } 382 | 383 | /* Normalize our bicubic sum back to the valid pixel range. */ 384 | float32 scale_factor = 1.0f / sample_count; 385 | output[0] = clip_range(scale_factor * total_samples[0], 0, 255); 386 | output[1] = clip_range(scale_factor * total_samples[1], 0, 255); 387 | output[2] = clip_range(scale_factor * total_samples[2], 0, 255); 388 | 389 | return true; 390 | } 391 | 392 | bool SampleKernelLanczosV(uint8* src, uint32 src_width, uint32 src_height, 393 | float32 f_x, float32 f_y, float32 coeff_a, 394 | uint8* output) { 395 | if (!src || !src_width || !src_height || f_x < 0 || f_y < 0 || !output) { 396 | return false; 397 | } 398 | 399 | int32 radius = coeff_a; 400 | float32 sample_count = 0; 401 | float32 total_samples[3] = {0}; 402 | 403 | /* Scan the kernel space adding up the bicubic weights and pixel values. */ 404 | for (int32 i = -radius; i < radius; i++) { 405 | int32 i_x = (int32)f_x; 406 | int32 i_y = (int32)f_y + i; 407 | 408 | if (i_x < 0 || i_y < 0 || i_x > src_width - 1 || i_y > src_height - 1) { 409 | continue; 410 | } 411 | 412 | float32 y_delta = (float32)f_y - i_y; 413 | float32 distance = fabs(y_delta); 414 | float32 weight = lanczos_weight(coeff_a, distance); 415 | 416 | uint8* src_pixel = BLOCK_OFFSET_RGB24(src, src_width, i_x, i_y); 417 | 418 | /* accumulate bicubic weighted samples from the source. */ 419 | total_samples[0] += src_pixel[0] * weight; 420 | total_samples[1] += src_pixel[1] * weight; 421 | total_samples[2] += src_pixel[2] * weight; 422 | 423 | /* record the total weights of the sample for later normalization. */ 424 | sample_count += weight; 425 | } 426 | 427 | /* Normalize our bicubic sum back to the valid pixel range. */ 428 | float32 scale_factor = 1.0f / sample_count; 429 | output[0] = clip_range(scale_factor * total_samples[0], 0, 255); 430 | output[1] = clip_range(scale_factor * total_samples[1], 0, 255); 431 | output[2] = clip_range(scale_factor * total_samples[2], 0, 255); 432 | 433 | return true; 434 | } 435 | 436 | bool SampleKernelLanczos(uint8* src, uint32 src_width, uint32 src_height, 437 | KernelDirection direction, float32 f_x, float32 f_y, 438 | float32 coeff_a, uint8* output) { 439 | switch (direction) { 440 | case KernelDirectionHorizontal: 441 | return SampleKernelLanczosH(src, src_width, src_height, f_x, f_y, coeff_a, 442 | output); 443 | case KernelDirectionVertical: 444 | return SampleKernelLanczosV(src, src_width, src_height, f_x, f_y, coeff_a, 445 | output); 446 | } 447 | 448 | return false; 449 | } 450 | 451 | bool SampleKernelAverageH(uint8* src, uint32 src_width, uint32 src_height, 452 | float32 f_x, float32 f_y, float32 h_ratio, 453 | uint8* output) { 454 | if (!src || !src_width || !src_height || f_x < 0 || f_y < 0 || !output) { 455 | return false; 456 | } 457 | 458 | int32 radius = h_ratio + 1.0f; 459 | float32 max_distance = h_ratio; 460 | float32 sample_count = 0; 461 | float32 total_samples[3] = {0}; 462 | 463 | /* Scan the kernel space adding up the bicubic weights and pixel values. */ 464 | for (int32 i = -radius + 1; i <= radius; i++) { 465 | int32 i_x = (int32)f_x + i; 466 | int32 i_y = (int32)f_y; 467 | 468 | if (i_x < 0 || i_y < 0 || i_x > src_width - 1 || i_y > src_height - 1) { 469 | continue; 470 | } 471 | 472 | float32 x_delta = (float32)f_x - i_x; 473 | float32 distance = fabs(x_delta); 474 | float32 weight = 0.0f; 475 | 476 | uint8* src_pixel = BLOCK_OFFSET_RGB24(src, src_width, i_x, i_y); 477 | 478 | if (h_ratio >= 1.0) { 479 | distance = min(max_distance, distance); 480 | weight = 1.0f - distance / max_distance; 481 | } else { 482 | if (distance >= 0.5f - h_ratio) { 483 | weight = 1.0f - distance; 484 | } else { 485 | /* our average kernel is smaller than a pixel and is fully contained 486 | within the source pixel, so we simply copy the value out. */ 487 | output[0] = src_pixel[0]; 488 | output[1] = src_pixel[1]; 489 | output[2] = src_pixel[2]; 490 | return true; 491 | } 492 | } 493 | 494 | /* accumulate bicubic weighted samples from the source. */ 495 | total_samples[0] += src_pixel[0] * weight; 496 | total_samples[1] += src_pixel[1] * weight; 497 | total_samples[2] += src_pixel[2] * weight; 498 | 499 | /* record the total weights of the sample for later normalization. */ 500 | sample_count += weight; 501 | } 502 | 503 | /* Normalize our bicubic sum back to the valid pixel range. */ 504 | float32 scale_factor = 1.0f / sample_count; 505 | output[0] = clip_range(scale_factor * total_samples[0], 0, 255); 506 | output[1] = clip_range(scale_factor * total_samples[1], 0, 255); 507 | output[2] = clip_range(scale_factor * total_samples[2], 0, 255); 508 | 509 | return true; 510 | } 511 | 512 | bool SampleKernelAverageV(uint8* src, uint32 src_width, uint32 src_height, 513 | float32 f_x, float32 f_y, float32 v_ratio, 514 | uint8* output) { 515 | if (!src || !src_width || !src_height || f_x < 0 || f_y < 0 || !output) { 516 | return false; 517 | } 518 | 519 | int32 radius = v_ratio + 1.0f; 520 | float32 max_distance = v_ratio; 521 | float32 sample_count = 0; 522 | float32 total_samples[3] = {0}; 523 | 524 | /* Scan the kernel space adding up the bicubic weights and pixel values. */ 525 | for (int32 i = -radius + 1; i <= radius; i++) { 526 | int32 i_x = (int32)f_x; 527 | int32 i_y = (int32)f_y + i; 528 | 529 | if (i_x < 0 || i_y < 0 || i_x > src_width - 1 || i_y > src_height - 1) { 530 | continue; 531 | } 532 | 533 | float32 y_delta = (float32)f_y - i_y; 534 | float32 distance = fabs(y_delta); 535 | float32 weight = 0.0f; 536 | 537 | uint8* src_pixel = BLOCK_OFFSET_RGB24(src, src_width, i_x, i_y); 538 | 539 | if (v_ratio >= 1.0) { 540 | distance = min(max_distance, distance); 541 | weight = 1.0f - distance / max_distance; 542 | } else { 543 | if (distance >= 0.5f - v_ratio) { 544 | weight = 1.0f - distance; 545 | } else { 546 | /* our average kernel is smaller than a pixel and is fully contained 547 | within the source pixel, so we simply copy the value out. */ 548 | output[0] = src_pixel[0]; 549 | output[1] = src_pixel[1]; 550 | output[2] = src_pixel[2]; 551 | return true; 552 | } 553 | } 554 | 555 | /* accumulate bicubic weighted samples from the source. */ 556 | total_samples[0] += src_pixel[0] * weight; 557 | total_samples[1] += src_pixel[1] * weight; 558 | total_samples[2] += src_pixel[2] * weight; 559 | 560 | /* record the total weights of the sample for later normalization. */ 561 | sample_count += weight; 562 | } 563 | 564 | /* Normalize our bicubic sum back to the valid pixel range. */ 565 | float32 scale_factor = 1.0f / sample_count; 566 | output[0] = clip_range(scale_factor * total_samples[0], 0, 255); 567 | output[1] = clip_range(scale_factor * total_samples[1], 0, 255); 568 | output[2] = clip_range(scale_factor * total_samples[2], 0, 255); 569 | 570 | return true; 571 | } 572 | 573 | bool SampleKernelGaussianH(uint8* src, uint32 src_width, uint32 src_height, 574 | float32 f_x, float32 f_y, float32 h_ratio, 575 | uint8* output) { 576 | if (!src || !src_width || !src_height || f_x < 0 || f_y < 0 || !output) { 577 | return false; 578 | } 579 | 580 | int32 radius = h_ratio + 1.0f; 581 | float32 max_distance = h_ratio; 582 | float32 sample_count = 0; 583 | float32 total_samples[3] = {0}; 584 | 585 | /* Scan the kernel space adding up the bicubic weights and pixel values. */ 586 | for (int32 i = -radius; i <= radius; i++) { 587 | int32 i_x = (int32)f_x + i; 588 | int32 i_y = (int32)f_y; 589 | 590 | if (i_x < 0 || i_y < 0 || i_x > src_width - 1 || i_y > src_height - 1) { 591 | continue; 592 | } 593 | 594 | float32 x_delta = (float32)f_x - i_x; 595 | float32 distance = fabs(x_delta); 596 | float32 weight = gaussian_weight(distance, max_distance); 597 | 598 | uint8* src_pixel = BLOCK_OFFSET_RGB24(src, src_width, i_x, i_y); 599 | 600 | /* accumulate bicubic weighted samples from the source. */ 601 | total_samples[0] += src_pixel[0] * weight; 602 | total_samples[1] += src_pixel[1] * weight; 603 | total_samples[2] += src_pixel[2] * weight; 604 | 605 | /* record the total weights of the sample for later normalization. */ 606 | sample_count += weight; 607 | } 608 | 609 | /* Normalize our bicubic sum back to the valid pixel range. */ 610 | float32 scale_factor = 1.0f / sample_count; 611 | output[0] = clip_range(scale_factor * total_samples[0], 0, 255); 612 | output[1] = clip_range(scale_factor * total_samples[1], 0, 255); 613 | output[2] = clip_range(scale_factor * total_samples[2], 0, 255); 614 | 615 | return true; 616 | } 617 | 618 | bool SampleKernelGaussianV(uint8* src, uint32 src_width, uint32 src_height, 619 | float32 f_x, float32 f_y, float32 v_ratio, 620 | uint8* output) { 621 | if (!src || !src_width || !src_height || f_x < 0 || f_y < 0 || !output) { 622 | return false; 623 | } 624 | 625 | int32 radius = v_ratio + 1.0f; 626 | float32 max_distance = v_ratio; 627 | float32 sample_count = 0; 628 | float32 total_samples[3] = {0}; 629 | 630 | /* Scan the kernel space adding up the bicubic weights and pixel values. */ 631 | for (int32 i = -radius; i <= radius; i++) { 632 | int32 i_x = (int32)f_x; 633 | int32 i_y = (int32)f_y + i; 634 | 635 | if (i_x < 0 || i_y < 0 || i_x > src_width - 1 || i_y > src_height - 1) { 636 | continue; 637 | } 638 | 639 | float32 y_delta = (float32)f_y - i_y; 640 | float32 distance = fabs(y_delta); 641 | float32 weight = gaussian_weight(distance, max_distance); 642 | 643 | uint8* src_pixel = BLOCK_OFFSET_RGB24(src, src_width, i_x, i_y); 644 | 645 | /* accumulate bicubic weighted samples from the source. */ 646 | total_samples[0] += src_pixel[0] * weight; 647 | total_samples[1] += src_pixel[1] * weight; 648 | total_samples[2] += src_pixel[2] * weight; 649 | 650 | /* record the total weights of the sample for later normalization. */ 651 | sample_count += weight; 652 | } 653 | 654 | /* Normalize our bicubic sum back to the valid pixel range. */ 655 | float32 scale_factor = 1.0f / sample_count; 656 | output[0] = clip_range(scale_factor * total_samples[0], 0, 255); 657 | output[1] = clip_range(scale_factor * total_samples[1], 0, 255); 658 | output[2] = clip_range(scale_factor * total_samples[2], 0, 255); 659 | 660 | return true; 661 | } 662 | 663 | bool SampleKernelAverage(uint8* src, uint32 src_width, uint32 src_height, 664 | KernelDirection direction, float32 f_x, float32 f_y, 665 | float32 h_ratio, float32 v_ratio, uint8* output) { 666 | switch (direction) { 667 | case KernelDirectionHorizontal: 668 | return SampleKernelAverageH(src, src_width, src_height, f_x, f_y, h_ratio, 669 | output); 670 | case KernelDirectionVertical: 671 | return SampleKernelAverageV(src, src_width, src_height, f_x, f_y, v_ratio, 672 | output); 673 | } 674 | 675 | return false; 676 | } 677 | 678 | bool SampleKernelGaussian(uint8* src, uint32 src_width, uint32 src_height, 679 | KernelDirection direction, float32 f_x, float32 f_y, 680 | float32 h_ratio, float32 v_ratio, uint8* output) { 681 | switch (direction) { 682 | case KernelDirectionHorizontal: 683 | return SampleKernelGaussianH(src, src_width, src_height, f_x, f_y, 684 | h_ratio, output); 685 | case KernelDirectionVertical: 686 | return SampleKernelGaussianV(src, src_width, src_height, f_x, f_y, 687 | v_ratio, output); 688 | } 689 | 690 | return false; 691 | } 692 | 693 | bool SampleKernelNearest(uint8* src, uint32 src_width, uint32 src_height, 694 | float32 f_x, float32 f_y, uint8* output) { 695 | if (!src || !src_width || !src_height || !output) { 696 | return false; 697 | } 698 | 699 | int32 i_x = (int32)(f_x + 0.5f); 700 | int32 i_y = (int32)(f_y + 0.5f); 701 | 702 | /* Floating point pixel coordinates are pixel-center based. Thus, a coordinate 703 | of (0,0) refers to the center of the first pixel in an image, and a 704 | coordinate of (0.5,0) refers to the border between the first and second 705 | pixels. */ 706 | i_x = clip_range(i_x, 0, src_width - 1); 707 | i_y = clip_range(i_y, 0, src_height - 1); 708 | 709 | /* Sample our pixel and write it to the output buffer. */ 710 | memcpy(output, BLOCK_OFFSET_RGB24(src, src_width, i_x, i_y), 3); 711 | 712 | return true; 713 | } 714 | 715 | bool SampleKernel(uint8* src, uint32 src_width, uint32 src_height, 716 | KernelDirection direction, float32 f_x, float32 f_y, 717 | KernelType type, float32 h_ratio, float32 v_ratio, 718 | uint8* output) { 719 | switch (type) { 720 | case KernelTypeNearest: 721 | return SampleKernelNearest(src, src_width, src_height, f_x, f_y, output); 722 | case KernelTypeBilinear: 723 | return SampleKernelBilinear(src, src_width, src_height, direction, f_x, 724 | f_y, output); 725 | case KernelTypeBicubic: 726 | return SampleKernelBicubic(src, src_width, src_height, direction, f_x, 727 | f_y, 0, 1, output); 728 | case KernelTypeCatmull: 729 | return SampleKernelBicubic(src, src_width, src_height, direction, f_x, 730 | f_y, 0, 0.5, output); 731 | case KernelTypeMitchell: 732 | return SampleKernelBicubic(src, src_width, src_height, direction, f_x, 733 | f_y, 1.0f / 3.0f, 1.0f / 3.0f, output); 734 | case KernelTypeCardinal: 735 | return SampleKernelBicubic(src, src_width, src_height, direction, f_x, 736 | f_y, 0.0f, 0.75f, output); 737 | case KernelTypeBSpline: 738 | return SampleKernelBicubic(src, src_width, src_height, direction, f_x, 739 | f_y, 1, 0, output); 740 | case KernelTypeLanczos: 741 | return SampleKernelLanczos(src, src_width, src_height, direction, f_x, 742 | f_y, 1, output); 743 | case KernelTypeLanczos2: 744 | return SampleKernelLanczos(src, src_width, src_height, direction, f_x, 745 | f_y, 2, output); 746 | case KernelTypeLanczos3: 747 | return SampleKernelLanczos(src, src_width, src_height, direction, f_x, 748 | f_y, 3, output); 749 | case KernelTypeLanczos4: 750 | return SampleKernelLanczos(src, src_width, src_height, direction, f_x, 751 | f_y, 4, output); 752 | case KernelTypeLanczos5: 753 | return SampleKernelLanczos(src, src_width, src_height, direction, f_x, 754 | f_y, 5, output); 755 | case KernelTypeAverage: 756 | return SampleKernelAverage(src, src_width, src_height, direction, f_x, 757 | f_y, h_ratio, v_ratio, output); 758 | case KernelTypeGaussian: 759 | return SampleKernelGaussian(src, src_width, src_height, direction, f_x, 760 | f_y, h_ratio, v_ratio, output); 761 | } 762 | 763 | return false; 764 | } 765 | 766 | /* Resamples a 24 bit RGB image using a bilinear, bicubic, or lanczos filter. */ 767 | bool ResampleImage24(uint8* src, uint32 src_width, uint32 src_height, 768 | uint8* dst, uint32 dst_width, uint32 dst_height, 769 | KernelType type, ::std::string* errors = nullptr) { 770 | if (!src || !dst || !src_width || !src_height || !dst_width || !dst_height || 771 | type == KernelTypeUnknown) { 772 | if (errors) { 773 | *errors = "Invalid parameter passed to ResampleImage24."; 774 | } 775 | return false; 776 | } 777 | 778 | uint32 src_row_pitch = 3 * src_width; 779 | uint32 dst_row_pitch = 3 * dst_width; 780 | uint32 buffer_size = dst_row_pitch * src_height; 781 | uint32 dst_image_size = dst_row_pitch * dst_height; 782 | 783 | if (src_width == dst_width && src_height == dst_height) { 784 | /* no resampling needed, simply copy the image over. */ 785 | memcpy(dst, src, dst_image_size); 786 | return true; 787 | } 788 | 789 | /* allocate a temporary buffer to hold our horizontal pass output. We're 790 | using unique_ptr rather than vector because we want a fast and smart way 791 | to allocate very large buffers without initialization. */ 792 | ::std::unique_ptr buffer(new uint8[buffer_size]); 793 | 794 | /* Prepare to perform our resample. This is perhaps the most important part of 795 | our resizer -- the calculation of our image ratios. These ratios are 796 | responsible for mapping between our integer pixel locations of the source 797 | image and our float sub-pixel coordinates within the source image that 798 | represent a reflection of our destination pixels. 799 | 800 | For a source 2x1 image and a destination 4x1 image: 801 | 802 | +------------+------------+ 803 | Src: | 0 | 1 | 804 | +------------+------------+ 805 | | | 806 | 0.0 1.0 807 | | | 808 | +---+---+---+---+ 809 | Dst: | 0 | 1 | 2 | 3 | 810 | +---+---+---+---+ 811 | 812 | o: Note that the center of the first and last pixels in both our src and dst 813 | images line up with our float edges of 0.0 and 1.0. 814 | 815 | o: Our sub-pixel interpolated coordinates will always be >= 0 and <= 816 | src_width. 817 | 818 | o: Thus the src pixel coordinate of our final destination pixel will always 819 | be src_width - 1. 820 | */ 821 | 822 | /* ratios define our kernel size and resample factor. */ 823 | float32 h_ratio = 824 | (1 == dst_width ? 1.0f : ((float32)src_width - 1) / (dst_width - 1)); 825 | float32 v_ratio = 826 | (1 == dst_height ? 1.0f : ((float32)src_height - 1) / (dst_height - 1)); 827 | 828 | /* horizontal sampling first. */ 829 | for (uint32 j = 0; j < src_height; j++) 830 | for (uint32 i = 0; i < dst_width; i++) { 831 | uint8* output = BLOCK_OFFSET_RGB24(buffer.get(), dst_width, i, j); 832 | 833 | /* Determine the sub-pixel location of our *target* (i,j) coordinate, in 834 | the space of our source image. */ 835 | float32 f_x = (float32)i * h_ratio; 836 | float32 f_y = (float32)j; 837 | 838 | if (!SampleKernel(src, src_width, src_height, KernelDirectionHorizontal, 839 | f_x, f_y, type, h_ratio, v_ratio, output)) { 840 | if (errors) { 841 | *errors = "Failure during horizontal resample operation."; 842 | } 843 | return false; 844 | } 845 | } 846 | 847 | /* vertical sampling next. */ 848 | for (uint32 j = 0; j < dst_height; j++) 849 | for (uint32 i = 0; i < dst_width; i++) { 850 | uint8* output = BLOCK_OFFSET_RGB24(dst, dst_width, i, j); 851 | 852 | /* Determine the sub-pixel location of our *target* (i,j) coordinate, in 853 | the space of our temp image. */ 854 | float32 f_x = (float32)i; 855 | float32 f_y = (float32)j * v_ratio; 856 | 857 | if (!SampleKernel(buffer.get(), dst_width, src_height, 858 | KernelDirectionVertical, f_x, f_y, type, h_ratio, 859 | v_ratio, output)) { 860 | if (errors) { 861 | *errors = "Failure during vertical resample operation."; 862 | } 863 | return false; 864 | } 865 | } 866 | 867 | return true; 868 | } 869 | 870 | } // namespace base 871 | 872 | #endif // __BASE_RESAMPLE_H__ 873 | -------------------------------------------------------------------------------- /build/image-resampler.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.156 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "image-resampler", "image-resampler.vcxproj", "{A3E81D7E-113A-4167-AA72-B145BA7E643C}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {A3E81D7E-113A-4167-AA72-B145BA7E643C}.Debug|x64.ActiveCfg = Debug|x64 17 | {A3E81D7E-113A-4167-AA72-B145BA7E643C}.Debug|x64.Build.0 = Debug|x64 18 | {A3E81D7E-113A-4167-AA72-B145BA7E643C}.Debug|x86.ActiveCfg = Debug|Win32 19 | {A3E81D7E-113A-4167-AA72-B145BA7E643C}.Debug|x86.Build.0 = Debug|Win32 20 | {A3E81D7E-113A-4167-AA72-B145BA7E643C}.Release|x64.ActiveCfg = Release|x64 21 | {A3E81D7E-113A-4167-AA72-B145BA7E643C}.Release|x64.Build.0 = Release|x64 22 | {A3E81D7E-113A-4167-AA72-B145BA7E643C}.Release|x86.ActiveCfg = Release|Win32 23 | {A3E81D7E-113A-4167-AA72-B145BA7E643C}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {C52F04C5-4C22-4FF6-8EAC-9CA9BA8F86E4} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /build/image-resampler.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {A3E81D7E-113A-4167-AA72-B145BA7E643C} 24 | imageresampler 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v142 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v142 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v142 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | $(ProjectName)-x64 74 | 75 | 76 | $(ProjectName)-x64 77 | 78 | 79 | 80 | Level3 81 | Disabled 82 | true 83 | true 84 | 85 | 86 | Console 87 | 88 | 89 | 90 | 91 | Level3 92 | Disabled 93 | true 94 | true 95 | 96 | 97 | Console 98 | 99 | 100 | 101 | 102 | Level3 103 | MaxSpeed 104 | true 105 | true 106 | true 107 | true 108 | 109 | 110 | Console 111 | true 112 | true 113 | 114 | 115 | 116 | 117 | Level3 118 | MaxSpeed 119 | true 120 | true 121 | true 122 | true 123 | 124 | 125 | Console 126 | true 127 | true 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /build/image-resampler.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | 26 | 27 | Source Files 28 | 29 | 30 | -------------------------------------------------------------------------------- /build/image-resampler.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/Tutorial1.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | // 3 | // Copyright (c) 1998-2019 Joe Bertolami. All Right Reserved. 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are met: 7 | // 8 | // * Redistributions of source code must retain the above copyright notice, 9 | // this list of conditions and the following disclaimer. 10 | // 11 | // * Redistributions in binary form must reproduce the above copyright notice, 12 | // this list of conditions and the following disclaimer in the documentation 13 | // and/or other materials provided with the distribution. 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, CLUDG, BUT NOT LIMITED TO, THE 17 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | // ARE DISCLAIMED. NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | // LIABLE FOR ANY DIRECT, DIRECT, CIDENTAL, SPECIAL, EXEMPLARY, OR 20 | // CONSEQUENTIAL DAMAGES (CLUDG, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 21 | // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSESS TERRUPTION) 22 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER CONTRACT, STRICT 23 | // LIABILITY, OR TORT (CLUDG NEGLIGENCE OR OTHERWISE) ARISG ANY WAY OF THE 24 | // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | // 26 | // Additional Information: 27 | // 28 | // For more information, visit http://www.bertolami.com. 29 | // 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | #include "bitmap.h" 36 | #include "../base_resample.h" 37 | 38 | #define MB (1024 * 1024) 39 | 40 | using namespace base; 41 | using ::std::make_unique; 42 | using ::std::string; 43 | using ::std::vector; 44 | 45 | void PrintUsage(const char* programName) { 46 | printf("Usage: %s [options]\n", programName); 47 | printf(" --src [source filename] \tSource image to load.\n"); 48 | printf(" --dst [dest filename] \tDestination image to save.\n"); 49 | printf(" --width [integer] \t\tSets the width of the output image.\n"); 50 | printf(" --height [integer] \t\tSets the height of the output image.\n"); 51 | printf(" --kernel [integer] \t\tSpecifies the kernel type.\n"); 52 | printf(" 1: nearest\n"); 53 | printf(" 2: average\n"); 54 | printf(" 3: bilinear\n"); 55 | printf(" 4: bicubic\n"); 56 | printf(" 5: Mitchell-Netravali\n"); 57 | printf(" 6: Cardinal\n"); 58 | printf(" 7: B-Spline\n"); 59 | printf(" 8: Lanczos\n"); 60 | printf(" 9: Lanczos-2\n"); 61 | printf(" 10: Lanczos-3\n"); 62 | printf(" 11: Lanczos-4\n"); 63 | printf(" 12: Lanczos-5\n"); 64 | printf(" 13: Catrom\n"); 65 | printf(" 14: Gaussian\n"); 66 | } 67 | 68 | int main(int argc, char** argv) { 69 | string errors; 70 | string src_filename; 71 | string dst_filename; 72 | uint32 src_width = 0; 73 | uint32 src_height = 0; 74 | vector src_image; 75 | uint32 output_width = 0; 76 | uint32 output_height = 0; 77 | KernelType kernel = KernelTypeBilinear; 78 | 79 | if (argc <= 1) { 80 | PrintUsage(argv[0]); 81 | return 0; 82 | } 83 | 84 | for (int i = 1; i < argc; i++) { 85 | char* optBegin = argv[i]; 86 | for (int j = 0; j < 2; j++) (optBegin[0] == '-') ? optBegin++ : optBegin; 87 | 88 | switch (optBegin[0]) { 89 | case 's': 90 | src_filename = argv[++i]; 91 | break; 92 | case 'd': 93 | dst_filename = argv[++i]; 94 | break; 95 | case 'w': 96 | output_width = atoi(argv[++i]); 97 | break; 98 | case 'h': 99 | output_height = atoi(argv[++i]); 100 | break; 101 | case 'k': 102 | kernel = (KernelType)atoi(argv[++i]); 103 | break; 104 | } 105 | } 106 | 107 | if (src_filename.empty() || dst_filename.empty()) { 108 | printf("You must specify a valid source and destination filename.\n"); 109 | return 0; 110 | } 111 | 112 | if (!output_width || !output_height || output_width > MB || 113 | output_height > MB) { 114 | printf("Output dimensions must be in the range of (0, 1,048,576].\n"); 115 | return 0; 116 | } 117 | 118 | /* Load our bitmap file into memory. */ 119 | if (!::base::LoadBitmapImage(src_filename, &src_image, &src_width, 120 | &src_height, &errors)) { 121 | printf("Error! %s\n", errors.c_str()); 122 | return 0; 123 | } 124 | 125 | printf("Loaded %s with dimensions %i, %i.\n", src_filename.c_str(), src_width, 126 | src_height); 127 | 128 | /* Attempt our resample operation. */ 129 | vector dst_image(3 * output_width * output_height); 130 | 131 | printf( 132 | "Resampling image using kernel %i and destination dimensions %i, %i.\n", 133 | kernel, output_width, output_height); 134 | 135 | if (!ResampleImage24(&src_image.at(0), src_width, src_height, 136 | &dst_image.at(0), output_width, output_height, kernel)) { 137 | printf("Error resampling image!\n"); 138 | return 0; 139 | } 140 | 141 | /* Save our resampled image out to a bitmap file. */ 142 | if (!::base::SaveBitmapImage(dst_filename, &dst_image, output_width, 143 | output_height, &errors)) { 144 | printf("Error! %s\n", errors.c_str()); 145 | return 0; 146 | } 147 | 148 | printf("Saved %s to disk.\n", dst_filename.c_str()); 149 | 150 | return 0; 151 | } -------------------------------------------------------------------------------- /examples/bitmap.h: -------------------------------------------------------------------------------- 1 | /* 2 | // 3 | // Copyright (c) 1998-2019 Joe Bertolami. All Right Reserved. 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are met: 7 | // 8 | // * Redistributions of source code must retain the above copyright notice, 9 | // this list of conditions and the following disclaimer. 10 | // 11 | // * Redistributions in binary form must reproduce the above copyright notice, 12 | // this list of conditions and the following disclaimer in the documentation 13 | // and/or other materials provided with the distribution. 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, CLUDG, BUT NOT LIMITED TO, THE 17 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | // ARE DISCLAIMED. NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | // LIABLE FOR ANY DIRECT, DIRECT, CIDENTAL, SPECIAL, EXEMPLARY, OR 20 | // CONSEQUENTIAL DAMAGES (CLUDG, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 21 | // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSESS TERRUPTION) 22 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER CONTRACT, STRICT 23 | // LIABILITY, OR TORT (CLUDG NEGLIGENCE OR OTHERWISE) ARISG ANY WAY OF THE 24 | // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | // 26 | // Additional Information: 27 | // 28 | // For more information, visit http://www.bertolami.com. 29 | // 30 | */ 31 | 32 | #ifndef __BITMAP_H__ 33 | #define __BITMAP_H__ 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | using ::std::ifstream; 41 | using ::std::ofstream; 42 | using ::std::string; 43 | using ::std::vector; 44 | 45 | #ifndef __BASE_TYPES_H__ 46 | #define __BASE_TYPES_H__ 47 | 48 | #if defined(WIN32) || defined(_WIN64) 49 | #define BASE_PLATFORM_WINDOWS 50 | #include "windows.h" 51 | #elif defined(__APPLE__) 52 | #define BASE_PLATFORM_MACOS 53 | #include "TargetConditionals.h" 54 | #include "ctype.h" 55 | #include "sys/types.h" 56 | #include "unistd.h" 57 | #else 58 | #error "Unsupported target platform detected." 59 | #endif 60 | 61 | namespace base { 62 | 63 | typedef int64_t int64; 64 | typedef int32_t int32; 65 | typedef int16_t int16; 66 | typedef int8_t int8; 67 | typedef uint64_t uint64; 68 | typedef uint32_t uint32; 69 | typedef uint16_t uint16; 70 | typedef uint8_t uint8; 71 | typedef float float32; 72 | typedef double float64; 73 | 74 | } // namespace base 75 | 76 | #endif // __BASE_TYPES_H__ 77 | 78 | namespace base { 79 | 80 | #ifndef BI_RGB 81 | #define BI_RGB (0) 82 | #endif 83 | 84 | #pragma pack(push) 85 | #pragma pack(2) 86 | 87 | typedef struct PTCX_BITMAP_FILE_HEADER { 88 | uint16 type; 89 | uint32 size; 90 | uint16 reserved[2]; 91 | uint32 off_bits; 92 | 93 | } PTCX_BITMAP_FILE_HEADER; 94 | 95 | typedef struct PTCX_BITMAP_INFO_HEADER { 96 | uint32 size; 97 | int32 width; 98 | int32 height; 99 | uint16 planes; 100 | uint16 bit_count; 101 | uint32 compression; 102 | uint32 size_image; 103 | int32 x_pels_per_meter; 104 | int32 y_pels_per_meter; 105 | uint32 clr_used; 106 | uint32 clr_important; 107 | 108 | } PTCX_BITMAP_INFO_HEADER; 109 | 110 | #pragma pack(pop) 111 | 112 | inline uint32 greater_multiple(uint32 value, uint32 multiple) { 113 | uint32 mod = value % multiple; 114 | 115 | if (0 != mod) { 116 | value += multiple - mod; 117 | } 118 | 119 | return value; 120 | } 121 | 122 | /* Loads a 24 bit RGB bitmap file into a vector. */ 123 | bool LoadBitmapImage(const string &filename, vector *output, 124 | uint32 *width, uint32 *height, string *error = nullptr) { 125 | if (filename.empty() || !output) { 126 | if (error) { 127 | *error = "Invalid inputs to LoadBitmapImage."; 128 | } 129 | return false; 130 | } 131 | 132 | uint32 bytes_read = 0; 133 | PTCX_BITMAP_INFO_HEADER bih; 134 | PTCX_BITMAP_FILE_HEADER bmf_header; 135 | 136 | ifstream input_file(filename, ::std::ios::in | ::std::ios::binary); 137 | 138 | if (!input_file.read((char *)&bmf_header, sizeof(PTCX_BITMAP_FILE_HEADER))) { 139 | if (error) { 140 | *error = "Failed to read bitmap file header"; 141 | } 142 | return false; 143 | } 144 | 145 | if (!input_file.read((char *)&bih, sizeof(PTCX_BITMAP_INFO_HEADER))) { 146 | if (error) { 147 | *error = "Failed to read bitmap info header.\n"; 148 | } 149 | return false; 150 | } 151 | 152 | if (bih.bit_count != 24) { 153 | if (error) { 154 | *error = "Unsupported bitmap data format.\n"; 155 | } 156 | return false; 157 | } 158 | 159 | uint32 image_row_pitch = bih.width * 3; 160 | uint32 image_size = image_row_pitch * bih.height; 161 | 162 | output->resize(image_size); 163 | *width = bih.width; 164 | *height = bih.height; 165 | 166 | /* The BMP format requires each scanline to be 32 bit aligned, so we insert 167 | padding if necessary. */ 168 | uint32 scanline_padding = 169 | greater_multiple(bih.width * 3, 4) - (bih.width * 3); 170 | 171 | uint32 row_stride = bih.width * 3; 172 | 173 | for (uint32 i = 0; i < bih.height; i++) { 174 | uint32 y_offset = i * row_stride; 175 | uint8 *dest_row = &output->at(y_offset); 176 | 177 | if (!input_file.read((char *)dest_row, row_stride)) { 178 | if (error) { 179 | *error = "Abrupt error reading file.\n"; 180 | } 181 | return false; 182 | } 183 | 184 | uint32 dummy = 0; /* Padding will always be < 4 bytes. */ 185 | if (!input_file.read((char *)&dummy, scanline_padding)) { 186 | if (error) { 187 | *error = "Abrupt error reading file.\n"; 188 | } 189 | return false; 190 | } 191 | 192 | /* Swap the R and B channels (as BMP stores its data in BGR). */ 193 | for (uint32 j = 0; j < bih.width; j++) { 194 | uint32 x_offset = y_offset + j * 3; 195 | uint8 temp_channel = output->at(x_offset + 0); 196 | output->at(x_offset + 0) = output->at(x_offset + 2); 197 | output->at(x_offset + 2) = temp_channel; 198 | } 199 | } 200 | 201 | return true; 202 | } 203 | 204 | bool SaveBitmapImage(const string &filename, vector *input, uint32 width, 205 | uint32 height, string *error = nullptr) { 206 | if (filename.empty() || input->empty()) { 207 | if (error) { 208 | *error = "Invalid inputs to SaveBitmapImage."; 209 | } 210 | return false; 211 | } 212 | 213 | uint32 bytes_written = 0; 214 | uint32 total_image_bytes = (3 * width) * height; 215 | uint32 header_size = 216 | sizeof(PTCX_BITMAP_FILE_HEADER) + sizeof(PTCX_BITMAP_INFO_HEADER); 217 | 218 | PTCX_BITMAP_FILE_HEADER bmf_header = {0x4D42, // BM 219 | header_size + total_image_bytes, 0, 0, 220 | header_size}; 221 | 222 | PTCX_BITMAP_INFO_HEADER bih = {sizeof(PTCX_BITMAP_INFO_HEADER), 223 | width, 224 | height, 225 | 1, 226 | 24, 227 | BI_RGB, 228 | total_image_bytes, 229 | 0, 230 | 0, 231 | 0, 232 | 0}; 233 | 234 | ofstream output_file(filename, ::std::ios::out | ::std::ios::binary); 235 | 236 | if (!output_file.write((char *)&bmf_header, 237 | sizeof(PTCX_BITMAP_FILE_HEADER))) { 238 | if (error) { 239 | *error = "Failed to write bitmap file header"; 240 | } 241 | return false; 242 | } 243 | 244 | if (!output_file.write((char *)&bih, sizeof(PTCX_BITMAP_INFO_HEADER))) { 245 | if (error) { 246 | *error = "Failed to write bitmap info header.\n"; 247 | } 248 | return false; 249 | } 250 | 251 | uint32 image_row_pitch = bih.width * 3; 252 | uint32 image_size = image_row_pitch * bih.height; 253 | uint32 row_stride = bih.width * 3; 254 | 255 | /* The BMP format requires each scanline to be 32 bit aligned, so we insert 256 | padding if necessary. */ 257 | uint32 scanline_padding = 258 | greater_multiple(bih.width * 3, 4) - (bih.width * 3); 259 | 260 | for (uint32 i = 0; i < bih.height; i++) { 261 | uint32 y_offset = i * row_stride; 262 | uint8 *src_row = &input->at(y_offset); 263 | 264 | /* Swap the R and B channels (as BMP stores its data in BGR). */ 265 | for (uint32 j = 0; j < bih.width; j++) { 266 | uint32 x_offset = y_offset + j * 3; 267 | uint8 temp_channel = input->at(x_offset + 0); 268 | input->at(x_offset + 0) = input->at(x_offset + 2); 269 | input->at(x_offset + 2) = temp_channel; 270 | } 271 | 272 | if (!output_file.write((char *)src_row, row_stride)) { 273 | if (error) { 274 | *error = "Abrupt error writing file.\n"; 275 | } 276 | return false; 277 | } 278 | 279 | uint32 dummy = 0; /* Padding will always be < 4 bytes. */ 280 | if (!output_file.write((char *)&dummy, scanline_padding)) { 281 | if (error) { 282 | *error = "Abrupt error writing file.\n"; 283 | } 284 | return false; 285 | } 286 | } 287 | 288 | return true; 289 | } 290 | 291 | } // namespace base 292 | 293 | #endif // __BITMAP_H__ --------------------------------------------------------------------------------