1 /**************************************************************************
3 * Copyright 2013-2014 RAD Game Tools and Valve Software
4 * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 **************************************************************************/
27 // File: vogl_image_utils.cpp
28 #include "vogl_core.h"
29 #include "vogl_image_utils.h"
30 #include "vogl_console.h"
31 #include "vogl_resampler.h"
32 #include "vogl_threaded_resampler.h"
33 #include "vogl_strutils.h"
34 #include "vogl_file_utils.h"
35 #include "vogl_threading.h"
36 #include "vogl_miniz.h"
37 #include "vogl_jpge.h"
38 #include "vogl_cfile_stream.h"
39 #include "vogl_mipmapped_texture.h"
40 #include "vogl_buffer_stream.h"
41 #include "vogl_vector2d.h"
43 #include "vogl_stb_image.h"
45 #include "vogl_jpgd.h"
47 #include "vogl_pixel_format.h"
51 const float cInfinitePSNR = 999999.0f;
52 const uint VOGL_LARGEST_SUPPORTED_IMAGE_DIMENSION = 16384;
56 bool read_from_stream_stb(data_stream_serializer &serializer, image_u8 &img)
59 if (!serializer.read_entire_file(buf))
62 int x = 0, y = 0, n = 0;
63 unsigned char *pData = stbi_load_from_memory(buf.get_ptr(), buf.size_in_bytes(), &x, &y, &n, 4);
68 if ((x > (int)VOGL_LARGEST_SUPPORTED_IMAGE_DIMENSION) || (y > (int)VOGL_LARGEST_SUPPORTED_IMAGE_DIMENSION))
70 stbi_image_free(pData);
74 const bool has_alpha = ((n == 2) || (n == 4));
78 bool grayscale = true;
80 for (int py = 0; py < y; py++)
82 const color_quad_u8 *pSrc = reinterpret_cast<const color_quad_u8 *>(pData) + (py * x);
83 color_quad_u8 *pDst = img.get_scanline(py);
84 color_quad_u8 *pDst_end = pDst + x;
86 while (pDst != pDst_end)
88 color_quad_u8 c(*pSrc++);
92 if (!c.is_grayscale())
99 stbi_image_free(pData);
101 img.reset_comp_flags();
102 img.set_grayscale(grayscale);
103 img.set_component_valid(3, has_alpha);
108 bool read_from_stream_jpgd(data_stream_serializer &serializer, image_u8 &img)
111 if (!serializer.read_entire_file(buf))
114 int width = 0, height = 0, actual_comps = 0;
115 unsigned char *pSrc_img = jpgd::decompress_jpeg_image_from_memory(buf.get_ptr(), buf.size_in_bytes(), &width, &height, &actual_comps, 4);
119 if (math::maximum(width, height) > (int)VOGL_LARGEST_SUPPORTED_IMAGE_DIMENSION)
125 if (!img.grant_ownership(reinterpret_cast<color_quad_u8 *>(pSrc_img), width, height))
131 img.reset_comp_flags();
132 img.set_grayscale(actual_comps == 1);
133 img.set_component_valid(3, false);
138 bool read_from_stream(image_u8 &dest, data_stream_serializer &serializer, uint read_flags)
140 if (read_flags > cReadFlagsAllFlags)
146 if (!serializer.get_stream())
152 dynamic_string ext(serializer.get_name());
153 file_utils::get_extension(ext);
155 if ((ext == "jpg") || (ext == "jpeg"))
157 // Use my jpeg decoder by default because it supports progressive jpeg's.
158 if ((read_flags & cReadFlagForceSTB) == 0)
160 return image_utils::read_from_stream_jpgd(serializer, dest);
164 return image_utils::read_from_stream_stb(serializer, dest);
167 bool read_from_file(image_u8 &dest, const char *pFilename, uint read_flags)
169 if (read_flags > cReadFlagsAllFlags)
175 cfile_stream file_stream;
176 if (!file_stream.open(pFilename))
179 data_stream_serializer serializer(file_stream);
180 return read_from_stream(dest, serializer, read_flags);
183 bool write_to_file(const char *pFilename, const image_u8 &img, uint write_flags, int grayscale_comp_index)
185 if ((grayscale_comp_index < -1) || (grayscale_comp_index > 3))
191 if (!img.get_width())
197 dynamic_string ext(pFilename);
198 bool is_jpeg = false;
199 if (file_utils::get_extension(ext))
201 is_jpeg = ((ext == "jpg") || (ext == "jpeg"));
203 if ((ext != "png") && (ext != "bmp") && (ext != "tga") && (!is_jpeg))
205 console::error("vogl::image_utils::write_to_file: Can only write .BMP, .TGA, .PNG, or .JPG files!\n");
210 vogl::vector<uint8> temp;
211 uint num_src_chans = 0;
212 const void *pSrc_img = NULL;
216 write_flags |= cWriteFlagIgnoreAlpha;
219 if ((img.get_comp_flags() & pixel_format_helpers::cCompFlagGrayscale) || (write_flags & image_utils::cWriteFlagGrayscale))
221 VOGL_ASSERT(grayscale_comp_index < 4);
222 if (grayscale_comp_index > 3)
223 grayscale_comp_index = 3;
225 temp.resize(img.get_total_pixels());
227 for (uint y = 0; y < img.get_height(); y++)
229 const color_quad_u8 *pSrc = img.get_scanline(y);
230 const color_quad_u8 *pSrc_end = pSrc + img.get_width();
231 uint8 *pDst = &temp[y * img.get_width()];
233 if (img.get_comp_flags() & pixel_format_helpers::cCompFlagGrayscale)
235 while (pSrc != pSrc_end)
236 *pDst++ = (*pSrc++)[1];
238 else if (grayscale_comp_index < 0)
240 while (pSrc != pSrc_end)
241 *pDst++ = static_cast<uint8>((*pSrc++).get_luma());
245 while (pSrc != pSrc_end)
246 *pDst++ = (*pSrc++)[grayscale_comp_index];
253 else if ((!img.is_component_valid(3)) || (write_flags & cWriteFlagIgnoreAlpha))
255 temp.resize(img.get_total_pixels() * 3);
257 for (uint y = 0; y < img.get_height(); y++)
259 const color_quad_u8 *pSrc = img.get_scanline(y);
260 const color_quad_u8 *pSrc_end = pSrc + img.get_width();
261 uint8 *pDst = &temp[y * img.get_width() * 3];
263 while (pSrc != pSrc_end)
265 const color_quad_u8 c(*pSrc++);
281 pSrc_img = img.get_ptr();
284 bool success = false;
287 size_t png_image_size = 0;
288 void *pPNG_image_data = tdefl_write_image_to_png_file_in_memory_ex(pSrc_img, img.get_width(), img.get_height(), num_src_chans, &png_image_size, MZ_DEFAULT_LEVEL, MZ_FALSE);
289 if (!pPNG_image_data)
291 success = file_utils::write_buf_to_file(pFilename, pPNG_image_data, png_image_size);
292 mz_free(pPNG_image_data);
297 if (write_flags & cWriteFlagJPEGQualityLevelMask)
298 params.m_quality = math::clamp<uint>((write_flags & cWriteFlagJPEGQualityLevelMask) >> cWriteFlagJPEGQualityLevelShift, 1U, 100U);
299 params.m_two_pass_flag = (write_flags & cWriteFlagJPEGTwoPass) != 0;
300 params.m_no_chroma_discrim_flag = (write_flags & cWriteFlagJPEGNoChromaDiscrim) != 0;
302 if (write_flags & cWriteFlagJPEGH1V1)
303 params.m_subsampling = jpge::H1V1;
304 else if (write_flags & cWriteFlagJPEGH2V1)
305 params.m_subsampling = jpge::H2V1;
306 else if (write_flags & cWriteFlagJPEGH2V2)
307 params.m_subsampling = jpge::H2V2;
309 success = jpge::compress_image_to_jpeg_file(pFilename, img.get_width(), img.get_height(), num_src_chans, (const jpge::uint8 *)pSrc_img, params);
313 success = ((ext == "bmp" ? stbi_write_bmp : stbi_write_tga)(pFilename, img.get_width(), img.get_height(), num_src_chans, pSrc_img) == VOGL_TRUE);
318 bool write_to_file(const char *pFilename, const image_f &img, const vec4F &l, const vec4F &h, uint write_flags, int grayscale_comp_index)
320 image_u8 temp(img.get_width(), img.get_height());
323 for (uint i = 0; i < 4; i++)
324 scale[i] = (scale[i] != 0.0f) ? (255.0f / scale[i]) : 0;
326 for (uint y = 0; y < img.get_height(); y++)
328 for (uint x = 0; x < img.get_width(); x++)
330 const color_quad_f &o = img(x, y);
333 for (uint i = 0; i < 4; i++)
334 c[i] = static_cast<uint8>(math::clamp(math::float_to_int_nearest((o[i] - l[i]) * scale[i]), 0, 255));
340 return write_to_file(pFilename, temp, write_flags, grayscale_comp_index);
343 bool has_alpha(const image_u8 &img)
345 for (uint y = 0; y < img.get_height(); y++)
346 for (uint x = 0; x < img.get_width(); x++)
347 if (img(x, y).a < 255)
353 void renorm_normal_map(image_u8 &img)
355 for (uint y = 0; y < img.get_height(); y++)
357 for (uint x = 0; x < img.get_width(); x++)
359 color_quad_u8 &c = img(x, y);
360 if ((c.r == 128) && (c.g == 128) && (c.b == 128))
363 vec3F v(c.r, c.g, c.b);
367 v.clamp(-1.0f, 1.0f);
369 float length = v.length();
371 c.set(128, 128, 128, c.a);
372 else if (fabs(length - 1.0f) > .077f)
377 for (uint i = 0; i < 3; i++)
378 c[i] = static_cast<uint8>(math::clamp<float>(floor((v[i] + 1.0f) * .5f * 255.0f + .5f), 0.0f, 255.0f));
380 if ((c.r == 128) && (c.g == 128))
392 bool is_normal_map(const image_u8 &img, const char *pFilename)
396 uint num_invalid_pixels = 0;
398 // TODO: Derive better score from pixel mean, eigenvecs/vals
399 //vogl::vector<vec3F> pixels;
401 for (uint y = 0; y < img.get_height(); y++)
403 for (uint x = 0; x < img.get_width(); x++)
405 const color_quad_u8 &c = img(x, y);
409 num_invalid_pixels++;
412 else if ((c.r != 128) || (c.g != 128) || (c.b != 128))
414 vec3F v(c.r, c.g, c.b);
417 //pixels.push_back(v);
418 v.clamp(-1.0f, 1.0f);
420 float norm = v.norm();
421 if ((norm < 0.83f) || (norm > 1.29f))
422 num_invalid_pixels++;
427 score -= math::clamp(float(num_invalid_pixels) / (img.get_width() * img.get_height()) - .026f, 0.0f, 1.0f) * 5.0f;
431 dynamic_string str(pFilename);
434 if (str.contains("normal") || str.contains("local") || str.contains("nmap"))
437 if (str.contains("diffuse") || str.contains("spec") || str.contains("gloss"))
441 return score >= 0.0f;
444 bool resample_single_thread(const image_u8 &src, image_u8 &dst, const resample_params ¶ms)
446 const uint src_width = src.get_width();
447 const uint src_height = src.get_height();
449 if (math::maximum(src_width, src_height) > VOGL_RESAMPLER_MAX_DIMENSION)
451 printf("Image is too large!\n");
455 const int cMaxComponents = 4;
456 if (((int)params.m_num_comps < 1) || ((int)params.m_num_comps > (int)cMaxComponents))
459 const uint dst_width = params.m_dst_width;
460 const uint dst_height = params.m_dst_height;
462 if ((math::minimum(dst_width, dst_height) < 1) || (math::maximum(dst_width, dst_height) > VOGL_RESAMPLER_MAX_DIMENSION))
464 printf("Image is too large!\n");
468 if ((src_width == dst_width) && (src_height == dst_height))
475 dst.crop(params.m_dst_width, params.m_dst_height);
477 // Partial gamma correction looks better on mips. Set to 1.0 to disable gamma correction.
478 const float source_gamma = params.m_source_gamma; //1.75f;
480 float srgb_to_linear[256];
483 for (int i = 0; i < 256; ++i)
484 srgb_to_linear[i] = (float)pow(i * 1.0f / 255.0f, source_gamma);
487 const int linear_to_srgb_table_size = 8192;
488 unsigned char linear_to_srgb[linear_to_srgb_table_size];
490 const float inv_linear_to_srgb_table_size = 1.0f / linear_to_srgb_table_size;
491 const float inv_source_gamma = 1.0f / source_gamma;
495 for (int i = 0; i < linear_to_srgb_table_size; ++i)
497 int k = (int)(255.0f * pow(i * inv_linear_to_srgb_table_size, inv_source_gamma) + .5f);
502 linear_to_srgb[i] = (unsigned char)k;
506 Resampler *resamplers[cMaxComponents];
507 vogl::vector<float> samples[cMaxComponents];
509 resamplers[0] = vogl_new(Resampler, src_width, src_height, dst_width, dst_height,
510 params.m_wrapping ? Resampler::BOUNDARY_WRAP : Resampler::BOUNDARY_CLAMP, 0.0f, 1.0f,
511 params.m_pFilter, (Resampler::Contrib_List *)NULL, (Resampler::Contrib_List *)NULL, params.m_filter_scale, params.m_filter_scale, params.m_x_ofs, params.m_y_ofs);
512 samples[0].resize(src_width);
514 for (uint i = 1; i < params.m_num_comps; i++)
516 resamplers[i] = vogl_new(Resampler, src_width, src_height, dst_width, dst_height,
517 params.m_wrapping ? Resampler::BOUNDARY_WRAP : Resampler::BOUNDARY_CLAMP, 0.0f, 1.0f,
518 params.m_pFilter, resamplers[0]->get_clist_x(), resamplers[0]->get_clist_y(), params.m_filter_scale, params.m_filter_scale, params.m_x_ofs, params.m_y_ofs);
519 samples[i].resize(src_width);
524 for (uint src_y = 0; src_y < src_height; src_y++)
526 const color_quad_u8 *pSrc = src.get_scanline(src_y);
528 for (uint x = 0; x < src_width; x++)
530 for (uint c = 0; c < params.m_num_comps; c++)
532 const uint comp_index = params.m_first_comp + c;
533 const uint8 v = (*pSrc)[comp_index];
535 if (!params.m_srgb || (comp_index == 3))
536 samples[c][x] = v * (1.0f / 255.0f);
538 samples[c][x] = srgb_to_linear[v];
544 for (uint c = 0; c < params.m_num_comps; c++)
546 if (!resamplers[c]->put_line(&samples[c][0]))
548 for (uint i = 0; i < params.m_num_comps; i++)
549 vogl_delete(resamplers[i]);
557 for (c = 0; c < params.m_num_comps; c++)
559 const uint comp_index = params.m_first_comp + c;
561 const float *pOutput_samples = resamplers[c]->get_line();
562 if (!pOutput_samples)
565 const bool linear = !params.m_srgb || (comp_index == 3);
566 VOGL_ASSERT(dst_y < dst_height);
567 color_quad_u8 *pDst = dst.get_scanline(dst_y);
569 for (uint x = 0; x < dst_width; x++)
573 int c = (int)(255.0f * pOutput_samples[x] + .5f);
578 (*pDst)[comp_index] = (unsigned char)c;
582 int j = (int)(linear_to_srgb_table_size * pOutput_samples[x] + .5f);
585 else if (j >= linear_to_srgb_table_size)
586 j = linear_to_srgb_table_size - 1;
587 (*pDst)[comp_index] = linear_to_srgb[j];
593 if (c < params.m_num_comps)
600 for (uint i = 0; i < params.m_num_comps; i++)
601 vogl_delete(resamplers[i]);
606 bool resample(const image_f &src, image_f &dst, const resample_params ¶ms)
608 const uint src_width = src.get_width();
609 const uint src_height = src.get_height();
611 if (math::maximum(src_width, src_height) > VOGL_RESAMPLER_MAX_DIMENSION)
613 printf("Image is too large!\n");
617 const int cMaxComponents = 4;
618 if (((int)params.m_num_comps < 1) || ((int)params.m_num_comps > (int)cMaxComponents))
621 const uint dst_width = params.m_dst_width;
622 const uint dst_height = params.m_dst_height;
624 if ((math::minimum(dst_width, dst_height) < 1) || (math::maximum(dst_width, dst_height) > VOGL_RESAMPLER_MAX_DIMENSION))
626 printf("Image is too large!\n");
630 if ((src_width == dst_width) && (src_height == dst_height))
637 dst.crop(params.m_dst_width, params.m_dst_height);
639 Resampler *resamplers[cMaxComponents];
640 vogl::vector<float> samples[cMaxComponents];
642 resamplers[0] = vogl_new(Resampler, src_width, src_height, dst_width, dst_height,
643 params.m_wrapping ? Resampler::BOUNDARY_WRAP : Resampler::BOUNDARY_CLAMP, 0.0f, 1.0f,
644 params.m_pFilter, (Resampler::Contrib_List *)NULL, (Resampler::Contrib_List *)NULL, params.m_filter_scale, params.m_filter_scale, params.m_x_ofs, params.m_y_ofs);
645 samples[0].resize(src_width);
647 for (uint i = 1; i < params.m_num_comps; i++)
649 resamplers[i] = vogl_new(Resampler, src_width, src_height, dst_width, dst_height,
650 params.m_wrapping ? Resampler::BOUNDARY_WRAP : Resampler::BOUNDARY_CLAMP, 0.0f, 1.0f,
651 params.m_pFilter, resamplers[0]->get_clist_x(), resamplers[0]->get_clist_y(), params.m_filter_scale, params.m_filter_scale, params.m_x_ofs, params.m_y_ofs);
652 samples[i].resize(src_width);
657 for (uint src_y = 0; src_y < src_height; src_y++)
659 const color_quad_f *pSrc = src.get_scanline(src_y);
661 for (uint x = 0; x < src_width; x++)
663 for (uint c = 0; c < params.m_num_comps; c++)
665 const uint comp_index = params.m_first_comp + c;
666 samples[c][x] = (*pSrc)[comp_index];
672 for (uint c = 0; c < params.m_num_comps; c++)
674 if (!resamplers[c]->put_line(&samples[c][0]))
676 for (uint i = 0; i < params.m_num_comps; i++)
677 vogl_delete(resamplers[i]);
685 for (c = 0; c < params.m_num_comps; c++)
687 const uint comp_index = params.m_first_comp + c;
689 const float *pOutput_samples = resamplers[c]->get_line();
690 if (!pOutput_samples)
693 VOGL_ASSERT(dst_y < dst_height);
694 color_quad_f *pDst = dst.get_scanline(dst_y);
696 for (uint x = 0; x < dst_width; x++)
698 (*pDst)[comp_index] = pOutput_samples[x];
703 if (c < params.m_num_comps)
710 for (uint i = 0; i < params.m_num_comps; i++)
711 vogl_delete(resamplers[i]);
716 bool resample_multithreaded(const image_u8 &src, image_u8 &dst, const resample_params ¶ms)
718 const uint src_width = src.get_width();
719 const uint src_height = src.get_height();
721 if (math::maximum(src_width, src_height) > VOGL_RESAMPLER_MAX_DIMENSION)
723 printf("Image is too large!\n");
727 const int cMaxComponents = 4;
728 if (((int)params.m_num_comps < 1) || ((int)params.m_num_comps > (int)cMaxComponents))
731 const uint dst_width = params.m_dst_width;
732 const uint dst_height = params.m_dst_height;
734 if ((math::minimum(dst_width, dst_height) < 1) || (math::maximum(dst_width, dst_height) > VOGL_RESAMPLER_MAX_DIMENSION))
736 printf("Image is too large!\n");
740 if ((src_width == dst_width) && (src_height == dst_height))
748 // Partial gamma correction looks better on mips. Set to 1.0 to disable gamma correction.
749 const float source_gamma = params.m_source_gamma; //1.75f;
751 float srgb_to_linear[256];
754 for (int i = 0; i < 256; ++i)
755 srgb_to_linear[i] = (float)pow(i * 1.0f / 255.0f, source_gamma);
758 const int linear_to_srgb_table_size = 8192;
759 unsigned char linear_to_srgb[linear_to_srgb_table_size];
761 const float inv_linear_to_srgb_table_size = 1.0f / linear_to_srgb_table_size;
762 const float inv_source_gamma = 1.0f / source_gamma;
766 for (int i = 0; i < linear_to_srgb_table_size; ++i)
768 int k = (int)(255.0f * pow(i * inv_linear_to_srgb_table_size, inv_source_gamma) + .5f);
773 linear_to_srgb[i] = (unsigned char)k;
778 tp.init(g_number_of_processors - 1);
780 threaded_resampler resampler(tp);
781 threaded_resampler::params p;
782 p.m_src_width = src_width;
783 p.m_src_height = src_height;
784 p.m_dst_width = dst_width;
785 p.m_dst_height = dst_height;
786 p.m_sample_low = 0.0f;
787 p.m_sample_high = 1.0f;
788 p.m_boundary_op = params.m_wrapping ? Resampler::BOUNDARY_WRAP : Resampler::BOUNDARY_CLAMP;
789 p.m_Pfilter_name = params.m_pFilter;
790 p.m_filter_x_scale = params.m_filter_scale;
791 p.m_filter_y_scale = params.m_filter_scale;
792 p.m_x_ofs = params.m_x_ofs;
793 p.m_y_ofs = params.m_y_ofs;
795 uint resampler_comps = 4;
796 if (params.m_num_comps == 1)
798 p.m_fmt = threaded_resampler::cPF_Y_F32;
801 else if (params.m_num_comps <= 3)
802 p.m_fmt = threaded_resampler::cPF_RGBX_F32;
804 p.m_fmt = threaded_resampler::cPF_RGBA_F32;
806 vogl::vector<float> src_samples;
807 vogl::vector<float> dst_samples;
809 if (!src_samples.try_resize(src_width * src_height * resampler_comps))
812 if (!dst_samples.try_resize(dst_width * dst_height * resampler_comps))
815 p.m_pSrc_pixels = src_samples.get_ptr();
816 p.m_src_pitch = src_width * resampler_comps * sizeof(float);
817 p.m_pDst_pixels = dst_samples.get_ptr();
818 p.m_dst_pitch = dst_width * resampler_comps * sizeof(float);
820 for (uint src_y = 0; src_y < src_height; src_y++)
822 const color_quad_u8 *pSrc = src.get_scanline(src_y);
823 float *pDst = src_samples.get_ptr() + src_width * resampler_comps * src_y;
825 for (uint x = 0; x < src_width; x++)
827 for (uint c = 0; c < params.m_num_comps; c++)
829 const uint comp_index = params.m_first_comp + c;
830 const uint8 v = (*pSrc)[comp_index];
832 if (!params.m_srgb || (comp_index == 3))
833 pDst[c] = v * (1.0f / 255.0f);
835 pDst[c] = srgb_to_linear[v];
839 pDst += resampler_comps;
843 if (!resampler.resample(p))
848 if (!dst.crop(params.m_dst_width, params.m_dst_height))
851 for (uint dst_y = 0; dst_y < dst_height; dst_y++)
853 const float *pSrc = dst_samples.get_ptr() + dst_width * resampler_comps * dst_y;
854 color_quad_u8 *pDst = dst.get_scanline(dst_y);
856 for (uint x = 0; x < dst_width; x++)
858 color_quad_u8 dst(0, 0, 0, 255);
860 for (uint c = 0; c < params.m_num_comps; c++)
862 const uint comp_index = params.m_first_comp + c;
863 const float v = pSrc[c];
865 if ((!params.m_srgb) || (comp_index == 3))
867 int c = static_cast<int>(255.0f * v + .5f);
872 dst[comp_index] = (unsigned char)c;
876 int j = static_cast<int>(linear_to_srgb_table_size * v + .5f);
879 else if (j >= linear_to_srgb_table_size)
880 j = linear_to_srgb_table_size - 1;
881 dst[comp_index] = linear_to_srgb[j];
887 pSrc += resampler_comps;
894 bool resample(const image_u8 &src, image_u8 &dst, const resample_params ¶ms)
896 if ((params.m_multithreaded) && (g_number_of_processors > 1))
897 return resample_multithreaded(src, dst, params);
899 return resample_single_thread(src, dst, params);
902 bool compute_delta(image_u8 &dest, const image_u8 &a, const image_u8 &b, uint scale)
904 if ((a.get_width() != b.get_width()) || (a.get_height() != b.get_height()))
907 dest.crop(a.get_width(), b.get_height());
909 for (uint y = 0; y < a.get_height(); y++)
911 for (uint x = 0; x < a.get_width(); x++)
913 const color_quad_u8 &ca = a(x, y);
914 const color_quad_u8 &cb = b(x, y);
917 for (uint c = 0; c < 4; c++)
919 int d = (ca[c] - cb[c]) * scale + 128;
920 d = math::clamp(d, 0, 255);
921 cd[c] = static_cast<uint8>(d);
931 // See page 605 of http://www.cns.nyu.edu/pub/eero/wang03-reprint.pdf
932 // Image Quality Assessment: From Error Visibility to Structural Similarity
933 double compute_block_ssim(uint t, const uint8 *pX, const uint8 *pY)
937 for (uint i = 0; i < t; i++)
948 for (uint i = 0; i < t; i++)
950 var_x += math::square(pX[i] - ave_x);
951 var_y += math::square(pY[i] - ave_y);
954 var_x = sqrt(var_x / (t - 1));
955 var_y = sqrt(var_y / (t - 1));
957 double covar_xy = 0.0f;
958 for (uint i = 0; i < t; i++)
959 covar_xy += (pX[i] - ave_x) * (pY[i] - ave_y);
963 const double c1 = 6.5025; //(255*.01)^2
964 const double c2 = 58.5225; //(255*.03)^2
966 double n = (2.0f * ave_x * ave_y + c1) * (2.0f * covar_xy + c2);
967 double d = (ave_x * ave_x + ave_y * ave_y + c1) * (var_x * var_x + var_y * var_y + c2);
972 double compute_ssim(const image_u8 &a, const image_u8 &b, int channel_index)
975 uint8 sx[N * N], sy[N * N];
977 double total_ssim = 0.0f;
978 uint total_blocks = 0;
980 for (uint y = 0; y < a.get_height(); y += N)
982 for (uint x = 0; x < a.get_width(); x += N)
984 for (uint iy = 0; iy < N; iy++)
986 for (uint ix = 0; ix < N; ix++)
988 if (channel_index < 0)
989 sx[ix + iy * N] = (uint8)a.get_clamped(x + ix, y + iy).get_luma();
991 sx[ix + iy * N] = (uint8)a.get_clamped(x + ix, y + iy)[channel_index];
993 if (channel_index < 0)
994 sy[ix + iy * N] = (uint8)b.get_clamped(x + ix, y + iy).get_luma();
996 sy[ix + iy * N] = (uint8)b.get_clamped(x + ix, y + iy)[channel_index];
1000 double ssim = compute_block_ssim(N * N, sx, sy);
1004 //uint ssim_c = (uint)math::clamp<double>(ssim * 127.0f + 128.0f, 0, 255);
1005 //yimg(x / N, y / N).set(ssim_c, ssim_c, ssim_c, 255);
1012 //save_to_file_stb_or_miniz("ssim.tga", yimg, cWriteFlagGrayscale);
1014 return total_ssim / total_blocks;
1017 void print_ssim(const image_u8 &src_img, const image_u8 &dst_img)
1019 double y_ssim = compute_ssim(src_img, dst_img, -1);
1020 console::printf("Luma MSSIM: %f, Scaled [.8,1]: %f", y_ssim, (y_ssim - .8f) / .2f);
1022 double r_ssim = compute_ssim(src_img, dst_img, 0);
1023 console::printf(" R MSSIM: %f", r_ssim);
1025 double g_ssim = compute_ssim(src_img, dst_img, 1);
1026 console::printf(" G MSSIM: %f", g_ssim);
1028 double b_ssim = compute_ssim(src_img, dst_img, 2);
1029 console::printf(" B MSSIM: %f", b_ssim);
1032 void error_metrics::print(const char *pName) const
1034 if (mPeakSNR >= cInfinitePSNR)
1035 console::printf("%s Error: Max: %3u, Mean: %3.3f, MSE: %3.3f, RMSE: %3.3f, PSNR: Infinite", pName, mMax, mMean, mMeanSquared, mRootMeanSquared);
1037 console::printf("%s Error: Max: %3u, Mean: %3.3f, MSE: %3.3f, RMSE: %3.3f, PSNR: %3.3f, SSIM: %1.6f", pName, mMax, mMean, mMeanSquared, mRootMeanSquared, mPeakSNR, mSSIM);
1040 bool error_metrics::compute(const image_u8 &a, const image_u8 &b, uint first_channel, uint num_channels, bool average_component_error)
1042 //if ( (!a.get_width()) || (!b.get_height()) || (a.get_width() != b.get_width()) || (a.get_height() != b.get_height()) )
1045 const uint width = math::minimum(a.get_width(), b.get_width());
1046 const uint height = math::minimum(a.get_height(), b.get_height());
1048 VOGL_ASSERT((first_channel < 4U) && (first_channel + num_channels <= 4U));
1050 // Histogram approach due to Charles Bloom.
1052 utils::zero_object(hist);
1054 for (uint y = 0; y < height; y++)
1056 for (uint x = 0; x < width; x++)
1058 const color_quad_u8 &ca = a(x, y);
1059 const color_quad_u8 &cb = b(x, y);
1062 hist[labs(ca.get_luma() - cb.get_luma())]++;
1065 for (uint c = 0; c < num_channels; c++)
1066 hist[labs(ca[first_channel + c] - cb[first_channel + c])]++;
1072 double sum = 0.0f, sum2 = 0.0f;
1073 for (uint i = 0; i < 256; i++)
1078 mMax = math::maximum(mMax, i);
1080 double x = i * hist[i];
1086 // See http://bmrc.berkeley.edu/courseware/cs294/fall97/assignment/psnr.html
1087 double total_values = width * height;
1089 if (average_component_error)
1090 total_values *= math::clamp<uint>(num_channels, 1, 4);
1092 mMean = math::clamp<double>(sum / total_values, 0.0f, 255.0f);
1093 mMeanSquared = math::clamp<double>(sum2 / total_values, 0.0f, 255.0f * 255.0f);
1095 mRootMeanSquared = sqrt(mMeanSquared);
1097 if (!mRootMeanSquared)
1098 mPeakSNR = cInfinitePSNR;
1100 mPeakSNR = math::clamp<double>(log10(255.0f / mRootMeanSquared) * 20.0f, 0.0f, 500.0f);
1102 if (!a.has_rgb() || !b.has_rgb())
1105 mSSIM = compute_ssim(a, b, (num_channels != 1) ? -1 : first_channel);
1110 void print_image_metrics(const image_u8 &src_img, const image_u8 &dst_img)
1112 if ((!src_img.get_width()) || (!dst_img.get_height()) || (src_img.get_width() != dst_img.get_width()) || (src_img.get_height() != dst_img.get_height()))
1113 console::printf("print_image_metrics: Image resolutions don't match exactly (%ux%u) vs. (%ux%u)", src_img.get_width(), src_img.get_height(), dst_img.get_width(), dst_img.get_height());
1115 image_utils::error_metrics error_metrics;
1117 if (src_img.has_rgb() || dst_img.has_rgb())
1119 error_metrics.compute(src_img, dst_img, 0, 3, false);
1120 error_metrics.print("RGB Total ");
1122 error_metrics.compute(src_img, dst_img, 0, 3, true);
1123 error_metrics.print("RGB Average");
1125 error_metrics.compute(src_img, dst_img, 0, 0);
1126 error_metrics.print("Luma ");
1128 error_metrics.compute(src_img, dst_img, 0, 1);
1129 error_metrics.print("Red ");
1131 error_metrics.compute(src_img, dst_img, 1, 1);
1132 error_metrics.print("Green ");
1134 error_metrics.compute(src_img, dst_img, 2, 1);
1135 error_metrics.print("Blue ");
1138 if (src_img.has_alpha() || dst_img.has_alpha())
1140 error_metrics.compute(src_img, dst_img, 3, 1);
1141 error_metrics.print("Alpha ");
1145 static uint8 regen_z(uint x, uint y)
1147 float vx = math::clamp((x - 128.0f) * 1.0f / 127.0f, -1.0f, 1.0f);
1148 float vy = math::clamp((y - 128.0f) * 1.0f / 127.0f, -1.0f, 1.0f);
1149 float vz = sqrt(math::clamp(1.0f - vx * vx - vy * vy, 0.0f, 1.0f));
1151 vz = vz * 127.0f + 128.0f;
1158 int ib = math::float_to_int(vz);
1160 return static_cast<uint8>(math::clamp(ib, 0, 255));
1163 void convert_image(image_u8 &img, image_utils::conversion_type conv_type)
1167 case image_utils::cConversion_To_CCxY:
1169 img.set_comp_flags(static_cast<pixel_format_helpers::component_flags>(pixel_format_helpers::cCompFlagRValid | pixel_format_helpers::cCompFlagGValid | pixel_format_helpers::cCompFlagAValid | pixel_format_helpers::cCompFlagLumaChroma));
1172 case image_utils::cConversion_From_CCxY:
1174 img.set_comp_flags(static_cast<pixel_format_helpers::component_flags>(pixel_format_helpers::cCompFlagRValid | pixel_format_helpers::cCompFlagGValid | pixel_format_helpers::cCompFlagBValid));
1177 case image_utils::cConversion_To_xGxR:
1179 img.set_comp_flags(static_cast<pixel_format_helpers::component_flags>(pixel_format_helpers::cCompFlagGValid | pixel_format_helpers::cCompFlagAValid | pixel_format_helpers::cCompFlagNormalMap));
1182 case image_utils::cConversion_From_xGxR:
1184 img.set_comp_flags(static_cast<pixel_format_helpers::component_flags>(pixel_format_helpers::cCompFlagRValid | pixel_format_helpers::cCompFlagGValid | pixel_format_helpers::cCompFlagBValid | pixel_format_helpers::cCompFlagNormalMap));
1187 case image_utils::cConversion_To_xGBR:
1189 img.set_comp_flags(static_cast<pixel_format_helpers::component_flags>(pixel_format_helpers::cCompFlagGValid | pixel_format_helpers::cCompFlagBValid | pixel_format_helpers::cCompFlagAValid | pixel_format_helpers::cCompFlagNormalMap));
1192 case image_utils::cConversion_To_AGBR:
1194 img.set_comp_flags(static_cast<pixel_format_helpers::component_flags>(pixel_format_helpers::cCompFlagRValid | pixel_format_helpers::cCompFlagGValid | pixel_format_helpers::cCompFlagBValid | pixel_format_helpers::cCompFlagAValid | pixel_format_helpers::cCompFlagNormalMap));
1197 case image_utils::cConversion_From_xGBR:
1199 img.set_comp_flags(static_cast<pixel_format_helpers::component_flags>(pixel_format_helpers::cCompFlagRValid | pixel_format_helpers::cCompFlagGValid | pixel_format_helpers::cCompFlagBValid | pixel_format_helpers::cCompFlagNormalMap));
1202 case image_utils::cConversion_From_AGBR:
1204 img.set_comp_flags(static_cast<pixel_format_helpers::component_flags>(pixel_format_helpers::cCompFlagRValid | pixel_format_helpers::cCompFlagGValid | pixel_format_helpers::cCompFlagBValid | pixel_format_helpers::cCompFlagAValid | pixel_format_helpers::cCompFlagNormalMap));
1207 case image_utils::cConversion_XY_to_XYZ:
1209 img.set_comp_flags(static_cast<pixel_format_helpers::component_flags>(pixel_format_helpers::cCompFlagRValid | pixel_format_helpers::cCompFlagGValid | pixel_format_helpers::cCompFlagBValid | pixel_format_helpers::cCompFlagNormalMap));
1212 case cConversion_Y_To_A:
1214 img.set_comp_flags(static_cast<pixel_format_helpers::component_flags>(img.get_comp_flags() | pixel_format_helpers::cCompFlagAValid));
1217 case cConversion_A_To_RGBA:
1219 img.set_comp_flags(static_cast<pixel_format_helpers::component_flags>(pixel_format_helpers::cCompFlagRValid | pixel_format_helpers::cCompFlagGValid | pixel_format_helpers::cCompFlagBValid | pixel_format_helpers::cCompFlagAValid));
1222 case cConversion_Y_To_RGB:
1224 img.set_comp_flags(static_cast<pixel_format_helpers::component_flags>(pixel_format_helpers::cCompFlagRValid | pixel_format_helpers::cCompFlagGValid | pixel_format_helpers::cCompFlagBValid | pixel_format_helpers::cCompFlagGrayscale | (img.has_alpha() ? pixel_format_helpers::cCompFlagAValid : 0)));
1227 case cConversion_To_Y:
1229 img.set_comp_flags(static_cast<pixel_format_helpers::component_flags>(img.get_comp_flags() | pixel_format_helpers::cCompFlagGrayscale));
1239 for (uint y = 0; y < img.get_height(); y++)
1241 for (uint x = 0; x < img.get_width(); x++)
1243 color_quad_u8 src(img(x, y));
1248 case image_utils::cConversion_To_CCxY:
1250 color::RGB_to_YCC(dst, src);
1253 case image_utils::cConversion_From_CCxY:
1255 color::YCC_to_RGB(dst, src);
1258 case image_utils::cConversion_To_xGxR:
1266 case image_utils::cConversion_From_xGxR:
1270 // This is kinda iffy, we're assuming the image is a normal map here.
1271 dst.b = regen_z(src.a, src.g);
1275 case image_utils::cConversion_To_xGBR:
1283 case image_utils::cConversion_To_AGBR:
1291 case image_utils::cConversion_From_xGBR:
1299 case image_utils::cConversion_From_AGBR:
1307 case image_utils::cConversion_XY_to_XYZ:
1311 // This is kinda iffy, we're assuming the image is a normal map here.
1312 dst.b = regen_z(src.r, src.g);
1316 case image_utils::cConversion_Y_To_A:
1321 dst.a = static_cast<uint8>(src.get_luma());
1324 case image_utils::cConversion_Y_To_RGB:
1326 uint8 y = static_cast<uint8>(src.get_luma());
1333 case image_utils::cConversion_A_To_RGBA:
1341 case image_utils::cConversion_To_Y:
1343 uint8 y = static_cast<uint8>(src.get_luma());
1363 image_utils::conversion_type get_conversion_type(bool cooking, pixel_format fmt)
1365 image_utils::conversion_type conv_type = image_utils::cConversion_Invalid;
1371 case PIXEL_FMT_DXT5_CCxY:
1373 conv_type = image_utils::cConversion_To_CCxY;
1376 case PIXEL_FMT_DXT5_xGxR:
1378 conv_type = image_utils::cConversion_To_xGxR;
1381 case PIXEL_FMT_DXT5_xGBR:
1383 conv_type = image_utils::cConversion_To_xGBR;
1386 case PIXEL_FMT_DXT5_AGBR:
1388 conv_type = image_utils::cConversion_To_AGBR;
1402 conv_type = image_utils::cConversion_XY_to_XYZ;
1405 case PIXEL_FMT_DXT5_CCxY:
1407 conv_type = image_utils::cConversion_From_CCxY;
1410 case PIXEL_FMT_DXT5_xGxR:
1412 conv_type = image_utils::cConversion_From_xGxR;
1415 case PIXEL_FMT_DXT5_xGBR:
1417 conv_type = image_utils::cConversion_From_xGBR;
1420 case PIXEL_FMT_DXT5_AGBR:
1422 conv_type = image_utils::cConversion_From_AGBR;
1433 image_utils::conversion_type get_image_conversion_type_from_vogl_format(vogl_format fmt)
1437 case cCRNFmtDXT5_CCxY:
1438 return image_utils::cConversion_To_CCxY;
1439 case cCRNFmtDXT5_xGxR:
1440 return image_utils::cConversion_To_xGxR;
1441 case cCRNFmtDXT5_xGBR:
1442 return image_utils::cConversion_To_xGBR;
1443 case cCRNFmtDXT5_AGBR:
1444 return image_utils::cConversion_To_AGBR;
1448 return image_utils::cConversion_Invalid;
1451 double compute_std_dev(uint n, const color_quad_u8 *pPixels, uint first_channel, uint num_channels)
1459 for (uint i = 0; i < n; i++)
1461 const color_quad_u8 &cp = pPixels[i];
1465 uint l = cp.get_luma();
1471 for (uint c = 0; c < num_channels; c++)
1473 uint l = cp[first_channel + c];
1480 double w = math::maximum(1U, num_channels) * n;
1484 double var = sum2 - sum * sum;
1485 var = math::maximum<double>(var, 0.0f);
1490 uint8 *read_image_from_memory(const uint8 *pImage, int nSize, int *pWidth, int *pHeight, int *pActualComps, int req_comps, const char *pFilename)
1496 if ((req_comps < 1) || (req_comps > 4))
1499 mipmapped_texture tex;
1501 buffer_stream buf_stream(pImage, nSize);
1502 buf_stream.set_name(pFilename);
1503 data_stream_serializer serializer(buf_stream);
1505 if (!tex.read_from_stream(serializer))
1508 if (tex.is_packed())
1510 if (!tex.unpack_from_dxt(true))
1515 image_u8 *pImg = tex.get_level_image(0, 0, img);
1519 *pWidth = tex.get_width();
1520 *pHeight = tex.get_height();
1522 if (pImg->has_alpha())
1524 else if (pImg->is_grayscale())
1532 pDst = (uint8 *)vogl_malloc(tex.get_total_pixels() * sizeof(uint32));
1533 uint8 *pSrc = (uint8 *)pImg->get_ptr();
1534 memcpy(pDst, pSrc, tex.get_total_pixels() * sizeof(uint32));
1542 luma_img.convert_to_grayscale();
1546 pixel_packer packer(req_comps, 8);
1548 pDst = image_utils::pack_image(*pImg, packer, n);
1554 // http://lodev.org/cgtutor/fourier.html
1555 // TODO: Rewrite this - the license is too restrictive
1556 // N=width, M=height
1557 // image in memory is: [x][y][c], C bytes per pixel
1559 void FFT2D(int n, int m, bool inverse, const float *gRe, const float *gIm, float *GRe, float *GIm)
1561 int l2n = 0, p = 1; //l2n will become log_2(n)
1568 p = 1; //l2m will become log_2(m)
1576 n = 1 << l2n; //Make sure m and n will be powers of 2, otherwise you'll get in an infinite loop
1578 //Erase all history of this array
1579 for (int x = 0; x < m; x++) //for each column
1581 for (int y = 0; y < m; y++) //for each row
1583 for (int c = 0; c < static_cast<int>(C); c++) //for each color component
1585 GRe[C * m * x + C * y + c] = gRe[C * m * x + C * y + c];
1586 GIm[C * m * x + C * y + c] = gIm[C * m * x + C * y + c];
1591 //Bit reversal of each row
1593 for (int y = 0; y < m; y++) //for each row
1595 for (int c = 0; c < static_cast<int>(C); c++) //for each color component
1598 for (int i = 0; i < n - 1; i++)
1600 GRe[C * m * i + C * y + c] = gRe[C * m * j + C * y + c];
1601 GIm[C * m * i + C * y + c] = gIm[C * m * j + C * y + c];
1613 //Bit reversal of each column
1614 float tx = 0, ty = 0;
1615 for (int x = 0; x < n; x++) //for each column
1617 for (int c = 0; c < static_cast<int>(C); c++) //for each color component
1620 for (int i = 0; i < m - 1; i++)
1624 tx = GRe[C * m * x + C * i + c];
1625 ty = GIm[C * m * x + C * i + c];
1626 GRe[C * m * x + C * i + c] = GRe[C * m * x + C * j + c];
1627 GIm[C * m * x + C * i + c] = GIm[C * m * x + C * j + c];
1628 GRe[C * m * x + C * j + c] = tx;
1629 GIm[C * m * x + C * j + c] = ty;
1642 //Calculate the FFT of the columns
1643 for (int x = 0; x < n; x++) //for each column
1645 for (int c = 0; c < static_cast<int>(C); c++) //for each color component
1647 //This is the 1D FFT:
1651 for (int l = 0; l < l2n; l++)
1657 for (int j = 0; j < l1; j++)
1659 for (int i = j; i < n; i += l2)
1662 float t1 = u1 * GRe[C * m * x + C * i1 + c] - u2 * GIm[C * m * x + C * i1 + c];
1663 float t2 = u1 * GIm[C * m * x + C * i1 + c] + u2 * GRe[C * m * x + C * i1 + c];
1664 GRe[C * m * x + C * i1 + c] = GRe[C * m * x + C * i + c] - t1;
1665 GIm[C * m * x + C * i1 + c] = GIm[C * m * x + C * i + c] - t2;
1666 GRe[C * m * x + C * i + c] += t1;
1667 GIm[C * m * x + C * i + c] += t2;
1669 float z = u1 * ca - u2 * sa;
1670 u2 = u1 * sa + u2 * ca;
1673 sa = sqrtf((1.0f - ca) / 2.0f);
1676 ca = sqrtf((1.0f + ca) / 2.0f);
1681 //Calculate the FFT of the rows
1682 for (int y = 0; y < m; y++) //for each row
1684 for (int c = 0; c < static_cast<int>(C); c++) //for each color component
1686 //This is the 1D FFT:
1690 for (int l = 0; l < l2m; l++)
1696 for (int j = 0; j < l1; j++)
1698 for (int i = j; i < n; i += l2)
1701 float t1 = u1 * GRe[C * m * i1 + C * y + c] - u2 * GIm[C * m * i1 + C * y + c];
1702 float t2 = u1 * GIm[C * m * i1 + C * y + c] + u2 * GRe[C * m * i1 + C * y + c];
1703 GRe[C * m * i1 + C * y + c] = GRe[C * m * i + C * y + c] - t1;
1704 GIm[C * m * i1 + C * y + c] = GIm[C * m * i + C * y + c] - t2;
1705 GRe[C * m * i + C * y + c] += t1;
1706 GIm[C * m * i + C * y + c] += t2;
1708 float z = u1 * ca - u2 * sa;
1709 u2 = u1 * sa + u2 * ca;
1712 sa = sqrtf((1.0f - ca) / 2.0f);
1715 ca = sqrtf((1.0f + ca) / 2.0f);
1725 float inv_d = 1.0f / d;
1727 for (int x = 0; x < n; x++)
1729 for (int y = 0; y < m; y++)
1731 for (int c = 0; c < static_cast<int>(C); c++) //for every value of the buffers
1733 GRe[C * m * x + C * y + c] *= inv_d;
1734 GIm[C * m * x + C * y + c] *= inv_d;
1740 // N=width, M=height
1741 // image in memory is: [x][y][c], C bytes per pixel
1742 //void FFT2D(int n, int m, bool inverse, const float *gRe, const float *gIm, float *GRe, float *GIm)
1743 bool forward_fourier_transform(const image_u8 &src, image_u8 &dst, uint comp_mask)
1745 const uint width = src.get_width();
1746 const uint height = src.get_height();
1747 const uint half_width = src.get_width() / 2;
1748 const uint half_height = src.get_height() / 2;
1750 if (!math::is_power_of_2(width) || !math::is_power_of_2(height))
1756 dst.resize(width * 2, height);
1758 vogl::vector2D<float> re0(height, width), im0(height, width);
1759 vogl::vector2D<float> re1(height, width), im1(height, width);
1761 for (uint c = 0; c < 4; c++)
1763 if ((comp_mask & (1 << c)) == 0)
1766 for (uint y = 0; y < height; y++)
1767 for (uint x = 0; x < width; x++)
1768 re0(y, x) = src(x, y)[c];
1770 FFT2D<1>(width, height, false, re0.get_ptr(), im0.get_ptr(), re1.get_ptr(), im1.get_ptr());
1772 for (uint y = 0; y < height; y++)
1774 for (uint x = 0; x < width; x++)
1776 dst(x, y)[c] = static_cast<uint8>(math::clamp<float>(128.5f + re1.at_wrapped(y + half_height, x + half_width), 0.0f, 255.0f));
1777 dst(x + width, y)[c] = static_cast<uint8>(math::clamp<float>(128.5f + im1.at_wrapped(y + half_height, x + half_width), 0.0f, 255.0f));
1785 void gaussian_filter(image_u8 &dst, const image_u8 &orig_img, uint width_divisor, uint height_divisor, uint odd_filter_width, float sigma_sqr, bool wrapping)
1787 VOGL_ASSERT(odd_filter_width && (odd_filter_width & 1));
1788 odd_filter_width |= 1;
1790 vector2D<float> kernel(odd_filter_width, odd_filter_width);
1791 math::compute_gaussian_kernel(kernel.get_ptr(), odd_filter_width, odd_filter_width, sigma_sqr, math::cComputeGaussianFlagNormalize);
1793 const int dst_width = orig_img.get_width() / width_divisor;
1794 const int dst_height = orig_img.get_height() / height_divisor;
1796 const int H = odd_filter_width / 2;
1799 dst.crop(dst_width, dst_height);
1801 for (int oy = 0; oy < dst_height; oy++)
1803 for (int ox = 0; ox < dst_width; ox++)
1807 for (int yd = L; yd <= H; yd++)
1809 int y = oy * height_divisor + (height_divisor >> 1) + yd;
1811 for (int xd = L; xd <= H; xd++)
1813 int x = ox * width_divisor + (width_divisor >> 1) + xd;
1815 const color_quad_u8 &p = orig_img.get_clamped_or_wrapped(x, y, wrapping, wrapping);
1817 float w = kernel(xd + H, yd + H);
1825 dst(ox, oy).set(math::float_to_int_nearest(c[0]), math::float_to_int_nearest(c[1]), math::float_to_int_nearest(c[2]), math::float_to_int_nearest(c[3]));
1830 void convolution_filter(image_u8 &dst, const image_u8 &src, const float *pWeights, uint M, uint N, bool wrapping)
1832 if (((M & 1) == 0) || ((N & 1) == 0))
1838 const int width = src.get_width();
1839 const int height = src.get_height();
1841 dst.crop(width, height);
1843 const int HM = M / 2;
1844 const int HN = N / 2;
1846 VOGL_ASSERT((HM * 2 + 1) == static_cast<int>(M));
1847 VOGL_ASSERT((HN * 2 + 1) == static_cast<int>(N));
1849 for (int dst_y = 0; dst_y < height; dst_y++)
1851 for (int dst_x = 0; dst_x < width; dst_x++)
1855 for (int yd = -HM; yd <= HM; yd++)
1857 int src_y = wrapping ? math::posmod(dst_y + yd, height) : math::clamp<int>(dst_y + yd, 0, height - 1);
1859 for (int xd = -HN; xd <= HN; xd++)
1861 int src_x = wrapping ? math::posmod(dst_x + xd, width) : math::clamp<int>(dst_x + xd, 0, width - 1);
1862 float w = pWeights[(yd + HM) * N + (xd + HN)];
1864 const color_quad_u8 &src_c = src(src_x, src_y);
1865 c[0] += src_c[0] * w;
1866 c[1] += src_c[1] * w;
1867 c[2] += src_c[2] * w;
1868 c[3] += src_c[3] * w;
1872 dst(dst_x, dst_y).set(static_cast<int>(c[0] + .5f), static_cast<int>(c[1] + .5f), static_cast<int>(c[2] + .5f), static_cast<int>(c[3] + .5f));
1877 void convolution_filter(image_f &dst, const image_f &src, const float *pWeights, uint M, uint N, bool wrapping)
1879 if (((M & 1) == 0) || ((N & 1) == 0))
1885 const int width = src.get_width();
1886 const int height = src.get_height();
1888 dst.crop(width, height);
1890 const int HM = M / 2;
1891 const int HN = N / 2;
1893 VOGL_ASSERT((HM * 2 + 1) == static_cast<int>(M));
1894 VOGL_ASSERT((HN * 2 + 1) == static_cast<int>(N));
1896 for (int dst_y = 0; dst_y < height; dst_y++)
1898 for (int dst_x = 0; dst_x < width; dst_x++)
1902 for (int yd = -HM; yd <= HM; yd++)
1904 int src_y = wrapping ? math::posmod(dst_y + yd, height) : math::clamp<int>(dst_y + yd, 0, height - 1);
1906 for (int xd = -HN; xd <= HN; xd++)
1908 int src_x = wrapping ? math::posmod(dst_x + xd, width) : math::clamp<int>(dst_x + xd, 0, width - 1);
1909 float w = pWeights[(yd + HM) * N + (xd + HN)];
1911 const color_quad_u8 &src_c = src(src_x, src_y);
1912 c[0] += src_c[0] * w;
1913 c[1] += src_c[1] * w;
1914 c[2] += src_c[2] * w;
1915 c[3] += src_c[3] * w;
1919 dst(dst_x, dst_y).set(c[0], c[1], c[2], c[3]);
1924 } // namespace image_utils