// Copyright (c) 2009-2011 Ignacio Castano <castano@gmail.com>
// Copyright (c) 2007-2009 NVIDIA Corporation -- Ignacio Castano <icastano@nvidia.com>
// 
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

#include "Surface.h"
#include "CompressorETC.h" // for ETC decoder.

#include "nvmath/Vector.inl"
#include "nvmath/Matrix.inl"
#include "nvmath/Color.h"
#include "nvmath/Half.h"
#include "nvmath/ftoi.h"
#include "nvmath/PackedFloat.h"

#include "nvimage/Filter.h"
#include "nvimage/ImageIO.h"
#include "nvimage/NormalMap.h"
#include "nvimage/BlockDXT.h"
#include "nvimage/ColorBlock.h"
#include "nvimage/PixelFormat.h"
#include "nvimage/ErrorMetric.h"
#include "nvimage/DirectDrawSurface.h"

#include "nvthread/ParallelFor.h"

#include "nvcore/Array.inl"
#include "nvcore/StrLib.h"

#include <float.h>
#include <string.h> // memset, memcpy
//#include <stdio.h> // printf?

#if NV_CC_GNUC
#include <math.h> // exp2f and log2f
#endif

using namespace nv;
using namespace nvtt;

namespace
{
    // 1 -> 1, 2 -> 2, 3 -> 2, 4 -> 4, 5 -> 4, ...
    static inline uint previousPowerOfTwo(uint v)
    {
        return nextPowerOfTwo(v + 1) / 2;
    }

    static inline uint nearestPowerOfTwo(uint v)
    {
        const uint np2 = nextPowerOfTwo(v);
        const uint pp2 = previousPowerOfTwo(v);

        if (np2 - v <= v - pp2)
        {
            return np2;
        }
        else
        {
            return pp2;
        }
    }

    static inline uint nextMultipleOfFour(uint v)
    {
        return (v + 3) & ~3;
    }
    static inline uint previousMultipleOfFour(uint v)
    {
        return v & ~3;
    }

    static inline uint nearestMultipleOfFour(uint v)
    {
        const uint nm4 = nextMultipleOfFour(v);
        const uint pm4 = previousMultipleOfFour(v);

        if (nm4 - v <= v - pm4)
        {
            return nm4;
        }
        else
        {
            return pm4;
        }
    }


    static int blockSize(Format format)
    {
        if (format == Format_DXT1 || format == Format_DXT1a || format == Format_DXT1n) {
            return 8;
        }
        else if (format == Format_DXT3) {
            return 16;
        }
        else if (format == Format_DXT5 || format == Format_DXT5n || format == Format_BC3_RGBM) {
            return 16;
        }
        else if (format == Format_BC4) {
            return 8;
        }
        else if (format == Format_BC5 /*|| format == Format_BC5_Luma*/) {
            return 16;
        }
        else if (format == Format_CTX1) {
            return 8;
        }
        else if (format == Format_BC6) {
            return 16;
        }
        else if (format == Format_BC7) {
            return 16;
        }
        else if (format == Format_ETC1 || format == Format_ETC2_R || format == Format_ETC2_RGB) {
            return 8;
        }
        else if (format == Format_ETC2_RG || format == Format_ETC2_RGBA || format == Format_ETC2_RGBM) {
            return 16;
        }
        else if (format == Format_PVR_2BPP_RGB || format == Format_PVR_2BPP_RGBA) {
            return 4;
        }
        else if (format == Format_PVR_4BPP_RGB || format == Format_PVR_4BPP_RGBA) {
            return 8;
        }
        return 0;
    }

    /*static int translateMask(int input) {
        if (input > 0) return 1 << input;
        return ~input;
    }*/
}

bool nv::canMakeNextMipmap(uint w, uint h, uint d, uint min_size)
{
    if (min_size==1u) {  
        if(w==1u && h==1u && d==1u) {
            return false;
        }
    }
    else if (((w <= min_size || h <= min_size) && d == 1u)) {
        return false;
    }

    return true;
}

uint nv::countMipmaps(uint w)
{
    uint mipmap = 0;

    while (w != 1) {
        w = max(1U, w / 2);
        mipmap++;
    }

    return mipmap + 1;
}

uint nv::countMipmaps(uint w, uint h, uint d)
{
    uint mipmap = 0;

    while (w != 1 || h != 1 || d != 1) {
        w = max(1U, w / 2);
        h = max(1U, h / 2);
        d = max(1U, d / 2);
        mipmap++;
    }

    return mipmap + 1;
}

uint nv::countMipmapsWithMinSize(uint w, uint h, uint d, uint min_size)
{
    uint mipmap = 0;

    while (canMakeNextMipmap(w, h, d, min_size)) {
        w = max(1U, w / 2);
        h = max(1U, h / 2);
        d = max(1U, d / 2);
        mipmap++;
    }

   return mipmap + 1;
}


uint nv::computeImageSize(uint w, uint h, uint d, uint bitCount, uint pitchAlignmentInBytes, Format format)
{
    if (format == Format_RGBA) {
        return d * h * computeBytePitch(w, bitCount, pitchAlignmentInBytes);
    }
    else {
        return ((w + 3) / 4) * ((h + 3) / 4) * blockSize(format) * d;
    }
}

void nv::getTargetExtent(int * width, int * height, int * depth, int maxExtent, RoundMode roundMode, TextureType textureType, nvtt::ShapeRestriction shapeRestriction /*= nvtt::ShapeRestriction_None*/) {
    nvDebugCheck(width != NULL && *width > 0);
    nvDebugCheck(height != NULL && *height > 0);
    nvDebugCheck(depth != NULL && *depth > 0);

    int w = *width;
    int h = *height;
    int d = *depth;

    if (roundMode != RoundMode_None && maxExtent > 0)
    {
        // rounded max extent should never be higher than original max extent.
        maxExtent = previousPowerOfTwo(maxExtent);
    }

    // Scale extents without changing aspect ratio.
    int m = max(max(w, h), d);
    if (maxExtent > 0 && m > maxExtent)
    {
        w = max((w * maxExtent) / m, 1);
        h = max((h * maxExtent) / m, 1);
        d = max((d * maxExtent) / m, 1);
    }

    if (textureType == TextureType_2D)
    {
        d = 1;
    }
    else if (textureType == TextureType_Cube)
    {
        w = h = (w + h) / 2;
        d = 1;
    }

    // Round to power of two.
    if (roundMode == RoundMode_ToNextPowerOfTwo)
    {
        w = nextPowerOfTwo(U32(w));
        h = nextPowerOfTwo(U32(h));
        d = nextPowerOfTwo(U32(d));
    }
    else if (roundMode == RoundMode_ToNearestPowerOfTwo)
    {
        w = nearestPowerOfTwo(U32(w));
        h = nearestPowerOfTwo(U32(h));
        d = nearestPowerOfTwo(U32(d));
    }
    else if (roundMode == RoundMode_ToPreviousPowerOfTwo)
    {
        w = previousPowerOfTwo(U32(w));
        h = previousPowerOfTwo(U32(h));
        d = previousPowerOfTwo(U32(d));
    }
    else if (roundMode == RoundMode_ToNextMultipleOfFour)
    {
        w = nextMultipleOfFour(w);
        h = nextMultipleOfFour(h);
        d = nextMultipleOfFour(d);
    }
    else if (roundMode == RoundMode_ToNextMultipleOfFour)
    {
        w = nearestMultipleOfFour(w);
        h = nearestMultipleOfFour(h);
        d = nearestMultipleOfFour(d);
    }
    else if (roundMode == RoundMode_ToPreviousMultipleOfFour)
    {
        w = previousMultipleOfFour(w);
        h = previousMultipleOfFour(h);
        d = previousMultipleOfFour(d);
    }

    if(shapeRestriction == ShapeRestriction_Square)
    {
        if (textureType == TextureType_2D)
        {
            int md = nv::min(w,h);
            w = md;
            h = md;
            d = 1;
        }
        else if (textureType == TextureType_3D)
        {
            int md = nv::min(nv::min(w,h),d);
            w = md;
            h = md;
            d = md;
        }
        else if (textureType == TextureType_Cube) 
        {
            int md = nv::min(w, h);
            w = md;
            h = md;
            d = 1;
        }
    }
    else 
    {
        if (textureType == TextureType_2D || textureType == TextureType_Cube)
        {
            d = 1;
        }
    }

    *width = w;
    *height = h;
    *depth = d;
}



Surface::Surface() : m(new Surface::Private())
{
    m->addRef();
}

Surface::Surface(const Surface & tex) : m(tex.m)
{
    if (m != NULL) m->addRef();
}

Surface::~Surface()
{
    if (m != NULL) m->release();
    m = NULL;
}

void Surface::operator=(const Surface & tex)
{
    if (tex.m != NULL) tex.m->addRef();
    if (m != NULL) m->release();
    m = tex.m;
}

void Surface::detach()
{
    if (m->refCount() > 1)
    {
        m->release();
        m = new Surface::Private(*m);
        m->addRef();
        nvDebugCheck(m->refCount() == 1);
    }
}

void Surface::setWrapMode(WrapMode wrapMode)
{
    if (m->wrapMode != wrapMode)
    {
        detach();
        m->wrapMode = wrapMode;
    }
}

void Surface::setAlphaMode(AlphaMode alphaMode)
{
    if (m->alphaMode != alphaMode)
    {
        detach();
        m->alphaMode = alphaMode;
    }
}

void Surface::setNormalMap(bool isNormalMap)
{
    if (m->isNormalMap != isNormalMap)
    {
        detach();
        m->isNormalMap = isNormalMap;
    }
}

bool Surface::isNull() const
{
    return m->image == NULL;
}

int Surface::width() const
{
    if (m->image != NULL) return m->image->width();
    return 0;
}

int Surface::height() const
{
    if (m->image != NULL) return m->image->height();
    return 0;
}

int Surface::depth() const
{
    if (m->image != NULL) return m->image->depth();
    return 0;
}

WrapMode Surface::wrapMode() const
{
    return m->wrapMode;
}

AlphaMode Surface::alphaMode() const
{
    return m->alphaMode;
}

bool Surface::isNormalMap() const
{
    return m->isNormalMap;
}

TextureType Surface::type() const
{
    return m->type;
}

int Surface::countMipmaps() const
{
    if (m->image == NULL) return 0;
    return ::countMipmaps(m->image->width(), m->image->height(), 1);
}

int Surface::countMipmaps(int min_size) const
{
    if (m->image == NULL) return 0;
    return ::countMipmapsWithMinSize(m->image->width(), m->image->height(), 1, min_size);
}

float Surface::alphaTestCoverage(float alphaRef/*= 0.5*/, int alpha_channel/*=3*/) const
{
    if (m->image == NULL) return 0.0f;

    alphaRef = nv::clamp(alphaRef, 1.0f/256, 255.0f/256);

    return m->image->alphaTestCoverage(alphaRef, alpha_channel);
}

float Surface::average(int channel, int alpha_channel/*= -1*/, float gamma /*= 2.2f*/) const
{
    if (m->image == NULL) return 0.0f;

    const uint count = m->image->width() * m->image->height();

    float sum = 0.0f;
    const float * c = m->image->channel(channel);

    float denom;

    if (alpha_channel == -1) {
        for (uint i = 0; i < count; i++) {
            sum += powf(c[i], gamma);
        }

        denom = float(count);
    }
    else {
        float alpha_sum = 0.0f;
        const float * a = m->image->channel(alpha_channel);
        
        for (uint i = 0; i < count; i++) {
            sum += powf(c[i], gamma) * a[i];
            alpha_sum += a[i];
        }

        denom = alpha_sum;
    }

    // Avoid division by zero.
    if (denom == 0.0f) return 0.0f;

    return powf(sum / denom, 1.0f/gamma);
}

const float * Surface::data() const
{
    return m->image->channel(0);
}

const float * Surface::channel(int i) const
{
    if (i < 0 || i > 3) return NULL;
    return m->image->channel(i);
}


void Surface::histogram(int channel, float rangeMin, float rangeMax, int binCount, int * binPtr) const
{
    // We assume it's clear in case we want to accumulate multiple histograms.
    //memset(bins, 0, sizeof(int)*count);

    if (m->image == NULL) return;

    const float * c = m->image->channel(channel);

    float scale = float(binCount) / rangeMax;
    float bias = - scale * rangeMin;

    const uint count = m->image->pixelCount();
    for (uint i = 0; i < count; i++) {
        float f = c[i] * scale + bias;
        int idx = ftoi_floor(f);
        if (idx < 0) idx = 0;
        if (idx > binCount-1) idx = binCount-1;
        binPtr[idx]++;
    }
}

