1 // This code is in the public domain -- castanyo@yahoo.es
3 //#include <nvcore/Containers.h>
4 //#include <nvcore/Ptr.h>
6 //#include <nvmath/Color.h>
11 #include "FloatImage.h"
18 static int iround(float f)
23 static int ifloor(float f)
28 static float frac(float f)
33 static int mirror(int x, int w)
44 FloatImage::FloatImage() : m_width(0), m_height(0),
45 m_componentNum(0), m_count(0), m_mem(NULL)
49 /*/// Ctor. Init from image.
50 FloatImage::FloatImage(const Image * img) : m_width(0), m_height(0),
51 m_componentNum(0), m_count(0), m_mem(NULL)
58 FloatImage::~FloatImage()
64 /// Init the floating point image from a regular image.
65 void FloatImage::initFrom(const Image * img)
69 allocate(4, img->width(), img->height());
71 float * red_channel = channel(0);
72 float * green_channel = channel(1);
73 float * blue_channel = channel(2);
74 float * alpha_channel = channel(3);
76 const uint count = m_width * m_height;
77 for(uint i = 0; i < count; i++) {
78 Color32 pixel = img->pixel(i);
79 red_channel[i] = float(pixel.r) / 255.0f;
80 green_channel[i] = float(pixel.g) / 255.0f;
81 blue_channel[i] = float(pixel.b) / 255.0f;
82 alpha_channel[i] = float(pixel.a) / 255.0f;
88 /// Convert the floating point image to a regular image.
89 Image * FloatImage::createImage(uint base_component/*= 0*/, uint num/*= 4*/) const
92 // nvCheck(base_component + num <= m_componentNum);
94 AutoPtr<Image> img(new Image());
95 img->allocate(m_width, m_height);
97 const uint size = m_width * m_height;
98 for(uint i = 0; i < size; i++) {
101 uint8 rgba[4]= {0, 0, 0, 0xff};
103 for(c = 0; c < num; c++) {
104 float f = m_mem[size * (base_component + c) + i];
105 rgba[c] = nv::clamp(int(255.0f * f), 0, 255);
108 img->pixel(i) = Color32(rgba[0], rgba[1], rgba[2], rgba[3]);
111 return img.release();
115 /// Convert the floating point image to a regular image. Correct gamma of rgb, but not alpha.
116 Image * FloatImage::createImageGammaCorrect(float gamma/*= 2.2f*/) const
118 // nvCheck(m_componentNum == 4);
120 AutoPtr<Image> img(new Image());
121 img->allocate(m_width, m_height);
123 const float * rChannel = this->channel(0);
124 const float * gChannel = this->channel(1);
125 const float * bChannel = this->channel(2);
126 const float * aChannel = this->channel(3);
128 const uint size = m_width * m_height;
129 for(uint i = 0; i < size; i++)
131 const uint8 r = nv::clamp(int(255.0f * pow(rChannel[i], 1.0f/gamma)), 0, 255);
132 const uint8 g = nv::clamp(int(255.0f * pow(gChannel[i], 1.0f/gamma)), 0, 255);
133 const uint8 b = nv::clamp(int(255.0f * pow(bChannel[i], 1.0f/gamma)), 0, 255);
134 const uint8 a = nv::clamp(int(255.0f * aChannel[i]), 0, 255);
136 img->pixel(i) = Color32(r, g, b, a);
139 return img.release();
143 /// Allocate a 2d float image of the given format and the given extents.
144 void FloatImage::allocate(uint c, uint w, uint h)
146 // nvCheck(m_mem == NULL);
151 // m_mem = reinterpret_cast<float *>(nv::mem::malloc(m_count * sizeof(float)));
152 m_mem = reinterpret_cast<float *>(malloc(m_count * sizeof(float)));
155 /// Free the image, but don't clear the members.
156 void FloatImage::free()
158 // nvCheck(m_mem != NULL);
159 // nv::mem::free( reinterpret_cast<void *>(m_mem) );
164 void FloatImage::clear(float f/*=0.0f*/)
166 for(uint i = 0; i < m_count; i++) {
172 void FloatImage::normalize(uint base_component)
174 // nvCheck(base_component + 3 <= m_componentNum);
176 float * xChannel = this->channel(base_component + 0);
177 float * yChannel = this->channel(base_component + 1);
178 float * zChannel = this->channel(base_component + 2);
180 const uint size = m_width * m_height;
181 for(uint i = 0; i < size; i++) {
183 Vector3 normal(xChannel[i], yChannel[i], zChannel[i]);
184 normal = normalizeSafe(normal, Vector3(zero), 0.0f);
186 xChannel[i] = normal.x();
187 yChannel[i] = normal.y();
188 zChannel[i] = normal.z();
193 void FloatImage::packNormals(uint base_component)
195 scaleBias(base_component, 3, 0.5f, 1.0f);
198 void FloatImage::expandNormals(uint base_component)
200 scaleBias(base_component, 3, 2, -0.5);
203 void FloatImage::scaleBias(uint base_component, uint num, float scale, float bias)
205 const uint size = m_width * m_height;
207 for(uint c = 0; c < num; c++) {
208 float * ptr = this->channel(base_component + c);
210 for(uint i = 0; i < size; i++) {
211 ptr[i] = scale * (ptr[i] + bias);
216 /// Clamp the elements of the image.
217 void FloatImage::clamp(float low, float high)
219 for(uint i = 0; i < m_count; i++) {
220 m_mem[i] = ::clamp(m_mem[i], low, high);
224 /// From gamma to linear space.
225 void FloatImage::toLinear(uint base_component, uint num, float gamma /*= 2.2f*/)
227 exponentiate(base_component, num, gamma);
230 /// From linear to gamma space.
231 void FloatImage::toGamma(uint base_component, uint num, float gamma /*= 2.2f*/)
233 exponentiate(base_component, num, 1.0f/gamma);
236 /// Exponentiate the elements of the image.
237 void FloatImage::exponentiate(uint base_component, uint num, float power)
239 const uint size = m_width * m_height;
241 for(uint c = 0; c < num; c++) {
242 float * ptr = this->channel(base_component + c);
244 for(uint i = 0; i < size; i++) {
245 ptr[i] = pow(ptr[i], power);
250 float FloatImage::sampleNearest(const float x, const float y, const int c, const WrapMode wm) const
252 if( wm == WrapMode_Clamp ) return sampleNearestClamp(x, y, c);
253 else if( wm == WrapMode_Repeat ) return sampleNearestRepeat(x, y, c);
254 else /*if( wm == WrapMode_Mirror )*/ return sampleNearestMirror(x, y, c);
257 float FloatImage::sampleLinear(const float x, const float y, const int c, const WrapMode wm) const
259 if( wm == WrapMode_Clamp ) return sampleLinearClamp(x, y, c);
260 else if( wm == WrapMode_Repeat ) return sampleLinearRepeat(x, y, c);
261 else /*if( wm == WrapMode_Mirror )*/ return sampleLinearMirror(x, y, c);
264 float FloatImage::sampleNearestClamp(const float x, const float y, const int c) const
266 int ix = ::clamp(iround(x * m_width), 0, (int) m_width-1);
267 int iy = ::clamp(iround(y * m_height), 0, (int) m_height-1);
268 return pixel(ix, iy, c);
271 float FloatImage::sampleNearestRepeat(const float x, const float y, const int c) const
273 int ix = iround(frac(x) * m_width);
274 int iy = iround(frac(y) * m_height);
275 return pixel(ix, iy, c);
278 float FloatImage::sampleNearestMirror(const float x, const float y, const int c) const
280 int ix = mirror(iround(x * m_width), m_width);
281 int iy = mirror(iround(y * m_height), m_height);
282 return pixel(ix, iy, c);
285 float FloatImage::sampleLinearClamp(float x, float y, const int c) const
287 const int w = m_width;
288 const int h = m_height;
293 const float fracX = frac(x);
294 const float fracY = frac(y);
296 const int ix0 = ::clamp(ifloor(x), 0, w-1);
297 const int iy0 = ::clamp(ifloor(y), 0, h-1);
298 const int ix1 = ::clamp(ifloor(x)+1, 0, w-1);
299 const int iy1 = ::clamp(ifloor(y)+1, 0, h-1);
301 float f1 = pixel(ix0, iy0, c);
302 float f2 = pixel(ix1, iy0, c);
303 float f3 = pixel(ix0, iy1, c);
304 float f4 = pixel(ix1, iy1, c);
306 float i1 = lerp(f1, f2, fracX);
307 float i2 = lerp(f3, f4, fracX);
309 return lerp(i1, i2, fracY);
312 float FloatImage::sampleLinearRepeat(float x, float y, int c) const
314 const int w = m_width;
315 const int h = m_height;
317 const float fracX = frac(x * w);
318 const float fracY = frac(y * h);
320 int ix0 = ifloor(frac(x) * w);
321 int iy0 = ifloor(frac(y) * h);
322 int ix1 = ifloor(frac(x + 1.0f/w) * w);
323 int iy1 = ifloor(frac(y + 1.0f/h) * h);
325 float f1 = pixel(ix0, iy0, c);
326 float f2 = pixel(ix1, iy0, c);
327 float f3 = pixel(ix0, iy1, c);
328 float f4 = pixel(ix1, iy1, c);
330 float i1 = lerp(f1, f2, fracX);
331 float i2 = lerp(f3, f4, fracX);
333 return lerp(i1, i2, fracY);
336 float FloatImage::sampleLinearMirror(float x, float y, int c) const
338 const int w = m_width;
339 const int h = m_height;
344 const float fracX = frac(x);
345 const float fracY = frac(y);
347 int ix0 = mirror(iround(x), w);
348 int iy0 = mirror(iround(y), h);
349 int ix1 = mirror(iround(x) + 1, w);
350 int iy1 = mirror(iround(y) + 1, h);
352 float f1 = pixel(ix0, iy0, c);
353 float f2 = pixel(ix1, iy0, c);
354 float f3 = pixel(ix0, iy1, c);
355 float f4 = pixel(ix1, iy1, c);
357 float i1 = lerp(f1, f2, fracX);
358 float i2 = lerp(f3, f4, fracX);
360 return lerp(i1, i2, fracY);
364 /// Fast downsampling using box filter.
366 /// The extents of the image are divided by two and rounded down.
368 /// When the size of the image is odd, this uses a polyphase box filter as explained in:
369 /// http://developer.nvidia.com/object/np2_mipmapping.html
371 FloatImage * FloatImage::fastDownSample() const
373 // nvDebugCheck(m_width != 1 || m_height != 1);
375 std::auto_ptr<FloatImage> dst_image( new FloatImage() );
376 // AutoPtr<FloatImage> dst_image( new FloatImage() );
378 const uint w = std::max((unsigned int) 1, m_width / 2);
379 const uint h = std::max((unsigned int) 1, m_height / 2);
380 dst_image->allocate(m_componentNum, w, h);
383 if (m_width == 1 || m_height == 1)
385 const uint n = w * h;
389 const float scale = 1.0f / (2 * n + 1);
391 for(uint c = 0; c < m_componentNum; c++)
393 const float * src = this->channel(c);
394 float * dst = dst_image->channel(c);
396 for(uint x = 0; x < n; x++)
398 const float w0 = float(n - x);
399 const float w1 = float(n - 0);
400 const float w2 = float(1 + x);
402 *dst++ = scale * (w0 * src[0] + w1 * src[1] + w2 * src[2]);
409 for(uint c = 0; c < m_componentNum; c++)
411 const float * src = this->channel(c);
412 float * dst = dst_image->channel(c);
414 for(uint x = 0; x < n; x++)
416 *dst = 0.5f * (src[0] + src[1]);
424 // Regular box filter.
425 else if ((m_width & 1) == 0 && (m_height & 1) == 0)
427 for(uint c = 0; c < m_componentNum; c++)
429 const float * src = this->channel(c);
430 float * dst = dst_image->channel(c);
432 for(uint y = 0; y < h; y++)
434 for(uint x = 0; x < w; x++)
436 *dst = 0.25f * (src[0] + src[1] + src[m_width] + src[m_width + 1]);
446 // Polyphase filters.
447 else if (m_width & 1 && m_height & 1)
449 // nvDebugCheck(m_width == 2 * w + 1);
450 // nvDebugCheck(m_height == 2 * h + 1);
452 const float scale = 1.0f / (m_width * m_height);
454 for(uint c = 0; c < m_componentNum; c++)
456 const float * src = this->channel(c);
457 float * dst = dst_image->channel(c);
459 for(uint y = 0; y < h; y++)
461 const float v0 = float(h - y);
462 const float v1 = float(h - 0);
463 const float v2 = float(1 + y);
465 for (uint x = 0; x < w; x++)
467 const float w0 = float(w - x);
468 const float w1 = float(w - 0);
469 const float w2 = float(1 + x);
472 f += v0 * (w0 * src[0 * m_width + 2 * x] + w1 * src[0 * m_width + 2 * x + 1] + w2 * src[0 * m_width + 2 * x + 2]);
473 f += v1 * (w0 * src[1 * m_width + 2 * x] + w1 * src[1 * m_width + 2 * x + 1] + w2 * src[0 * m_width + 2 * x + 2]);
474 f += v2 * (w0 * src[2 * m_width + 2 * x] + w1 * src[2 * m_width + 2 * x + 1] + w2 * src[0 * m_width + 2 * x + 2]);
484 else if (m_width & 1)
486 // nvDebugCheck(m_width == 2 * w + 1);
487 const float scale = 1.0f / (2 * m_width);
489 for(uint c = 0; c < m_componentNum; c++)
491 const float * src = this->channel(c);
492 float * dst = dst_image->channel(c);
494 for(uint y = 0; y < h; y++)
496 for (uint x = 0; x < w; x++)
498 const float w0 = float(w - x);
499 const float w1 = float(w - 0);
500 const float w2 = float(1 + x);
503 f += w0 * (src[2 * x + 0] + src[m_width + 2 * x + 0]);
504 f += w1 * (src[2 * x + 1] + src[m_width + 2 * x + 1]);
505 f += w2 * (src[2 * x + 2] + src[m_width + 2 * x + 2]);
515 else if (m_height & 1)
517 // nvDebugCheck(m_height == 2 * h + 1);
519 const float scale = 1.0f / (2 * m_height);
521 for(uint c = 0; c < m_componentNum; c++)
523 const float * src = this->channel(c);
524 float * dst = dst_image->channel(c);
526 for(uint y = 0; y < h; y++)
528 const float v0 = float(h - y);
529 const float v1 = float(h - 0);
530 const float v2 = float(1 + y);
532 for (uint x = 0; x < w; x++)
535 f += v0 * (src[0 * m_width + 2 * x] + src[0 * m_width + 2 * x + 1]);
536 f += v1 * (src[1 * m_width + 2 * x] + src[1 * m_width + 2 * x + 1]);
537 f += v2 * (src[2 * m_width + 2 * x] + src[2 * m_width + 2 * x + 1]);
548 return dst_image.release();
552 /// Downsample applying a 1D kernel separately in each dimension.
553 FloatImage * FloatImage::downSample(const Kernel1 & kernel, WrapMode wm) const
555 const uint w = max(1, m_width / 2);
556 const uint h = max(1, m_height / 2);
558 return downSample(kernel, w, h, wm);
562 /// Downsample applying a 1D kernel separately in each dimension.
563 FloatImage * FloatImage::downSample(const Kernel1 & kernel, uint w, uint h, WrapMode wm) const
565 nvCheck(!(kernel.windowSize() & 1)); // Make sure that kernel m_width is even.
567 AutoPtr<FloatImage> tmp_image( new FloatImage() );
568 tmp_image->allocate(m_componentNum, w, m_height);
570 AutoPtr<FloatImage> dst_image( new FloatImage() );
571 dst_image->allocate(m_componentNum, w, h);
573 const float xscale = float(m_width) / float(w);
574 const float yscale = float(m_height) / float(h);
576 for(uint c = 0; c < m_componentNum; c++) {
577 float * tmp_channel = tmp_image->channel(c);
579 for(uint y = 0; y < m_height; y++) {
580 for(uint x = 0; x < w; x++) {
582 float sum = this->applyKernelHorizontal(&kernel, uint(x*xscale), y, c, wm);
584 const uint tmp_index = tmp_image->index(x, y);
585 tmp_channel[tmp_index] = sum;
589 float * dst_channel = dst_image->channel(c);
591 for(uint y = 0; y < h; y++) {
592 for(uint x = 0; x < w; x++) {
594 float sum = tmp_image->applyKernelVertical(&kernel, uint(x*xscale), uint(y*yscale), c, wm);
596 const uint dst_index = dst_image->index(x, y);
597 dst_channel[dst_index] = sum;
602 return dst_image.release();
606 /// Downsample applying a 1D kernel separately in each dimension.
607 FloatImage * FloatImage::downSample(const Filter & filter, WrapMode wm) const
609 const uint w = std::max((unsigned int) 1, m_width / 2);
610 const uint h = std::max((unsigned int) 1, m_height / 2);
612 return downSample(filter, w, h, wm);
616 /// Downsample applying a 1D kernel separately in each dimension.
617 FloatImage * FloatImage::downSample(const Filter & filter, uint w, uint h, WrapMode wm) const
619 // @@ Use monophase filters when frac(m_width / w) == 0
621 // AutoPtr<FloatImage> tmp_image( new FloatImage() );
622 // AutoPtr<FloatImage> dst_image( new FloatImage() );
623 std::auto_ptr<FloatImage> tmp_image(new FloatImage() );
624 std::auto_ptr<FloatImage> dst_image(new FloatImage() );
626 PolyphaseKernel xkernel(filter, m_width, w, 32);
627 PolyphaseKernel ykernel(filter, m_height, h, 32);
629 // @@ Select fastest filtering order:
630 //if (w * m_height <= h * m_width)
632 tmp_image->allocate(m_componentNum, w, m_height);
633 dst_image->allocate(m_componentNum, w, h);
635 // Array<float> tmp_column(h);
636 std::vector<float> tmp_column(h);
637 tmp_column.resize(h);
639 for (uint c = 0; c < m_componentNum; c++)
641 float * tmp_channel = tmp_image->channel(c);
643 for (uint y = 0; y < m_height; y++) {
644 this->applyKernelHorizontal(xkernel, y, c, wm, tmp_channel + y * w);
647 float * dst_channel = dst_image->channel(c);
649 for (uint x = 0; x < w; x++) {
650 tmp_image->applyKernelVertical(ykernel, x, c, wm, &tmp_column[0]);
652 for (uint y = 0; y < h; y++) {
653 dst_channel[y * w + x] = tmp_column[y];
660 tmp_image->allocate(m_componentNum, m_width, h);
661 dst_image->allocate(m_componentNum, w, h);
663 Array<float> tmp_column(h);
664 tmp_column.resize(h);
666 for (uint c = 0; c < m_componentNum; c++)
668 float * tmp_channel = tmp_image->channel(c);
670 for (uint x = 0; x < w; x++) {
671 tmp_image->applyKernelVertical(ykernel, x, c, wm, tmp_column.unsecureBuffer());
673 for (uint y = 0; y < h; y++) {
674 tmp_channel[y * w + x] = tmp_column[y];
678 float * dst_channel = dst_image->channel(c);
680 for (uint y = 0; y < m_height; y++) {
681 this->applyKernelHorizontal(xkernel, y, c, wm, dst_channel + y * w);
686 return dst_image.release();
691 /// Apply 2D kernel at the given coordinates and return result.
692 float FloatImage::applyKernel(const Kernel2 * k, int x, int y, int c, WrapMode wm) const
694 // nvDebugCheck(k != NULL);
696 const uint kernelWindow = k->windowSize();
697 const int kernelOffset = int(kernelWindow / 2) - 1;
699 const float * channel = this->channel(c);
702 for (uint i = 0; i < kernelWindow; i++)
704 const int src_y = int(y + i) - kernelOffset;
706 for (uint e = 0; e < kernelWindow; e++)
708 const int src_x = int(x + e) - kernelOffset;
710 int idx = this->index(src_x, src_y, wm);
712 sum += k->valueAt(e, i) * channel[idx];
720 /// Apply 1D vertical kernel at the given coordinates and return result.
721 float FloatImage::applyKernelVertical(const Kernel1 * k, int x, int y, int c, WrapMode wm) const
723 // nvDebugCheck(k != NULL);
725 const uint kernelWindow = k->windowSize();
726 const int kernelOffset = int(kernelWindow / 2) - 1;
728 const float * channel = this->channel(c);
731 for (uint i = 0; i < kernelWindow; i++)
733 const int src_y = int(y + i) - kernelOffset;
734 const int idx = this->index(x, src_y, wm);
736 sum += k->valueAt(i) * channel[idx];
742 /// Apply 1D horizontal kernel at the given coordinates and return result.
743 float FloatImage::applyKernelHorizontal(const Kernel1 * k, int x, int y, int c, WrapMode wm) const
745 // nvDebugCheck(k != NULL);
747 const uint kernelWindow = k->windowSize();
748 const int kernelOffset = int(kernelWindow / 2) - 1;
750 const float * channel = this->channel(c);
753 for (uint e = 0; e < kernelWindow; e++)
755 const int src_x = int(x + e) - kernelOffset;
756 const int idx = this->index(src_x, y, wm);
758 sum += k->valueAt(e) * channel[idx];
765 /// Apply 1D vertical kernel at the given coordinates and return result.
766 void FloatImage::applyKernelVertical(const PolyphaseKernel & k, int x, int c, WrapMode wm, float * output) const
768 const uint length = k.length();
769 const float scale = float(length) / float(m_height);
770 const float iscale = 1.0f / scale;
772 const float width = k.width();
773 const int windowSize = k.windowSize();
775 const float * channel = this->channel(c);
777 for (uint i = 0; i < length; i++)
779 const float center = (0.5f + i) * iscale;
781 const int left = (int)floorf(center - width);
782 const int right = (int)ceilf(center + width);
783 // nvCheck(right - left <= windowSize);
786 for (int j = 0; j < windowSize; ++j)
788 const int idx = this->index(x, j+left, wm);
790 sum += k.valueAt(i, j) * channel[idx];
797 /// Apply 1D horizontal kernel at the given coordinates and return result.
798 void FloatImage::applyKernelHorizontal(const PolyphaseKernel & k, int y, int c, WrapMode wm, float * output) const
800 const uint length = k.length();
801 const float scale = float(length) / float(m_width);
802 const float iscale = 1.0f / scale;
804 const float width = k.width();
805 const int windowSize = k.windowSize();
807 const float * channel = this->channel(c);
809 for (uint i = 0; i < length; i++)
811 const float center = (0.5f + i) * iscale;
813 const int left = (int)floorf(center - width);
814 const int right = (int)ceilf(center + width);
815 // nvDebugCheck(right - left <= windowSize);
818 for (int j = 0; j < windowSize; ++j)
820 const int idx = this->index(left + j, y, wm);
822 sum += k.valueAt(i, j) * channel[idx];