0

When I load an image as float using STB_Image the values seem to be off. I created an image to test it Test image. (The used RGB code here is [127, 255, 32])
When I load this image as unsigned char using stbi_load() I get the correct values. But when I load it as float using stbi_loadf() I get wrong values which don't really make sense to me.

This is the code I used for testing:

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#include <sstream>
#include <iomanip>
#include <iostream>
#include <string>

struct ColorF {
  float r;
  float g;
  float b;
  float a;

  std::string toString() {
    std::stringstream stream;

    stream << std::fixed << std::setprecision(2) << "[" << this->r << ", " << this->g << ", " << this->b << ", " << this->a << "]";

    return stream.str();
  }
};

struct ColorUC {
  unsigned char r;
  unsigned char g;
  unsigned char b;
  unsigned char a;

  std::string toString() {
    std::stringstream stream;

    stream << "[" << (float) this->r / 255.0f << ", " << (float) this->g / 255.0f << ", " << (float) this->b / 255.0f << ", " << (float) this->a / 255.0f << "]";

    return stream.str();
  }
};

int main() {
  int width, height, channels;
  float* image = stbi_loadf("test.png", &width, &height, &channels, STBI_rgb_alpha);

  // print content of first pixel of the image
  std::cout << ((ColorF*) image)->toString() << std::endl;

  unsigned char* jpeg = stbi_load("test.png", &width, &height, &channels, STBI_rgb_alpha);

  // print content of first pixel of the image
  std::cout << ((ColorUC*) jpeg)->toString() << std::endl;  

  stbi_image_free(image);
  stbi_image_free(jpeg);

  return 0;
}

The test output I get is this:

[0.22, 1.00, 0.01, 1.00]
[0.50, 1.00, 0.13, 1.00]

In theory this should print out the same values, the bottom one being the correct one, on both lines but for some reason it doesn't.
Now i could of course use the unsigned char values and write myself a function that converts everything to the proper float values but I feel like there should be a way to do this just using STB_Image itself.

DJSchaffner
  • 562
  • 7
  • 22
  • 2
    The first line appears to be gamma-corrected with a gamma of `2.2`. – Nico Schertler Jan 16 '20 at 17:12
  • Do you know if theres a way to disable the gamma correction or why this only happens on the float value? – DJSchaffner Jan 16 '20 at 17:15
  • 1
    Thank you, what you said seemed to be the problem. Setting `stbi_ldr_to_hdr_gamma(1.0f);` fixed this. If thats the wrong way to do it, I'd be glad if someone would point it out to me. Also, why the downvote on my question? – DJSchaffner Jan 16 '20 at 17:19

1 Answers1

2

From the stb_image.h header comments:

// If you load LDR images through this interface, those images will
// be promoted to floating point values, run through the inverse of
// constants corresponding to the above:
//
//     stbi_ldr_to_hdr_scale(1.0f);
//     stbi_ldr_to_hdr_gamma(2.2f);

That means the stbi_loadf() function uses built-in gamma correction to return its float values.

The gamma correction formula looks like this:

Vout = (Vin / 255)^γ; Where γ = 2.2

When you put [127, 255, 32] through the gamma correction formula, you get the result you are seeing:

R: 0.22 = (127 / 255)^2.2
G: 1.00 = (255 / 255)^2.2 
B: 0.01 = ( 32 / 255)^2.2

You can set the stb_image gamma factor with stbi_ldr_to_hdr_gamma(1.0f) as a quick fix.

However, stb_image defines the stbi_loadf() specifically to support non-linear encoded images (HDR or gamma corrected). If you were to load an actual HDR image with that function, setting the gamma to 1.0 would negatively impact the accuracy of the image when drawn; So it is best to only use stbi_loadf() if you actually need the image to be loaded with gamma correction.

Now i could of course use the unsigned char values and write myself a function that converts everying to the proper float values but I feel like there should be a way to do this just using STB_Image itself.

Manually converting the 8-bit image data to a float using Vout = (float)Vin / 255f is an obvious tell to readers of your code that the image data is not gamma corrected. Using stbi_loadf() with stbi_ldr_to_hdr_gamma(1.0f) can obscure this important detail.

Romen
  • 1,617
  • 10
  • 26
  • @DJSchaffner, I've updated with some more info about the purpose of `stbi_loadf()` and why it may still be a good idea to do the conversion yourself, or at least document *why* you are using a gamma of `1.0`. – Romen Jan 16 '20 at 17:40
  • Good point. For now i added it with a comment stating that i set it to 1.0 `to avoid automatic gamma correction on loading`. But maybe its a better idea to load as uchar and convert it manually. – DJSchaffner Jan 16 '20 at 17:48