void Surface::range(int channel, float * rangeMin, float * rangeMax, int alpha_channel/*= -1*/, float alpha_ref/*= 0.f*/) const
{
    Vector2 range(FLT_MAX, -FLT_MAX);

    FloatImage * img = m->image;

    if (alpha_channel == -1) { // no alpha channel; just like the original range function

        if (m->image != NULL) {
            float * c = img->channel(channel);

            const uint count = img->pixelCount();
            for (uint p = 0; p < count; p++) {
                float f = c[p];
                if (f < range.x) range.x = f;
                if (f > range.y) range.y = f;
            }
        }
    }
    else { // use alpha test to ignore some pixels
        //note, it's quite possible to get FLT_MAX,-FLT_MAX back if all pixels fail the test

        if (m->image != NULL)
        {
            const float * c = img->channel(channel);
            const float * a = img->channel(alpha_channel);

            const uint count = img->pixelCount();
            for (uint p = 0; p < count; p++) {
                if(a[p]>alpha_ref) {
                    float f = c[p];
                    if (f < range.x) range.x = f;
                    if (f > range.y) range.y = f;
                }
            }
        }
    }

    if (rangeMin) *rangeMin = range.x;
    if (rangeMax) *rangeMax = range.y;
}

bool Surface::load(const char * fileName, bool * hasAlpha/*= NULL*/)
{
    AutoPtr<FloatImage> img(ImageIO::loadFloat(fileName));
    if (img == NULL) {
        // Try loading as DDS.
        if (!nv::strEqual(nv::Path::extension(fileName), ".dds")) {
            return false;
        }

        nv::DirectDrawSurface dds;
        if (!dds.load(fileName)) {
            return false;
        }

        if (dds.header.isBlockFormat()) {
            int w = dds.surfaceWidth(0);
            int h = dds.surfaceHeight(0);
            uint size = dds.surfaceSize(0);

            void * data = malloc(size);
            dds.readSurface(0, 0, data, size);

            // @@ Handle all formats! @@ Get nvtt format from dds.surfaceFormat() ?

            if (dds.header.hasDX10Header()) {
                if (dds.header.header10.dxgiFormat == DXGI_FORMAT_BC1_UNORM || dds.header.header10.dxgiFormat == DXGI_FORMAT_BC1_UNORM_SRGB) {
                    this->setImage2D(nvtt::Format_BC1, nvtt::Decoder_D3D10, w, h, data);
                }
                else if (dds.header.header10.dxgiFormat == DXGI_FORMAT_BC2_UNORM || dds.header.header10.dxgiFormat == DXGI_FORMAT_BC2_UNORM_SRGB) {
                    this->setImage2D(nvtt::Format_BC2, nvtt::Decoder_D3D10, w, h, data);
                }
                else if (dds.header.header10.dxgiFormat == DXGI_FORMAT_BC3_UNORM || dds.header.header10.dxgiFormat == DXGI_FORMAT_BC3_UNORM_SRGB) {
                    this->setImage2D(nvtt::Format_BC3, nvtt::Decoder_D3D10, w, h, data);
                }
                else if (dds.header.header10.dxgiFormat == DXGI_FORMAT_BC6H_UF16) {
                    this->setImage2D(nvtt::Format_BC6, nvtt::Decoder_D3D10, w, h, data);
                }
                else if (dds.header.header10.dxgiFormat == DXGI_FORMAT_BC7_UNORM || dds.header.header10.dxgiFormat == DXGI_FORMAT_BC7_UNORM_SRGB) {
                    this->setImage2D(nvtt::Format_BC7, nvtt::Decoder_D3D10, w, h, data);
                }
                else {
                    // @@
                    nvCheck(false && "Format not handled with DDS10 header.");
                }
            }
            else {
                uint fourcc = dds.header.pf.fourcc;
                if (fourcc == FOURCC_DXT1) {
                    this->setImage2D(nvtt::Format_BC1, nvtt::Decoder_D3D10, w, h, data);
                }
                else if (fourcc == FOURCC_DXT3) {
                    this->setImage2D(nvtt::Format_BC2, nvtt::Decoder_D3D10, w, h, data);
                }
                else if (fourcc == FOURCC_DXT5) {
                    this->setImage2D(nvtt::Format_BC3, nvtt::Decoder_D3D10, w, h, data);
                }
                else {
                    // @@ 
                    nvCheck(false && "Format not handled with DDS9 header.");
                }
            }

            free(data);
        }
        else {
            // @@ Separate image decoder from dds reader.
            Image img;
            imageFromDDS(&img, dds, /*face=*/0, /*mipmap=*/0);

            int w = img.width;
            int h = img.height;
            int d = img.depth;

            // @@ Add support for all pixel formats.

            this->setImage(nvtt::InputFormat_BGRA_8UB, w, h, d, img.pixels());
        }

        return true;
    }

    detach();

    if (hasAlpha != NULL) {
        *hasAlpha = (img->componentCount() == 4);
    }

    // @@ Have loadFloat allocate the image with the desired number of channels.
    img->resizeChannelCount(4); // Block compressors expect a 4 channel texture.

    delete m->image;
    m->image = img.release();

    return true;
}

bool Surface::save(const char * fileName, bool hasAlpha/*=0*/, bool hdr/*=0*/) const
{
    if (m->image == NULL) {
        return false;
    }

    if (hdr) {
        return ImageIO::saveFloat(fileName, m->image, 0, 4);
    }
    else {
        uint c = min<uint>(m->image->componentCount(), 4);
        AutoPtr<Image> image(m->image->createImage(0, c));
        nvCheck(image != NULL);

        if (hasAlpha) {
            image->format = Image::Format_ARGB;
        }

        return ImageIO::save(fileName, image.ptr());
    }
}


bool Surface::setImage(int w, int h, int d)
{
    detach();

    if (m->image == NULL) {
        m->image = new FloatImage();
    }
    m->image->allocate(4, w, h, d);
    m->type = (d == 1) ? TextureType_2D : TextureType_3D;

    m->image->clear();

    return true;
}


#if 0 //NV_OS_WIN32

#include <windows.h>
#undef min
#undef max

static int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep) {
   if (code == EXCEPTION_ACCESS_VIOLATION) {
      return EXCEPTION_EXECUTE_HANDLER;
   }
   else {
      return EXCEPTION_CONTINUE_SEARCH;
   };
}

#define TRY __try
#define CATCH __except (filter(GetExceptionCode(), GetExceptionInformation()))

#else // 0

#define TRY if (true)
#define CATCH else

#endif

bool Surface::setImage(nvtt::InputFormat format, int w, int h, int d, const void * data)
{
    detach();

    if (m->image == NULL) {
        m->image = new FloatImage();
    }
    m->image->allocate(4, w, h, d);
    m->type = (d == 1) ? TextureType_2D : TextureType_3D;

    const int count = m->image->pixelCount();

    float * rdst = m->image->channel(0);
    float * gdst = m->image->channel(1);
    float * bdst = m->image->channel(2);
    float * adst = m->image->channel(3);

    if (format == InputFormat_BGRA_8UB)
    {
        const Color32 * src = (const Color32 *)data;

        TRY {
            for (int i = 0; i < count; i++)
            {
                rdst[i] = float(src[i].r) / 255.0f;
                gdst[i] = float(src[i].g) / 255.0f;
                bdst[i] = float(src[i].b) / 255.0f;
                adst[i] = float(src[i].a) / 255.0f;
            }
        }
        CATCH {
            return false;
        }
    }
    else if (format == InputFormat_RGBA_16F)
    {
        const uint16 * src = (const uint16 *)data;

        TRY {
            for (int i = 0; i < count; i++)
            {
                ((uint32 *)rdst)[i] = half_to_float(src[4*i+0]);
                ((uint32 *)gdst)[i] = half_to_float(src[4*i+1]);
                ((uint32 *)bdst)[i] = half_to_float(src[4*i+2]);
                ((uint32 *)adst)[i] = half_to_float(src[4*i+3]);
            }
        }
        CATCH {
            return false;
        }
    }
    else if (format == InputFormat_RGBA_32F)
    {
        const float * src = (const float *)data;

        TRY {
            for (int i = 0; i < count; i++)
            {
                rdst[i] = src[4 * i + 0];
                gdst[i] = src[4 * i + 1];
                bdst[i] = src[4 * i + 2];
                adst[i] = src[4 * i + 3];
            }
        }
        CATCH {
            return false;
        }
    }
    else if (format == InputFormat_R_32F)
    {
        const float * src = (const float *)data;

        TRY {
            for (int i = 0; i < count; i++)
            {
                rdst[i] = src[i];
                gdst[i] = 0;
                bdst[i] = 0;
                adst[i] = 0;
            }
        }
        CATCH {
            return false;
        }
    }

    return true;
}

bool Surface::setImage(InputFormat format, int w, int h, int d, const void * r, const void * g, const void * b, const void * a)
{
    detach();

    if (m->image == NULL) {
        m->image = new FloatImage();
    }
    m->image->allocate(4, w, h, d);
    m->type = (d == 1) ? TextureType_2D : TextureType_3D;

    const int count = m->image->pixelCount();

    float * rdst = m->image->channel(0);
    float * gdst = m->image->channel(1);
    float * bdst = m->image->channel(2);
    float * adst = m->image->channel(3);

    if (format == InputFormat_BGRA_8UB)
    {
        const uint8 * rsrc = (const uint8 *)r;
        const uint8 * gsrc = (const uint8 *)g;
        const uint8 * bsrc = (const uint8 *)b;
        const uint8 * asrc = (const uint8 *)a;

        TRY {
            for (int i = 0; i < count; i++) rdst[i] = float(rsrc[i]) / 255.0f;
            for (int i = 0; i < count; i++) gdst[i] = float(gsrc[i]) / 255.0f;
            for (int i = 0; i < count; i++) bdst[i] = float(bsrc[i]) / 255.0f;
            for (int i = 0; i < count; i++) adst[i] = float(asrc[i]) / 255.0f;
        }
        CATCH {
            return false;
        }
    }
    else if (format == InputFormat_RGBA_16F)
    {
        const uint16 * rsrc = (const uint16 *)r;
        const uint16 * gsrc = (const uint16 *)g;
        const uint16 * bsrc = (const uint16 *)b;
        const uint16 * asrc = (const uint16 *)a;

        TRY {
            for (int i = 0; i < count; i++) ((uint32 *)rdst)[i] = half_to_float(rsrc[i]);
            for (int i = 0; i < count; i++) ((uint32 *)gdst)[i] = half_to_float(gsrc[i]);
            for (int i = 0; i < count; i++) ((uint32 *)bdst)[i] = half_to_float(bsrc[i]);
            for (int i = 0; i < count; i++) ((uint32 *)adst)[i] = half_to_float(asrc[i]);
        }
        CATCH {
            return false;
        }
    }
    else if (format == InputFormat_RGBA_32F)
    {
        const float * rsrc = (const float *)r;
        const float * gsrc = (const float *)g;
        const float * bsrc = (const float *)b;
        const float * asrc = (const float *)a;

        TRY {
            memcpy(rdst, rsrc, count * sizeof(float));
            memcpy(gdst, gsrc, count * sizeof(float));
            memcpy(bdst, bsrc, count * sizeof(float));
            memcpy(adst, asrc, count * sizeof(float));
        }
        CATCH {
            return false;
        }
    }
    else if (format == InputFormat_R_32F)
    {
        const float * rsrc = (const float *)r;

        TRY {
            memcpy(rdst, rsrc, count * sizeof(float));
            memset(gdst, 0, count * sizeof(float));
            memset(bdst, 0, count * sizeof(float));
            memset(adst, 0, count * sizeof(float));
        }
        CATCH {
            return false;
        }
    }

    return true;
}

#if defined(HAVE_PVRTEXTOOL)
#include <PVRTDecompress.h>
#endif

// @@ Add support for compressed 3D textures.
bool Surface::setImage2D(Format format, Decoder decoder, int w, int h, const void * data)
{
    if (format != nvtt::Format_BC1 &&
        format != nvtt::Format_BC2 &&
        format != nvtt::Format_BC3 &&
        format != nvtt::Format_BC3n &&
        format != nvtt::Format_BC3_RGBM &&
        format != nvtt::Format_BC4 &&
        format != nvtt::Format_BC5 &&
        format != nvtt::Format_BC6 &&
        format != nvtt::Format_BC7 &&
        format != nvtt::Format_ETC1 &&
        format != nvtt::Format_ETC2_R &&
        format != nvtt::Format_ETC2_RG &&
        format != nvtt::Format_ETC2_RGB &&
        format != nvtt::Format_ETC2_RGBA &&
        format != nvtt::Format_ETC2_RGBM
    #if defined(HAVE_PVRTEXTOOL)
        && format != nvtt::Format_PVR_2BPP_RGB
        && format != nvtt::Format_PVR_4BPP_RGB
        && format != nvtt::Format_PVR_2BPP_RGBA
        && format != nvtt::Format_PVR_4BPP_RGBA
    #endif
        )
    {
        return false;
    }

    detach();

    if (m->image == NULL) {
        m->image = new FloatImage();
    }
    m->image->allocate(4, w, h, 1);
    m->type = TextureType_2D;

    const int bw = (w + 3) / 4;     // @@ Not if PVR 2bpp!
    const int bh = (h + 3) / 4;

    const uint bs = blockSize(format);

    const uint8 * ptr = (const uint8 *)data;

    TRY {
#if defined(HAVE_PVRTEXTOOL)
        if (format >= nvtt::Format_PVR_2BPP_RGB && format <= nvtt::Format_PVR_4BPP_RGBA)
        {
            bool two_bit_mode = (format == nvtt::Format_PVR_2BPP_RGB || format == nvtt::Format_PVR_2BPP_RGBA);

            uint8 * output = new uint8[4 * w * h];

            PVRTDecompressPVRTC(ptr, two_bit_mode, w, h, output);

            for (int y = 0; y < h; y++) {
                for (int x = 0; x < w; x++) {
                    m->image->pixel(0, x, y, 0) = output[4*(y*w + x) + 0] / 255.0f;
                    m->image->pixel(1, x, y, 0) = output[4*(y*w + x) + 1] / 255.0f;
                    m->image->pixel(2, x, y, 0) = output[4*(y*w + x) + 2] / 255.0f;
                    m->image->pixel(3, x, y, 0) = output[4*(y*w + x) + 3] / 255.0f;
                }
            }

            delete [] output;
        }
        else
#endif
        if (format == nvtt::Format_BC6 || (format >= nvtt::Format_ETC1 && format <= nvtt::Format_ETC2_RGBM))
        {
            // Some formats we decode directly to float:

            for (int y = 0; y < bh; y++) {
                for (int x = 0; x < bw; x++) {
                    Vector4 colors[16];

                    if (format == nvtt::Format_BC6) {
                        const BlockBC6 * block = (const BlockBC6 *)ptr;
                        block->decodeBlock(colors);
                    }
                    else if (format == nvtt::Format_ETC1 || format == nvtt::Format_ETC2_RGB) {
                        nv::decompress_etc(ptr, colors);
                    }
                    else if (format == nvtt::Format_ETC2_RGBA || format == nvtt::Format_ETC2_RGBM) {
                        nv::decompress_etc_eac(ptr, colors);
                    }
                    else if (format == nvtt::Format_ETC2_R) {
                        // @@ Not implemented.
                        //nv::decompress_eac(ptr, colors);
                    }
                    else if (format == nvtt::Format_ETC2_RG) {
                        // @@ Not implemented.
                        //nv::decompress_eac(ptr, colors);
                    }
                    else if (format == nvtt::Format_ETC2_RGB_A1) {
                        // @@ Not implemented?
                        //nv::decompress_etc(ptr, colors);
                    }

                    for (int yy = 0; yy < 4; yy++) {
                        for (int xx = 0; xx < 4; xx++) {
                            Vector4 c = colors[yy*4 + xx];

                            if (x * 4 + xx < w && y * 4 + yy < h) {
                                m->image->pixel(0, x*4 + xx, y*4 + yy, 0) = c.x;
                                m->image->pixel(1, x*4 + xx, y*4 + yy, 0) = c.y;
                                m->image->pixel(2, x*4 + xx, y*4 + yy, 0) = c.z;
                                m->image->pixel(3, x*4 + xx, y*4 + yy, 0) = c.w;
                            }
                        }
                    }

                    ptr += bs;
                }
            }
        }
        else
        {
            // Others, we decode to 8-bit, then convert to float

            for (int y = 0; y < bh; y++) {
                for (int x = 0; x < bw; x++) {
                    ColorBlock colors;

                    if (format == nvtt::Format_BC1)
                    {
                        const BlockDXT1 * block = (const BlockDXT1 *)ptr;

                        if (decoder == Decoder_D3D10) {
                            block->decodeBlock(&colors, false);
                        }
                        else if (decoder == Decoder_D3D9) {
                            block->decodeBlock(&colors, false);
                        }
                        else if (decoder == Decoder_NV5x) {
                            block->decodeBlockNV5x(&colors);
                        }
                    }
                    else if (format == nvtt::Format_BC2)
                    {
                        const BlockDXT3 * block = (const BlockDXT3 *)ptr;

                        if (decoder == Decoder_D3D10) {
                            block->decodeBlock(&colors, false);
                        }
                        else if (decoder == Decoder_D3D9) {
                            block->decodeBlock(&colors, false);
                        }
                        else if (decoder == Decoder_NV5x) {
                            block->decodeBlockNV5x(&colors);
                        }
                    }
                    else if (format == nvtt::Format_BC3 || format == nvtt::Format_BC3n || format == nvtt::Format_BC3_RGBM)
                    {
                        const BlockDXT5 * block = (const BlockDXT5 *)ptr;

                        if (decoder == Decoder_D3D10) {
                            block->decodeBlock(&colors, false);
                        }
                        else if (decoder == Decoder_D3D9) {
                            block->decodeBlock(&colors, false);
                        }
                        else if (decoder == Decoder_NV5x) {
                            block->decodeBlockNV5x(&colors);
                        }
                    }
                    else if (format == nvtt::Format_BC4)
                    {
                        const BlockATI1 * block = (const BlockATI1 *)ptr;
                        block->decodeBlock(&colors, decoder == Decoder_D3D9);
                    }
                    else if (format == nvtt::Format_BC5)
                    {
                        const BlockATI2 * block = (const BlockATI2 *)ptr;
                        block->decodeBlock(&colors, decoder == Decoder_D3D9);
                    }
                    else if (format == nvtt::Format_BC7)
                    {
                        const BlockBC7 * block = (const BlockBC7 *)ptr;
                        block->decodeBlock(&colors);
                    }
                    else
                    {
                        nvDebugCheck(false);
                    }

                    for (int yy = 0; yy < 4; yy++)
                    {
                        for (int xx = 0; xx < 4; xx++)
                        {
                            Color32 c = colors.color(xx, yy);

                            if (x * 4 + xx < w && y * 4 + yy < h)
                            {
                                m->image->pixel(0, x*4 + xx, y*4 + yy, 0) = float(c.r) * 1.0f/255.0f;
                                m->image->pixel(1, x*4 + xx, y*4 + yy, 0) = float(c.g) * 1.0f/255.0f;
                                m->image->pixel(2, x*4 + xx, y*4 + yy, 0) = float(c.b) * 1.0f/255.0f;
                                m->image->pixel(3, x*4 + xx, y*4 + yy, 0) = float(c.a) * 1.0f/255.0f;
                            }
                        }
                    }

                    ptr += bs;
                }
            }
        }
    }
    CATCH {
        return false;
    }

    return true;
}


static void getDefaultFilterWidthAndParams(int filter, float * filterWidth, float params[2])
{
    if (filter == ResizeFilter_Box) {
        *filterWidth = 0.5f;
    }
    else if (filter == ResizeFilter_Triangle) {
        *filterWidth = 1.0f;
    }
    else if (filter == ResizeFilter_Kaiser)
    {
        *filterWidth = 3.0f;
        params[0] = 4.0f;
        params[1] = 1.0f;
    }
    else //if (filter == ResizeFilter_Mitchell)
    {
        *filterWidth = 2.0f;
        params[0] = 1.0f / 3.0f;
        params[1] = 1.0f / 3.0f;
    }
}

void Surface::resize(int w, int h, int d, ResizeFilter filter)
{
    float filterWidth;
    float params[2];
    getDefaultFilterWidthAndParams(filter, &filterWidth, params);

    resize(w, h, d, filter, filterWidth, params);
}

void Surface::resize(int w, int h, int d, ResizeFilter filter, float filterWidth, const float * params)
{
    if (isNull() || (w == width() && h == height() && d == depth())) {
        return;
    }

    detach();

    FloatImage * img = m->image;

    FloatImage::WrapMode wrapMode = (FloatImage::WrapMode)m->wrapMode;

    if (m->alphaMode == AlphaMode_Transparency)
    {
        if (filter == ResizeFilter_Box)
        {
            BoxFilter filter(filterWidth);
            img = img->resize(filter, w, h, d, wrapMode, 3);
        }
        else if (filter == ResizeFilter_Triangle)
        {
            TriangleFilter filter(filterWidth);
            img = img->resize(filter, w, h, d, wrapMode, 3);
        }
        else if (filter == ResizeFilter_Kaiser)
        {
            KaiserFilter filter(filterWidth);
            if (params != NULL) filter.setParameters(params[0], params[1]);
            img = img->resize(filter, w, h, d, wrapMode, 3);
        }
        else //if (filter == ResizeFilter_Mitchell)
        {
            nvDebugCheck(filter == ResizeFilter_Mitchell);
            MitchellFilter filter;
            if (params != NULL) filter.setParameters(params[0], params[1]);
            img = img->resize(filter, w, h, d, wrapMode, 3);
        }
    }
    else
    {
        if (filter == ResizeFilter_Box)
        {
            BoxFilter filter(filterWidth);
            img = img->resize(filter, w, h, d, wrapMode);
        }
        else if (filter == ResizeFilter_Triangle)
        {
            TriangleFilter filter(filterWidth);
            img = img->resize(filter, w, h, d, wrapMode);
        }
        else if (filter == ResizeFilter_Kaiser)
        {
            KaiserFilter filter(filterWidth);
            if (params != NULL) filter.setParameters(params[0], params[1]);
            img = img->resize(filter, w, h, d, wrapMode);
        }
        else //if (filter == ResizeFilter_Mitchell)
        {
            nvDebugCheck(filter == ResizeFilter_Mitchell);
            MitchellFilter filter;
            if (params != NULL) filter.setParameters(params[0], params[1]);
            img = img->resize(filter, w, h, d, wrapMode);
        }
    }

    delete m->image;
    m->image = img;
}

void Surface::resizeMakeSquare(int maxExtent, RoundMode roundMode, ResizeFilter filter)
{
    if (isNull()) return;

    float filterWidth;
    float params[2];
    getDefaultFilterWidthAndParams(filter, &filterWidth, params);

    int w = m->image->width();
    int h = m->image->height();
    int d = m->image->depth();

    getTargetExtent(&w, &h, &d, maxExtent, roundMode, m->type, nvtt::ShapeRestriction_Square);

    if (m->type == TextureType_2D) 
    {
        nvDebugCheck(d==1);
    }
    else if (m->type == TextureType_Cube)
    {
        nvDebugCheck(d==1);
        nvDebugCheck(w==h);
    }

    resize(w, h, d, filter, filterWidth, params);
}

void Surface::resize(int maxExtent, RoundMode roundMode, ResizeFilter filter)
{
    float filterWidth;
    float params[2];
    getDefaultFilterWidthAndParams(filter, &filterWidth, params);

    resize(maxExtent, roundMode, filter, filterWidth, params);
}

void Surface::resize(int maxExtent, RoundMode roundMode, ResizeFilter filter, float filterWidth, const float * params)
{
    if (isNull()) return;

    int w = m->image->width();
    int h = m->image->height();
    int d = m->image->depth();

    getTargetExtent(&w, &h, &d, maxExtent, roundMode, m->type);

    resize(w, h, d, filter, filterWidth, params);
}


float rmsBilinearError(nvtt::Surface original, nvtt::Surface resized) {
    return nv::rmsBilinearColorError(original.m->image, resized.m->image, (FloatImage::WrapMode)original.wrapMode(), original.alphaMode() == AlphaMode_Transparency);
}


void Surface::autoResize(float errorTolerance, RoundMode mode, ResizeFilter filter)
{
    Surface original = *this;
    Surface resized = original;

    int w = width();
    int h = height();
    int d = depth();

    w = (w + 1) / 2;
    h = (h + 1) / 2;
    d = (d + 1) / 2;

    while (w >= 4 && h >= 4 && d >= 1) {

        // Resize always from original? This is more expensive, but should produce higher quality.
        //resized = original;
        
        resized.resize(w, h, d, filter);

#if 0
        // Scale back up to original size. @@ Upscaling not implemented!
        Surface restored = resized;
        restored.resize(original.width(), original.height(), original.depth(), ResizeFilter_Triangle);

        float error;
        if (isNormalMap()) {
            error = nvtt::angularError(original, restored);
        }
        else {
            error = nvtt::rmsError(original, restored);
        }
#else
        float error = rmsBilinearError(original, resized);
#endif

        if (error < errorTolerance) {
            *this = resized;
            nvDebug("image resized %dx%d -> %dx%d (error=%f)\n", original.width(), original.height(), w, h, error);
        }
        else {
            nvDebug("image can't be resized further (error=%f)\n", error);
            break;
        }

        w = (w + 1) / 2;
        h = (h + 1) / 2;
        d = (d + 1) / 2;
    }
}

bool Surface::canMakeNextMipmap(int min_size /*= 1*/)
{
    if (isNull()) return false;

    return nv::canMakeNextMipmap(width(), height(), depth(), min_size);
}


bool Surface::buildNextMipmap(MipmapFilter filter, int min_size /*= 1*/)
{
    float filterWidth;
    float params[2];
    getDefaultFilterWidthAndParams(filter, &filterWidth, params);

    return buildNextMipmap(filter, filterWidth, params, min_size);
}

bool Surface::buildNextMipmap(MipmapFilter filter, float filterWidth, const float * params, int min_size /*= 1*/)
{
    if (!canMakeNextMipmap(min_size)) {
        return false;
    }

    detach();

    FloatImage * img = m->image;

    FloatImage::WrapMode wrapMode = (FloatImage::WrapMode)m->wrapMode;

    if (m->alphaMode == AlphaMode_Transparency)
    {
        if (filter == MipmapFilter_Box)
        {
            BoxFilter filter(filterWidth);
            img = img->downSample(filter, wrapMode, 3);
        }
        else if (filter == MipmapFilter_Triangle)
        {
            TriangleFilter filter(filterWidth);
            img = img->downSample(filter, wrapMode, 3);
        }
        else if (filter == MipmapFilter_Kaiser)
        {
            nvDebugCheck(filter == MipmapFilter_Kaiser);
            KaiserFilter filter(filterWidth);
            if (params != NULL) filter.setParameters(/*alpha=*/params[0], /*stretch=*/params[1]);
            img = img->downSample(filter, wrapMode, 3);
        }
    }
    else
    {
        if (filter == MipmapFilter_Box)
        {
            if (filterWidth == 0.5f && img->depth() == 1) {
                img = img->fastDownSample();
            }
            else {
                BoxFilter filter(filterWidth);
                img = img->downSample(filter, wrapMode);
            }
        }
        else if (filter == MipmapFilter_Triangle)
        {
            TriangleFilter filter(filterWidth);
            img = img->downSample(filter, wrapMode);
        }
        else //if (filter == MipmapFilter_Kaiser)
        {
            nvDebugCheck(filter == MipmapFilter_Kaiser);
            KaiserFilter filter(filterWidth);
            if (params != NULL) filter.setParameters(params[0], params[1]);
            img = img->downSample(filter, wrapMode);
        }
    }

    delete m->image;
    m->image = img;

    return true;
}

bool Surface::buildNextMipmapSolidColor(const float * const color_components)
{
    if (isNull() || (width() == 1 && height() == 1 && depth() == 1)) {
        return false;
    }

    detach();

    FloatImage * img = new FloatImage();
    const uint w = max(1, m->image->m_width / 2);
    const uint h = max(1, m->image->m_height / 2);
    img->allocate(m->image->m_componentCount, w, h);

    for(uint c = 0; c < img->m_componentCount; c++)
    {
        img->clear(c, color_components[c]);
    }

    delete m->image;
    m->image = img;

    return true;
}

void Surface::canvasSize(int w, int h, int d)
{
    nvDebugCheck(w > 0 && h > 0 && d > 0);

    if (isNull() || (w == width() && h == height() && d == depth())) {
        return;
    }

    detach();

    FloatImage * img = m->image;

    FloatImage * new_img = new FloatImage;
    new_img->allocate(4, w, h, d);
    new_img->clear();

    w = min(uint(w), img->width());
    h = min(uint(h), img->height());
    d = min(uint(d), img->depth());

    for (int z = 0; z < d; z++) {
        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                new_img->pixel(0, x, y, z) = img->pixel(0, x, y, z);
                new_img->pixel(1, x, y, z) = img->pixel(1, x, y, z);
                new_img->pixel(2, x, y, z) = img->pixel(2, x, y, z);
                new_img->pixel(3, x, y, z) = img->pixel(3, x, y, z);
            }
        }
    }

    delete m->image;
    m->image = new_img;
    m->type = (d == 1) ? TextureType_2D : TextureType_3D;
}


// Color transforms.
void Surface::toLinear(float gamma)
{
    if (isNull()) return;
    if (equal(gamma, 1.0f)) return;

    detach();

    m->image->toLinear(0, 3, gamma);
}

void Surface::toGamma(float gamma)
{
    if (isNull()) return;
    if (equal(gamma, 1.0f)) return;

    detach();

    m->image->toGamma(0, 3, gamma);
}

void Surface::toLinear(int channel, float gamma)
{
    if (isNull()) return;
    if (equal(gamma, 1.0f)) return;

    detach();

    m->image->toLinear(channel, 1, gamma);
}

void Surface::toGamma(int channel, float gamma)
{
    if (isNull()) return;
    if (equal(gamma, 1.0f)) return;

    detach();

    m->image->toGamma(channel, 1, gamma);
}



static float toSrgb(float f) {
    if (isNan(f))               f = 0.0f;
    else if (f <= 0.0f)         f = 0.0f;
    else if (f <= 0.0031308f)   f = 12.92f * f;
    else if (f <= 1.0f)         f = (powf(f, 0.41666f) * 1.055f) - 0.055f;
    else                        f = 1.0f;
    return f;
}

// sRGB approximation from: http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
static float toSrgbFast(float f) {
    f = saturate(f);
    float s1 = sqrtf(f);
    float s2 = sqrtf(s1);
    float s3 = sqrtf(s2);
    return 0.662002687f * s1 + 0.684122060f * s2 - 0.323583601f * s3 - 0.0225411470f * f;
}

void Surface::toSrgb() {
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;

    const uint count = 3 * img->pixelCount();
    float * channel = img->channel(0);
    for (uint i = 0; i < count; i++) {
        channel[i] = ::toSrgb(channel[i]);
    }
}

void Surface::toSrgbFast() {
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;

    const uint count = 3 * img->pixelCount();
    float * channel = img->channel(0);
    for (uint i = 0; i < count; i++) {
        channel[i] = ::toSrgbFast(channel[i]);
    }
    //parallel_for(count, 128, [=](int i) {
    //    channel[i] = toSrgbFast(channel[i]);
    //});
}


static float fromSrgb(float f) {
    if (f < 0.0f)           f = 0.0f;
    else if (f < 0.04045f)  f = f / 12.92f;
    else if (f <= 1.0f)     f = powf((f + 0.055f) / 1.055f, 2.4f);
    else                    f = 1.0f;
    return f;
}

// sRGB approximation from: http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
static float fromSrgbFast(float f) {
    f = saturate(f);
    return f * (f * (f * 0.305306011f + 0.682171111f) + 0.012522878f);
}


void Surface::toLinearFromSrgb() {
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;

    const uint count = 3  *img->pixelCount();
    float * channel = img->channel(0);
    for (uint i = 0; i < count; i++) {
        channel[i] = ::fromSrgb(channel[i]);
    }
    //parallel_for(count, 128, [=](int i) {
    //    channel[i] = ::fromSrgb(channel[i]);
    //});
}

void Surface::toLinearFromSrgbFast() {
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;

    const uint count = 3 * img->pixelCount();
    float * channel = img->channel(0);
    for (uint i = 0; i < count; i++) {
        channel[i] = ::fromSrgbFast(channel[i]);
    }
    //parallel_for(count, 128, [=](int i) {
    //    channel[i] = ::fromSrgbFast(channel[i]);
    //});
}


static float toXenonSrgb(float f) {
    if (f < 0)                  f = 0;
    else if (f < (1.0f/16.0f))  f = 4.0f * f;
    else if (f < (1.0f/8.0f))   f = 0.25f  + 2.0f * (f - 0.0625f);
    else if (f < 0.5f)          f = 0.375f + 1.0f * (f - 0.125f);
    else if (f < 1.0f)          f = 0.75f  + 0.5f * (f - 0.50f);
    else                        f = 1.0f;
    return f;
}

void Surface::toXenonSrgb()
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;

    const uint count = img->pixelCount();
    for (uint c = 0; c < 3; c++) {
        float * channel = img->channel(c);
        for (uint i = 0; i < count; i++) {
            channel[i] = ::toXenonSrgb(channel[i]);
        }
    }
}


void Surface::transform(const float w0[4], const float w1[4], const float w2[4], const float w3[4], const float offset[4])
{
    if (isNull()) return;

    detach();

    Matrix xform(
        Vector4(w0[0], w0[1], w0[2], w0[3]),
        Vector4(w1[0], w1[1], w1[2], w1[3]),
        Vector4(w2[0], w2[1], w2[2], w2[3]),
        Vector4(w3[0], w3[1], w3[2], w3[3]));

    Vector4 voffset(offset[0], offset[1], offset[2], offset[3]);

    m->image->transform(0, xform, voffset);
}

// R, G, B, A, 1, 0, -1
void Surface::swizzle(int r, int g, int b, int a)
{
    if (isNull()) return;
    if (r == 0 && g == 1 && b == 2 && a == 3) return;

    detach();

    m->image->swizzle(0, r, g, b, a);
}

// color * scale + bias
void Surface::scaleBias(int channel, float scale, float bias)
{
    if (isNull()) return;
    if (equal(scale, 1.0f) && equal(bias, 0.0f)) return;

    detach();

    m->image->scaleBias(channel, 1, scale, bias);
}

void Surface::clamp(int channel, float low, float high)
{
    if (isNull()) return;

    detach();

    m->image->clamp(channel, 1, low, high);
}

void Surface::blend(float red, float green, float blue, float alpha, float t)
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);
    float * a = img->channel(3);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++)
    {
        r[i] = lerp(r[i], red, t);
        g[i] = lerp(g[i], green, t);
        b[i] = lerp(b[i], blue, t);
        a[i] = lerp(a[i], alpha, t);
    }
}

void Surface::premultiplyAlpha()
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);
    float * a = img->channel(3);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++)
    {
        r[i] *= a[i];
        g[i] *= a[i];
        b[i] *= a[i];
    }
}


void Surface::toGreyScale(float redScale, float greenScale, float blueScale, float alphaScale)
{
    if (isNull()) return;

    detach();

    float sum = redScale + greenScale + blueScale + alphaScale;
    redScale /= sum;
    greenScale /= sum;
    blueScale /= sum;
    alphaScale /= sum;

    FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);
    float * a = img->channel(3);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++)
    {
        float grey = r[i] * redScale + g[i] * greenScale + b[i] * blueScale + a[i] * alphaScale;
        a[i] = b[i] = g[i] = r[i] = grey;
    }
}

// Draw colored border.
void Surface::setBorder(float r, float g, float b, float a)
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;
    const uint w = img->width();
    const uint h = img->height();
    const uint d = img->depth();

    for (uint z = 0; z < d; z++)
    {
        for (uint i = 0; i < w; i++)
        {
            img->pixel(0, i, 0, z) = r;
            img->pixel(1, i, 0, z) = g;
            img->pixel(2, i, 0, z) = b;
            img->pixel(3, i, 0, z) = a;

            img->pixel(0, i, h-1, z) = r;
            img->pixel(1, i, h-1, z) = g;
            img->pixel(2, i, h-1, z) = b;
            img->pixel(3, i, h-1, z) = a;
        }

        for (uint i = 0; i < h; i++)
        {
            img->pixel(0, 0, i, z) = r;
            img->pixel(1, 0, i, z) = g;
            img->pixel(2, 0, i, z) = b;
            img->pixel(3, 0, i, z) = a;

            img->pixel(0, w-1, i, z) = r;
            img->pixel(1, w-1, i, z) = g;
            img->pixel(2, w-1, i, z) = b;
            img->pixel(3, w-1, i, z) = a;
        }
    }
}

// Fill image with the given color.
void Surface::fill(float red, float green, float blue, float alpha)
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);
    float * a = img->channel(3);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++) r[i] = red;
    for (uint i = 0; i < count; i++) g[i] = green;
    for (uint i = 0; i < count; i++) b[i] = blue;
    for (uint i = 0; i < count; i++) a[i] = alpha;
}


void Surface::scaleAlphaToCoverage(float coverage, float alphaRef/*= 0.5f*/, int alpha_channel/*= 3*/)
{
    if (isNull()) return;

    detach();

    alphaRef = nv::clamp(alphaRef, 1.0f/256, 255.0f/256);

    m->image->scaleAlphaToCoverage(coverage, alphaRef, alpha_channel);
}

/*bool Surface::normalizeRange(float * rangeMin, float * rangeMax)
{
    if (m->image == NULL) return false;

    range(0, rangeMin, rangeMax);

    if (*rangeMin == *rangeMax) {
        // Single color image.
        return false;
    }

    const float scale = 1.0f / (*rangeMax - *rangeMin);
    const float bias = *rangeMin * scale;

    if (range.x == 0.0f && range.y == 1.0f) {
        // Already normalized.
        return true;
    }

    detach();

    // Scale to range.
    img->scaleBias(0, 4, scale, bias);
    //img->clamp(0, 4, 0.0f, 1.0f);

    return true;
}*/

// Ideally you should compress/quantize the RGB and M portions independently.
// Once you have M quantized, you would compute the corresponding RGB and quantize that.
void Surface::toRGBM(float range/*= 1*/, float threshold/*= 0.25*/)
{
    if (isNull()) return;

    detach();

    threshold = ::clamp(threshold, 1e-6f, 1.0f);

    FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);
    float * a = img->channel(3);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++) {
        float R = nv::clamp(r[i], 0.0f, 1.0f);
        float G = nv::clamp(g[i], 0.0f, 1.0f);
        float B = nv::clamp(b[i], 0.0f, 1.0f);

#if 0
        // Baseline, no compression:
        r[i] = R;
        g[i] = G;
        b[i] = B;
        a[i] = 1;

#elif 0
        float M = max(max(R, G), max(B, threshold));

        r[i] = R / M;
        g[i] = G / M;
        b[i] = B / M;

        a[i] = (M - threshold) / (1 - threshold);

#else
        // The optimal compressor produces the best results, but can introduce interpolation errors!
        float bestM;
        float bestError = FLT_MAX;

        //float range = 15;  // 4 bit quantization.
        //int irange = 16;
        float range = 255;  // 8 bit quantization.
        int irange = 256;


        float M = max(max(R, G), max(B, threshold));
        int iM = ftoi_ceil((M - threshold) / (1 - threshold) * range);

        //for (int m = 0; m < 256; m++) {                           // If we use the entire search space, interpolation errors are very likely to occur.
        for (int m = max(iM-16, 0); m < min(iM+16, irange); m++) {     // If we constrain the search space, these errors disappear.
        //for (int m = max(iM-4, 0); m < min(iM+4, irange); m++) {     // If we constrain the search space, these errors disappear.
            float fm = float(m) / range;

            // Decode M
            float M = fm * (1 - threshold) + threshold;

            // Encode.
            int ir = ftoi_round(range * nv::saturate(R / M));
            int ig = ftoi_round(range * nv::saturate(G / M));
            int ib = ftoi_round(range * nv::saturate(B / M));

            // Decode.
            float fr = (float(ir) / range) * M;
            float fg = (float(ig) / range) * M;
            float fb = (float(ib) / range) * M;

            // Measure error.
            float error = square(R-fr) + square(G-fg) + square(B-fb);

            if (error < bestError) {
                bestError = error;
                bestM = M;
            }
        }

        M = bestM;
        r[i] = nv::saturate(R / M);
        g[i] = nv::saturate(G / M);
        b[i] = nv::saturate(B / M);
        a[i] = (M - threshold) / (1 - threshold);
#endif
    }
}

// @@ IC: Dubious merge. Review!
void Surface::fromRGBM(float range/*= 1*/, float threshold/*= 0.25*/)
{
    if (isNull()) return;

    detach();

    threshold = ::clamp(threshold, 1e-6f, 1.0f);

	FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);
    float * a = img->channel(3);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++) {
        float M = a[i] * (range - threshold) + threshold;

        r[i] *= M;
        g[i] *= M;
        b[i] *= M;
        a[i] = 1.0f;
    }
}

// This is dumb way to encode luminance only values.
void Surface::toLM(float range/*= 1*/, float threshold/*= 0.25*/)
{
    if (isNull()) return;

    detach();

    threshold = ::clamp(threshold, 1e-6f, 1.0f);

    FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);
    float * a = img->channel(3);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++) {
        float R = nv::clamp(r[i], 0.0f, 1.0f);
        float G = nv::clamp(g[i], 0.0f, 1.0f);
        float B = nv::clamp(b[i], 0.0f, 1.0f);

        float M = max(max(R, G), max(B, threshold));

        float L = (R + G + B) / 3;
        r[i] = L / M;
        b[i] = L / M;
        g[i] = L / M;
        a[i] = (M - threshold) / (1 - threshold);
    }
}


static Color32 toRgbe8(float r, float g, float b)
{
    Color32 c;
    float v = max(max(r, g), b);
    if (v < 1e-32) {
        c.r = c.g = c.b = c.a = 0;
    }
    else {
        int e;
        v = frexpf(v, &e) * 256.0f / v;
        c.r = uint8(clamp(r * v, 0.0f, 255.0f));
        c.g = uint8(clamp(g * v, 0.0f, 255.0f));
        c.b = uint8(clamp(b * v, 0.0f, 255.0f));
        c.a = e + 128;
    }

    return c;
}


/*
  Alen Ladavac @ GDAlgorithms-list on Feb 7, 2007:
    One trick that we use to alleviate such problems is to use RGBE5.3 -
    i.e. have a fixed point exponent. Note that it is not enough to just
    shift the exponent up for 3 bits, but you actually have to convert
    each pixel in the RGBE8 texture by unpacking it to floats and then
    repacking it with a non-integer exponent, which gives different
    mantissas as well. Now your jumps in exponent are much smaller, thus
    the bands are not that noticeable. It is still not as good as FP16,
    but it is much better than RGBE8. I hope this explanation is
    understandable, if not I can fill in more details.

    Though there still are some bands, you can get an even better
    precision if you upload that same texture as RGBA16, because you'll
    get even more interpolation then, and it works good as a scalable
    option for people with more GPU RAM). Alternatively, when some of the
    future cards (hopefully, because I'm trying to lobby for that
    everywhere :) ), start returning more than 8 bits, your scenes will
    automatically look better even without using RGBA16.

  Jon Watte:
    The interpolation of 5.3 is the same as that of 8 bits, because it's a
    fixed point format.

    The reason using 5.3 helps, is that each bit of quantization in the
    interpolation only means 1/8th of a fully significant bit. The
    quantization still happens, it's just less visible. The trade-off is
    that you get less dynamic range.

  Alen Ladavac:
    True, but it is just a small part of the improvement. The greater part
    is that RGB values have to be calculated according to the fractional
    exponent. With integer exponent, the RGB values jump by a factor of 2
    when each bit changes in exponent, and 5.3 with correct adjustment of
    RGB lowers this jump to be about 1.09, which is much better. I may not
    be entirely correct on the numbers, which I'm pulling out from my
    memory now, but it's a rough estimate.
*/
/* Ward's version:
static Color32 toRgbe8(float r, float g, float b)
{
    Color32 c;
    float v = max(max(r, g), b);
    if (v < 1e-32) {
        c.r = c.g = c.b = c.a = 0;
    }
    else {
        int e;
        v = frexp(v, &e) * 256.0f / v;
        c.r = uint8(clamp(r * v, 0.0f, 255.0f));
        c.g = uint8(clamp(g * v, 0.0f, 255.0f));
        c.b = uint8(clamp(b * v, 0.0f, 255.0f));
        c.a = e + 128;
    }

    return c;
}
*/

// For R9G9B9E5, use toRGBE(9, 5), for Ward's RGBE, use toRGBE(8, 8)
// @@ Note that most Radiance HDR loaders use an exponent bias of 128 instead of 127! This implementation
// matches the OpenGL extension.
void Surface::toRGBE(int mantissaBits, int exponentBits)
{
    // According to the OpenGL extension:
    // http://www.opengl.org/registry/specs/EXT/texture_shared_exponent.txt
    //
    // Components red, green, and blue are first clamped (in the process,
    // mapping NaN to zero) so:
    //
    //     red_c   = max(0, min(sharedexp_max, red))
    //     green_c = max(0, min(sharedexp_max, green))
    //     blue_c  = max(0, min(sharedexp_max, blue))
    //
    // where sharedexp_max is (2^N-1)/2^N * 2^(Emax-B), N is the number
    // of mantissa bits per component, Emax is the maximum allowed biased
    // exponent value (careful: not necessarily 2^E-1 when E is the number of
    // exponent bits), bits, and B is the exponent bias.  For the RGB9_E5_EXT
    // format, N=9, Emax=31, and B=15.
    //
    // The largest clamped component, max_c, is determined:
    //
    //     max_c = max(red_c, green_c, blue_c)
    //
    // A preliminary shared exponent is computed:
    //
    //     exp_shared_p = max(-B-1, floor(log2(max_c))) + 1 + B
    //
    // A refined shared exponent is then computed as:
    //
    //     max_s   = floor(max_c   / 2^(exp_shared_p - B - N) + 0.5)
    //
    //                  { exp_shared_p,    0 <= max_s <  2^N
    //     exp_shared = {
    //                  { exp_shared_p+1,       max_s == 2^N
    //
    // These integers values in the range 0 to 2^N-1 are then computed:
    //
    //     red_s   = floor(red_c   / 2^(exp_shared - B - N) + 0.5)
    //     green_s = floor(green_c / 2^(exp_shared - B - N) + 0.5)
    //     blue_s  = floor(blue_c  / 2^(exp_shared - B - N) + 0.5)

    if (isNull()) return;

    detach();

    // mantissaBits = N
    // exponentBits = E
    // exponentMax = Emax
    // exponentBias = B
    // maxValue = sharedexp_max

    // max exponent: 5 -> 31, 8 -> 255
    const int exponentMax = (1 << exponentBits) - 1;

    // exponent bias: 5 -> 15, 8 -> 127
    const int exponentBias = (1 << (exponentBits - 1)) - 1;

    // Maximum representable value: 5 -> 63488, 8 -> HUGE
    const float maxValue = float(exponentMax) / float(exponentMax + 1) * float(1 << (exponentMax - exponentBias));


    FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);
    float * a = img->channel(3);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++) {
        // Clamp components:
        float R = ::clamp(r[i], 0.0f, maxValue);
        float G = ::clamp(g[i], 0.0f, maxValue);
        float B = ::clamp(b[i], 0.0f, maxValue);

        // Compute max:
        float M = max3(R, G, B);

        // Preliminary exponent:
        int E = max(- exponentBias - 1, floatExponent(M)) + 1 + exponentBias;
        nvDebugCheck(E >= 0 && E < (1 << exponentBits));

        double denom = pow(2.0, double(E - exponentBias - mantissaBits));

        // Refine exponent:
        int m = ftoi_round(float(M / denom));
        nvDebugCheck(m <= (1 << mantissaBits));

        if (m == (1 << mantissaBits)) {
            denom *= 2;
            E += 1;
            nvDebugCheck(E < (1 << exponentBits));
        }

        R = floatRound(float(R / denom));
        G = floatRound(float(G / denom));
        B = floatRound(float(B / denom));

        nvDebugCheck(R >= 0 && R < (1 << mantissaBits));
        nvDebugCheck(G >= 0 && G < (1 << mantissaBits));
        nvDebugCheck(B >= 0 && B < (1 << mantissaBits));

        // Store as normalized float.
        r[i] = R / ((1 << mantissaBits) - 1);
        g[i] = G / ((1 << mantissaBits) - 1);
        b[i] = B / ((1 << mantissaBits) - 1);
        a[i] = float(E) / ((1 << exponentBits) - 1);
    }
}

void Surface::fromRGBE(int mantissaBits, int exponentBits)
{
    // According to the OpenGL extension:
    // http://www.opengl.org/registry/specs/EXT/texture_shared_exponent.txt
    //
    // The 1st, 2nd, 3rd, and 4th components are called
    // p_red, p_green, p_blue, and p_exp respectively and are treated as
    // unsigned integers.  These are then used to compute floating-point
    // RGB components (ignoring the "Conversion to floating-point" section
    // below in this case) as follows:
    //
    //   red   = p_red   * 2^(p_exp - B - N)
    //   green = p_green * 2^(p_exp - B - N)
    //   blue  = p_blue  * 2^(p_exp - B - N)
    //
    // where B is 15 (the exponent bias) and N is 9 (the number of mantissa
    // bits)."


    // int exponent = v.field.biasedexponent - RGB9E5_EXP_BIAS - RGB9E5_MANTISSA_BITS;
    // float scale = (float) pow(2, exponent);
    //
    // retval[0] = v.field.r * scale;
    // retval[1] = v.field.g * scale;
    // retval[2] = v.field.b * scale;


    if (isNull()) return;

    detach();

    // exponent bias: 5 -> 15, 8 -> 127
    const int exponentBias = (1 << (exponentBits - 1)) - 1;

    FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);
    float * a = img->channel(3);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++) {
        // Expand normalized float to to 9995
        int R = ftoi_round(r[i] * ((1 << mantissaBits) - 1));
        int G = ftoi_round(g[i] * ((1 << mantissaBits) - 1));
        int B = ftoi_round(b[i] * ((1 << mantissaBits) - 1));
        int E = ftoi_round(a[i] * ((1 << exponentBits) - 1));

        //float scale = ldexpf(1.0f, E - exponentBias - mantissaBits);
        float scale = powf(2, float(E - exponentBias - mantissaBits));

        r[i] = R * scale;
        g[i] = G * scale;
        b[i] = B * scale;
        a[i] = 1;
    }
}

// Y is in the [0, 1] range, while CoCg are in the [-1, 1] range.
void Surface::toYCoCg()
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);
    float * a = img->channel(3);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++) {
        float R = r[i];
        float G = g[i];
        float B = b[i];

        float Y = (2*G + R + B) * 0.25f;
        float Co = (R - B);
        float Cg = (2*G - R - B) * 0.5f;

        r[i] = Co;
        g[i] = Cg;
        b[i] = 1.0f;
        a[i] = Y;
    }
}

// img.toYCoCg();
// img.blockScaleCoCg();
// img.scaleBias(0, 0.5, 0.5);
// img.scaleBias(1, 0.5, 0.5);

// @@ Add support for threshold.
// We could do something to prevent scale values from adjacent blocks from being too different to each other
// and minimize bilinear interpolation artifacts.
void Surface::blockScaleCoCg(int bits/*= 5*/, float threshold/*= 0.0*/)
{
    if (isNull() || depth() != 1) return;

    detach();

    FloatImage * img = m->image;
    const uint w = img->width();
    const uint h = img->height();
    const uint bw = max(1U, w/4);
    const uint bh = max(1U, h/4);

    for (uint bj = 0; bj < bh; bj++) {
        for (uint bi = 0; bi < bw; bi++) {

            // Compute per block scale.
            float m = 1.0f / 255.0f;
            for (uint j = 0; j < 4; j++) {
                const uint y = bj*4 + j;
                if (y >= h) continue;

                for (uint i = 0; i < 4; i++) {
                    const uint x = bi*4 + i;
                    if (x >= w) continue;

                    float Co = img->pixel(0, x, y, 0);
                    float Cg = img->pixel(1, x, y, 0);

                    m = max(m, fabsf(Co));
                    m = max(m, fabsf(Cg));
                }
            }

            float scale = PixelFormat::quantizeCeil(m, bits, 8);
            nvDebugCheck(scale >= m);

            // Store block scale in blue channel and scale CoCg.
            for (uint j = 0; j < 4; j++) {
                for (uint i = 0; i < 4; i++) {
                    uint x = min(bi*4 + i, w);
                    uint y = min(bj*4 + j, h);

                    float & Co = img->pixel(0, x, y, 0);
                    float & Cg = img->pixel(1, x, y, 0);

                    Co /= scale;
                    nvDebugCheck(fabsf(Co) <= 1.0f);

                    Cg /= scale;
                    nvDebugCheck(fabsf(Cg) <= 1.0f);

                    img->pixel(2, x, y, 0) = scale;
                }
            }
        }
    }
}

void Surface::fromYCoCg()
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);
    float * a = img->channel(3);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++) {
        float Co = r[i];
        float Cg = g[i];
        float scale = b[i] * 0.5f;
        float Y = a[i];

        Co *= scale;
        Cg *= scale;

        float R = Y + Co - Cg;
        float G = Y + Cg;
        float B = Y - Co - Cg;

        r[i] = R;
        g[i] = G;
        b[i] = B;
        a[i] = 1.0f;
    }
}

void Surface::toLUVW(float range/*= 1.0f*/)
{
    if (isNull()) return;

    detach();

    float irange = 1.0f / range;

    FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);
    float * a = img->channel(3);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++) {
        float R = nv::clamp(r[i] * irange, 0.0f, 1.0f);
        float G = nv::clamp(g[i] * irange, 0.0f, 1.0f);
        float B = nv::clamp(b[i] * irange, 0.0f, 1.0f);

        float L = max(sqrtf(R*R + G*G + B*B), 1e-6f); // Avoid division by zero.

        r[i] = R / L;
        g[i] = G / L;
        b[i] = B / L;
        a[i] = L / sqrtf(3);
    }
}

void Surface::fromLUVW(float range/*= 1.0f*/)
{
    // Decompression is the same as in RGBM.
    fromRGBM(range * sqrtf(3));
}

void Surface::abs(int channel)
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;
    float * c = img->channel(channel);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++) {
        c[i] = fabsf(c[i]);
    }
}

void Surface::convolve(int channel, int kernelSize, float * kernelData)
{
    if (isNull()) return;

    detach();

    Kernel2 k(kernelSize, kernelData);
    m->image->convolve(k, channel, (FloatImage::WrapMode)m->wrapMode);
}

// Assumes input has already been scaled by exposure.
void Surface::toneMap(ToneMapper tm, float * parameters)
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);
    const uint count = img->pixelCount();

    if (tm == ToneMapper_Linear) {
        // Clamp preserving the hue.
        for (uint i = 0; i < count; i++) {
            float m = max3(r[i], g[i], b[i]);
            if (m > 1.0f) {
                r[i] *= 1.0f / m;
                g[i] *= 1.0f / m;
                b[i] *= 1.0f / m;
            }
        }
    }
    else if (tm == ToneMapper_Reindhart) {
        for (uint i = 0; i < count; i++) {
            r[i] /= r[i] + 1;
            g[i] /= g[i] + 1;
            b[i] /= b[i] + 1;
        }
    }
    else if (tm == ToneMapper_Halo) {
        for (uint i = 0; i < count; i++) {
            r[i] = 1 - exp2f(-r[i]);
            g[i] = 1 - exp2f(-g[i]);
            b[i] = 1 - exp2f(-b[i]);
        }
    }
    else if (tm == ToneMapper_Lightmap) {
        // @@ Goals:
        // Preserve hue.
        // Avoid clamping abrubtly.
        // Minimize color difference along most of the color range. [0, alpha)
        for (uint i = 0; i < count; i++) {
            float m = max3(r[i], g[i], b[i]);
            if (m > 1.0f) {
                r[i] *= 1.0f / m;
                g[i] *= 1.0f / m;
                b[i] *= 1.0f / m;
            }
        }
    }
}

void Surface::toLogScale(int channel, float base) {
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;
    float * c = img->channel(channel);

    float scale = 1.0f / log2f(base);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++) {
        c[i] = log2f(c[i]) * scale;
    }
}

void Surface::fromLogScale(int channel, float base) {
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;
    float * c = img->channel(channel);

    float scale = log2f(base);

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++) {
        c[i] = exp2f(c[i] * scale);
    }
}



/*
void Surface::blockLuminanceScale(float scale)
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;

    //float * r = img->channel(0);
    //float * g = img->channel(1);
    //float * b = img->channel(2);
    //float * a = img->channel(3);

    const uint w = img->width();
    const uint h = img->height();
    const uint bw = max(1U, w/4);
    const uint bh = max(1U, h/4);

    Vector3 L = normalize(Vector3(1, 1, 1));

    for (uint bj = 0; bj < bh; bj++) {
        for (uint bi = 0; bi < bw; bi++) {

            // Compute block centroid.
            Vector3 centroid(0.0f);
            int count = 0;
            for (uint j = 0; j < 4; j++) {
                const uint y = bj*4 + j;
                if (y >= h) continue;

                for (uint i = 0; i < 4; i++) {
                    const uint x = bi*4 + i;
                    if (x >= w) continue;

                    float r = img->pixel(x, y, 0);
                    float g = img->pixel(x, y, 1);
                    float b = img->pixel(x, y, 2);
                    Vector3 rgb(r, g, b);

                    centroid += rgb;
                    count++;
                }
            }

            centroid /= float(count);

            // Project to luminance plane.
            for (uint j = 0; j < 4; j++) {
                const uint y = bj*4 + j;
                if (y >= h) continue;

                for (uint i = 0; i < 4; i++) {
                    const uint x = bi*4 + i;
                    if (x >= w) continue;

                    float & r = img->pixel(x, y, 0);
                    float & g = img->pixel(x, y, 1);
                    float & b = img->pixel(x, y, 2);
                    Vector3 rgb(r, g, b);

                    Vector3 delta = rgb - centroid;

                    delta -= scale * dot(delta, L) * L;

                    r = centroid.x + delta.x;
                    g = centroid.y + delta.y;
                    b = centroid.z + delta.z;
                }
            }
        }
    }
}
*/

/*
void Surface::toJPEGLS()
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);

    const uint count = img->width() * img->height();
    for (uint i = 0; i < count; i++) {
        float R = nv::clamp(r[i], 0.0f, 1.0f);
        float G = nv::clamp(g[i], 0.0f, 1.0f);
        float B = nv::clamp(b[i], 0.0f, 1.0f);

        r[i] = R-G;
        g[i] = G;
        b[i] = B-G;
    }
}

void Surface::fromJPEGLS()
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;
    float * r = img->channel(0);
    float * g = img->channel(1);
    float * b = img->channel(2);

    const uint count = img->width() * img->height();
    for (uint i = 0; i < count; i++) {
        float R = nv::clamp(r[i], -1.0f, 1.0f);
        float G = nv::clamp(g[i], 0.0f, 1.0f);
        float B = nv::clamp(b[i], -1.0f, 1.0f);

        r[i] = R+G;
        g[i] = G;
        b[i] = B+G;
    }
}
*/


// If dither is true, this uses Floyd-Steinberg dithering method.
void Surface::binarize(int channel, float threshold, bool dither)
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;

    if (!dither) {
        float * c = img->channel(channel);
        const uint count = img->pixelCount();
        for (uint i = 0; i < count; i++) {
            c[i] = float(c[i] > threshold);
        }
    }
    else {
        const uint w = img->width();
        const uint h = img->height();
        const uint d = img->depth();

        float * row0 = new float[(w+2)];
        float * row1 = new float[(w+2)];

        // @@ Extend Floyd-Steinberg dithering to 3D properly.
        for (uint z = 0; z < d; z++) {
            memset(row0, 0, sizeof(float)*(w+2));
            memset(row1, 0, sizeof(float)*(w+2));

            for (uint y = 0; y < h; y++) {
                for (uint x = 0; x < w; x++) {

                    float & f = img->pixel(channel, x, y, 0);

                    // Add error and quantize.
                    float qf = float(f + row0[1+x] > threshold);

                    // Compute new error:
                    float diff = f - qf;

                    // Store color.
                    f = qf;

                    // Propagate new error.
                    row0[1+x+1] += (7.0f / 16.0f) * diff;
                    row1[1+x-1] += (3.0f / 16.0f) * diff;
                    row1[1+x+0] += (5.0f / 16.0f) * diff;
                    row1[1+x+1] += (1.0f / 16.0f) * diff;
                }

                swap(row0, row1);
                memset(row1, 0, sizeof(float)*(w+2));
            }
        }

        delete [] row0;
        delete [] row1;
    }
}

// Uniform quantizer.
// Assumes input is in [0, 1] range. Output is in the [0, 1] range, but rounded to the middle of each bin.
// If exactEndPoints is true, [0, 1] are represented exactly, and the correponding bins are half the size, so quantization is not truly uniform.
// When dither is true, this uses Floyd-Steinberg dithering.
void Surface::quantize(int channel, int bits, bool exactEndPoints, bool dither)
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;

    float scale, offset0, offset1;
    if (exactEndPoints) {
        // floor(x*(range-1) + 0.5) / (range-1)
        scale = float((1 << bits) - 1);
        offset0 = 0.5f;
        offset1 = 0.0f;
    }
    else {
        // (floor(x*range) + 0.5) / range
        scale = float(1 << bits);
        offset0 = 0.0f;
        offset1 = 0.5f;
    }

    if (!dither) {
        float * c = img->channel(channel);
        const uint count = img->pixelCount();
        for (uint i = 0; i < count; i++) {
            c[i] = saturate((floorf(c[i] * scale + offset0) + offset1) / scale);
        }
    }
    else {
        const uint w = img->width();
        const uint h = img->height();
        const uint d = img->depth();

        float * row0 = new float[(w+2)];
        float * row1 = new float[(w+2)];

        for (uint z = 0; z < d; z++) {
            memset(row0, 0, sizeof(float)*(w+2));
            memset(row1, 0, sizeof(float)*(w+2));

            for (uint y = 0; y < h; y++) {
                for (uint x = 0; x < w; x++) {

                    float & f = img->pixel(channel, x, y, 0);

                    // Add error and quantize.
                    float qf = saturate((floorf((f + row0[1+x]) * scale + offset0) + offset1) / scale);

                    // Compute new error:
                    float diff = f - qf;

                    // Store color.
                    f = qf;

                    // Propagate new error.
                    row0[1+x+1] += (7.0f / 16.0f) * diff;
                    row1[1+x-1] += (3.0f / 16.0f) * diff;
                    row1[1+x+0] += (5.0f / 16.0f) * diff;
                    row1[1+x+1] += (1.0f / 16.0f) * diff;
                }

                swap(row0, row1);
                memset(row1, 0, sizeof(float)*(w+2));
            }
        }

        delete [] row0;
        delete [] row1;
    }
}



// Set normal map options.
void Surface::toNormalMap(float sm, float medium, float big, float large)
{
    if (isNull()) return;

    detach();

    const Vector4 filterWeights(sm, medium, big, large);

    const FloatImage * img = m->image;
    m->image = nv::createNormalMap(img, (FloatImage::WrapMode)m->wrapMode, filterWeights);

    delete img;

    m->isNormalMap = true;
}

void Surface::normalizeNormalMap()
{
    if (isNull()) return;
    if (!m->isNormalMap) return;

    detach();

    nv::normalizeNormalMap(m->image);
}

void Surface::transformNormals(NormalTransform xform)
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++) {
        float & x = img->pixel(0, i);
        float & y = img->pixel(1, i);
        float & z = img->pixel(2, i);
        Vector3 n(x, y, z);

        n = normalizeSafe(n, Vector3(0.0f), 0.0f);

        if (xform == NormalTransform_Orthographic) {
            n.z = 0.0f;
        }
        else if (xform == NormalTransform_Stereographic) {
            n.x = n.x / (1 + n.z);
            n.y = n.y / (1 + n.z);
            n.z = 0.0f;
        }
        else if (xform == NormalTransform_Paraboloid) {
            float a = (n.x * n.x) + (n.y * n.y);
            float b = n.z;
            float c = -1.0f;
            float discriminant = b * b - 4.0f * a * c;
            float t = (-b + sqrtf(discriminant)) / (2.0f * a);
            n.x = n.x * t;
            n.y = n.y * t;
            n.z = 0.0f;
        }
        else if (xform == NormalTransform_Quartic) {
            // Use Newton's method to solve equation:
            // f(t) = 1 - zt - (x^2+y^2)t^2 + x^2y^2t^4 = 0
            // f'(t) = - z - 2(x^2+y^2)t + 4x^2y^2t^3

            // Initial approximation:
            float a = (n.x * n.x) + (n.y * n.y);
            float b = n.z;
            float c = -1.0f;
            float discriminant = b * b - 4.0f * a * c;
            float t = (-b + sqrtf(discriminant)) / (2.0f * a);

            float d = fabsf(n.z * t - (1 - n.x*n.x*t*t) * (1 - n.y*n.y*t*t));

            while (d > 0.0001) {
                float ft = 1 - n.z * t - (n.x*n.x + n.y*n.y)*t*t + n.x*n.x*n.y*n.y*t*t*t*t;
                float fit = - n.z - 2*(n.x*n.x + n.y*n.y)*t + 4*n.x*n.x*n.y*n.y*t*t*t;
                t -= ft / fit;
                d = fabsf(n.z * t - (1 - n.x*n.x*t*t) * (1 - n.y*n.y*t*t));
            };

            n.x = n.x * t;
            n.y = n.y * t;
            n.z = 0.0f;
        }
        /*else if (xform == NormalTransform_DualParaboloid) {

        }*/

        x = n.x;
        y = n.y;
        z = n.z;
    }
}

void Surface::reconstructNormals(NormalTransform xform)
{
    if (isNull()) return;

    detach();

    FloatImage * img = m->image;

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++) {
        float & x = img->pixel(0, i);
        float & y = img->pixel(1, i);
        float & z = img->pixel(2, i);
        Vector3 n(x, y, z);

        if (xform == NormalTransform_Orthographic) {
            n.z = sqrtf(1 - nv::clamp(n.x * n.x + n.y * n.y, 0.0f, 1.0f));
        }
        else if (xform == NormalTransform_Stereographic) {
            float denom = 2.0f / (1 + nv::clamp(n.x * n.x + n.y * n.y, 0.0f, 1.0f));
            n.x *= denom;
            n.y *= denom;
            n.z = denom - 1;
        }
        else if (xform == NormalTransform_Paraboloid) {
            n.x = n.x;
            n.y = n.y;
            n.z = 1.0f - nv::clamp(n.x * n.x + n.y * n.y, 0.0f, 1.0f);
            n = normalizeSafe(n, Vector3(0.0f), 0.0f);
        }
        else if (xform == NormalTransform_Quartic) {
            n.x = n.x;
            n.y = n.y;
            n.z = nv::clamp((1 - n.x * n.x) * (1 - n.y * n.y), 0.0f, 1.0f);
            n = normalizeSafe(n, Vector3(0.0f), 0.0f);
        }
        /*else if (xform == NormalTransform_DualParaboloid) {

        }*/

        x = n.x;
        y = n.y;
        z = n.z;
    }
}

void Surface::toCleanNormalMap()
{
    if (isNull()) return;

    detach();

    const uint count = m->image->pixelCount();
    for (uint i = 0; i < count; i++) {
        float x = m->image->pixel(0, i);
        float y = m->image->pixel(1, i);

        m->image->pixel(2, i) = x*x + y*y;
    }
}

// [-1,1] -> [ 0,1]
void Surface::packNormals(float scale/*= 0.5f*/, float bias/*= 0.5f*/) {
    if (isNull()) return;
    detach();
    m->image->scaleBias(0, 3, scale, bias);
}

// [ 0,1] -> [-1,1]
void Surface::expandNormals(float scale/*= 2.0f*/, float bias/*= - 2.0f * 127.0f / 255.0f*/) {
    if (isNull()) return;
    detach();
    m->image->scaleBias(0, 3, scale, bias);
}


// Create a Toksvig map for this normal map.
// http://blog.selfshadow.com/2011/07/22/specular-showdown/
// @@ Assumes this is a normal map expanded in the [-1, 1] range.
Surface Surface::createToksvigMap(float power) const
{
    if (isNull()) return Surface();

    // @@ TODO

    return Surface();
}

// @@ Should I add support for LEAN maps? That requires 5 terms, which would have to be encoded in two textures.
// There's nothing stopping us from having 5 channels in a surface, and then, let the user swizzle them as they wish.
// CLEAN maps are probably more practical, though.
// http://www.cs.umbc.edu/~olano/papers/lean/
// http://gaim.umbc.edu/2011/07/24/shiny-and-clean/
// http://gaim.umbc.edu/2011/07/26/on-error/
NVTT_API Surface Surface::createCleanMap() const
{
    if (isNull()) return Surface();

    // @@ TODO

    return Surface();
}


void Surface::flipX()
{
    if (isNull()) return;

    detach();

    m->image->flipX();
}

void Surface::flipY()
{
    if (isNull()) return;

    detach();

    m->image->flipY();
}

void Surface::flipZ()
{
    if (isNull()) return;

    detach();

    m->image->flipZ();
}

Surface Surface::createSubImage(int x0, int x1, int y0, int y1, int z0, int z1) const
{
    Surface s;

    if (isNull()) return s;
    if (x0 < 0 || x1 > width() || x0 > x1) return s;
    if (y0 < 0 || y1 > height() || y0 > y1) return s;
    if (z0 < 0 || z1 > depth() || z0 > z1) return s;
    if (x1 >= width() || y1 >= height() || z1 >= depth()) return s;

    FloatImage * img = s.m->image = new FloatImage;

    int w = x1 - x0 + 1;
    int h = y1 - y0 + 1;
    int d = z1 - z0 + 1;

    img->allocate(4, w, h, d);

    for (int c = 0; c < 4; c++) {
        for (int z = 0; z < d; z++) {
            for (int y = 0; y < h; y++) {
                for (int x = 0; x < w; x++) {
                    img->pixel(c, x, y, z) = m->image->pixel(c, x0+x, y0+y, z0+z);
                }
            }
        }
    }

    return s;
}


Surface Surface::warp(int w, int h, WarpFunction * warp_function) const
{
    Surface s;

    FloatImage * img = s.m->image = new FloatImage;

    const int C = m->image->componentCount();
    img->allocate(C, w, h, 1);

#define USE_PARALLEL_FOR 0
#if USE_PARALLEL_FOR
    nv::parallel_for(h, 1, [=](int y) {
#else
    for (int y = 0; y < h; y++) {
#endif
        for (int x = 0; x < w; x++) {
            float fx = (float(x) + 0.0f) / w;
            float fy = (float(y) + 0.0f) / h;
            float fz = 0;

            warp_function(fx, fy, fz);

            for (int c = 0; c < C; c++) {
                img->pixel(c, x, y, 0) = m->image->sampleLinearClamp(c, fx, fy);
            }
        }
    }
#if USE_PARALLEL_FOR
    );
#endif

    return s;
}

Surface Surface::warp(int w, int h, int d, WarpFunction * warp_function) const
{
    Surface s;

    FloatImage * img = s.m->image = new FloatImage;

    const int C = m->image->componentCount();
    img->allocate(C, w, h, d);

    for (int z = 0; z < d; z++) {
#define USE_PARALLEL_FOR 0
#if USE_PARALLEL_FOR
        nv::parallel_for(h, 1, [=](int y) {
#else
        for (int y = 0; y < h; y++) {
#endif
            for (int x = 0; x < w; x++) {
                float fx = (float(x) + 0.0f) / w;
                float fy = (float(y) + 0.0f) / h;
                float fz = (float(z) + 0.0f) / d;

                warp_function(fx, fy, fz);

                for (int c = 0; c < C; c++) {
                    img->pixel(c, x, y, z) = m->image->sampleLinearClamp(c, fx, fy, fz);    // @@ 2D only.
                }
            }
        }
#if USE_PARALLEL_FOR
        );
#endif
    }

    return s;
}


bool Surface::copyChannel(const Surface & srcImage, int srcChannel)
{
    return copyChannel(srcImage, srcChannel, srcChannel);
}

bool Surface::copyChannel(const Surface & srcImage, int srcChannel, int dstChannel)
{
    if (srcChannel < 0 || srcChannel > 3 || dstChannel < 0 || dstChannel > 3) return false;

    FloatImage * dst = m->image;
    const FloatImage * src = srcImage.m->image;

    if (!sameLayout(dst, src)) {
        return false;
    }
    nvDebugCheck(dst->componentCount() == 4 && src->componentCount() == 4);

    detach();

    dst = m->image;

    memcpy(dst->channel(dstChannel), src->channel(srcChannel), dst->pixelCount()*sizeof(float));

    return true;
}

bool Surface::addChannel(const Surface & srcImage, int srcChannel, int dstChannel, float scale)
{
    if (srcChannel < 0 || srcChannel > 3 || dstChannel < 0 || dstChannel > 3) return false;

    FloatImage * dst = m->image;
    const FloatImage * src = srcImage.m->image;

    if (!sameLayout(dst, src)) {
        return false;
    }
    nvDebugCheck(dst->componentCount() == 4 && src->componentCount() == 4);

    detach();

    dst = m->image;

    float * d = dst->channel(dstChannel);
    const float * s = src->channel(srcChannel);

    const uint count = src->pixelCount();
    for (uint i = 0; i < count; i++) {
        d[i] += s[i] * scale;
    }

    return true;
}


bool Surface::copy(const Surface & srcImage, int xsrc, int ysrc, int zsrc, int xsize, int ysize, int zsize, int xdst, int ydst, int zdst)
{
    if (xsrc < 0 || ysrc < 0 || zsrc < 0) return false;
    if (xdst < 0 || ydst < 0 || zdst < 0) return false;

    FloatImage * dst = m->image;
    const FloatImage * src = srcImage.m->image;

    if (U32(xsrc + xsize) > src->width() || U32(ysrc + ysize) > src->height() || U32(zsrc + zsize) > src->depth()) return false;
    if (U32(xdst + xsize) > dst->width() || U32(ydst + ysize) > dst->height() || U32(zdst + zsize) > dst->depth()) return false;

    detach();

    // For each channel.
    for(int i = 0; i < 4; i++) {
        float * d = dst->channel(i);
        const float * s = src->channel(i);

        // Copy region from src to dst.
        for (int z = 0; z < zsize; z++) {
            for (int y = 0; y < ysize; y++) {
                for (int x = 0; x < xsize; x++) {
                    d[dst->index(xdst + x, ydst + y, zdst + z)] = s[src->index(xsrc + x, ysrc + y, zsrc + z)];
                }
            }
        }
    }

    return true;
}


// Draw colored border around atlas elements.
void Surface::setAtlasBorder(int aw, int ah, float r, float g, float b, float a)
{
    if (isNull()) return;
    if (aw <= 0) return;
    if (ah <= 0) return;

    detach();

    FloatImage * img = m->image;
    const uint w = img->width();
    const uint h = img->height();
    const uint d = img->depth();

    // @@ Ideally the reminder of these divisions should be 0.
    uint tile_height = h / ah;
    uint tile_width = w / aw;

    // Note that this renders two consecutive lines between tiles. In theory we could just have one, but this way I think we have better rotation invariance.

    for (uint z = 0; z < d; z++)
    {
        // Horizontal lines:
        for (uint i = 0, y = 0; i < uint(ah); i++, y += tile_height)
        {
            for (uint x = 0; x < w; x++)
            {
                img->pixel(0, x, y, z) = r;
                img->pixel(1, x, y, z) = g;
                img->pixel(2, x, y, z) = b;
                img->pixel(3, x, y, z) = a;

                img->pixel(0, x, y + tile_height - 1, z) = r;
                img->pixel(1, x, y + tile_height - 1, z) = g;
                img->pixel(2, x, y + tile_height - 1, z) = b;
                img->pixel(3, x, y + tile_height - 1, z) = a;
            }
        }

        // Vertical lines:
        for (uint i = 0, x = 0; i < uint(aw); i++, x += tile_width)
        {
            for (uint y = 0; y < h; y++)
            {
                img->pixel(0, x, y, z) = r;
                img->pixel(1, x, y, z) = g;
                img->pixel(2, x, y, z) = b;
                img->pixel(3, x, y, z) = a;

                img->pixel(0, x + tile_width - 1, y, z) = r;
                img->pixel(1, x + tile_width - 1, y, z) = g;
                img->pixel(2, x + tile_width - 1, y, z) = b;
                img->pixel(3, x + tile_width - 1, y, z) = a;
            }
        }
    }
}



float nvtt::rmsError(const Surface & reference, const Surface & image)
{
    return nv::rmsColorError(reference.m->image, image.m->image, reference.alphaMode() == nvtt::AlphaMode_Transparency);
}


float nvtt::rmsAlphaError(const Surface & reference, const Surface & image)
{
    return nv::rmsAlphaError(reference.m->image, image.m->image);
}


float nvtt::cieLabError(const Surface & reference, const Surface & image)
{
    return nv::cieLabError(reference.m->image, image.m->image);
}

float nvtt::angularError(const Surface & reference, const Surface & image)
{
    //return nv::averageAngularError(reference.m->image, image.m->image);
    return nv::rmsAngularError(reference.m->image, image.m->image);
}


Surface nvtt::diff(const Surface & reference, const Surface & image, float scale)
{
    const FloatImage * ref = reference.m->image;
    const FloatImage * img = image.m->image;

    if (!sameLayout(img, ref)) {
        return Surface();
    }

    nvDebugCheck(img->componentCount() == 4);
    nvDebugCheck(ref->componentCount() == 4);

    nvtt::Surface diffImage;
    FloatImage * diff = diffImage.m->image = new FloatImage;
    diff->allocate(4, img->width(), img->height(), img->depth());

    const uint count = img->pixelCount();
    for (uint i = 0; i < count; i++)
    {
        float r0 = img->pixel(0, i);
        float g0 = img->pixel(1, i);
        float b0 = img->pixel(2, i);
        //float a0 = img->pixel(3, i);
        float r1 = ref->pixel(0, i);
        float g1 = ref->pixel(1, i);
        float b1 = ref->pixel(2, i);
        float a1 = ref->pixel(3, i);

        float dr = r0 - r1;
        float dg = g0 - g1;
        float db = b0 - b1;
        //float da = a0 - a1;

        if (reference.alphaMode() == nvtt::AlphaMode_Transparency)
        {
            dr *= a1;
            dg *= a1;
            db *= a1;
        }

        diff->pixel(0, i) = dr * scale;
        diff->pixel(1, i) = dg * scale;
        diff->pixel(2, i) = db * scale;
        diff->pixel(3, i) = a1;
    }

    return diffImage;
}

float nvtt::rmsToneMappedError(const Surface & reference, const Surface & img, float exposure)
{
    // @@ We could do this in the rms function without having to create image copies.
    Surface r = reference;
    Surface i = img;

    // @@ Ideally we should use our Reindhart operator. Add Reindhart_L & Reindhart_M ?

    float scale = 1.0f / exposure;

    r.scaleBias(0, scale, 0); r.scaleBias(1, scale, 0); r.scaleBias(2, scale, 0);
    r.toneMap(ToneMapper_Reindhart, NULL);
    r.toSrgb();

    i.scaleBias(0, scale, 0); i.scaleBias(1, scale, 0); i.scaleBias(2, scale, 0);
    i.toneMap(ToneMapper_Reindhart, NULL);
    i.toSrgb();

    return nv::rmsColorError(r.m->image, i.m->image, reference.alphaMode() == nvtt::AlphaMode_Transparency);
}


Surface nvtt::histogram(const Surface & img, int width, int height)
{
    float min_color[3], max_color[3];
    img.range(0, &min_color[0], &max_color[0]);
    img.range(1, &min_color[1], &max_color[1]);
    img.range(2, &min_color[2], &max_color[2]);

    float minRange = nv::min3(min_color[0], min_color[1], min_color[2]);
    float maxRange = nv::max3(max_color[0], max_color[1], max_color[2]);

    if (maxRange > 16) maxRange = 16;

    return histogram(img, /*minRange*/0, maxRange, width, height);
}

//#include "nvcore/Array.inl"
//#include "nvmath/PackedFloat.h"
//#include <stdio.h>

nvtt::Surface nvtt::histogram(const Surface & img, float minRange, float maxRange, int width, int height)
{
    nv::Array<Vector3> buckets;
    buckets.resize(width, Vector3(0));

    int w = img.width();
    int h = img.height();
    int d = img.depth();

    const float * r = img.channel(0);
    const float * g = img.channel(1);
    const float * b = img.channel(2);
    const float * a = img.channel(3);

#if 0
    for (int z = 0; z < d; z++)
    for (int y = 0; y < h; y++)
    for (int x = 0; x < w; x++)
    {
        int i = x + y * w + z * w * d;

        float fr = (r[i] - minRange) / (maxRange - minRange);
        float fg = (g[i] - minRange) / (maxRange - minRange);
        float fb = (b[i] - minRange) / (maxRange - minRange);

        int R = ftoi_round(fr * (width - 1));
        int G = ftoi_round(fg * (width - 1));
        int B = ftoi_round(fb * (width - 1));

        R = nv::clamp(R, 0, width-1);
        G = nv::clamp(G, 0, width-1);
        B = nv::clamp(B, 0, width-1);
        
        // Alpha weighted histogram?
        float A = nv::saturate(a[i]);

        buckets[R].x += A;
        buckets[G].y += A;
        buckets[B].z += A;
    }

#elif 1
    
    float exposure = 0.22f;

    //int E = 8, M = 23;    // float
    int E = 5, M = 10;    // half
    //int E = 5, M = 9;     // rgb9e5
    //int E = 5, M = 6;     // r11g11b10

    for (int e = 0; e < (1 << E); e++)
    {
        /*if (e == 0x1f) {    // Skip NaN and inf.
            continue;
        }*/
        if (e == 0) {       // Skip denormals.
            continue;
        }

        for (int m = 0; m < (1 << M); m++)
        {
            Float754 F;
            F.field.negative = 0;
            F.field.biasedexponent = e + 128 - (1 << (E - 1)) - 1;  // E=5 -> 128 - 15
            F.field.mantissa = m << (23 - M);

            // value = (1 + mantissa) * 2^(e-15)

            // @@ Handle denormals.

            float fc = F.value;

            // Tone mapping:
            fc /= exposure;
            //fc /= (fc + 1);             // Reindhart tone mapping.
            fc = 1 - exp2f(-fc);        // Halo2 tone mapping.

            // Gamma space conversion:
            //fc = sqrtf(fc);
            fc = powf(fc, 1.0f/2.2f);
            //fc = toSrgb(fc);

            //fc = (fc - 0.5f) * 8; // zoom in
            //if (fc < 0 || fc > 1) continue;

            //printf("%f\n", fc);

            int c = ftoi_round(fc * (width - 1) / 1);
            c = clamp(c, 0, width - 1);

            buckets[c] += Vector3(1);
        }
    }

#else

    float exposure = 0.22f;

    int R = 8, M = 8;
    //int R = 6, M = 8;
    //int R = 9, M = 5;

    float threshold = 1.0f / (1 << M);
    //float threshold = 0.25f;

    for (int r = 0; r < (1 << R); r++)
    {
        float fr = float(r) / ((1 << R) - 1);

        for (int m = 0; m < (1 << M); m++)
        {
            float fm = float(m) / ((1 << M) - 1);
            float M = fm * (1 - threshold) + threshold;

            float fc = fr * M;

            fc /= exposure;
            
            //fc /= (fc + 1);             // Reindhart tone mapping.
            fc = 1 - exp2f(-fc);        // Halo2 tone mapping.

            // Gamma space conversion:
            //fc = sqrtf(fc);
            fc = powf(fc, 1.0f/2.2f);
            //fc = toSrgb(fc);

            //fc = (fc - 0.5f) * 8; // zoom in
            //if (fc < 0 || fc > 1) continue;

            int c = ftoi_round(fc * (width - 1));
            c = clamp(c, 0, width - 1);

            buckets[c] += Vector3(1);
        }
    }

    //buckets[0] = Vector3(1);    // Hack, for prettier histograms.

#endif


    // Compute largerst height.
    float maxh = 0;
    for (int i = 0; i < width; i++) {
        maxh = nv::max(maxh, nv::max3(buckets[i].x, buckets[i].y, buckets[i].z));
    }

    //printf("maxh = %f\n", maxh);
    //maxh = 80;
    maxh = 256;

    // Draw histogram.
    nvtt::Surface hist;
    hist.setImage(width, height, 1);
    
    for (int y = 0; y < height; y++) {
        float fy = 1.0f - float(y) / (height - 1);
        for (int x = 0; x < width; x++) {
            hist.m->image->pixel(0, x, y, /*z=*/0) = fy < (buckets[x].x / maxh);
            hist.m->image->pixel(1, x, y, /*z=*/0) = fy < (buckets[x].y / maxh);
            hist.m->image->pixel(2, x, y, /*z=*/0) = fy < (buckets[x].z / maxh);
        }
    }

    return hist;
}
