1

I have a 256x256 array of floats that represents a heightmap. I would like to export it to a 16bit per pixel RAW image. What would be the correct way of converting float to uint16_t. (I'm aware of the precision loss)

My quick and dirty code for testing:

void ExportHeightmap(const Vector<float>& rHeights)
{
    std::vector<uint16_t> output(256 * 256);

    float min = std::numeric_limits<float>::max();
    float max = std::numeric_limits<float>::min();

    for (size_t i = 0; i < output.size(); ++i)
    {
        float f = rHeights[i];

        if (min > f) min = f;
        if (max < f) max = f;

        output[i] = static_cast<uint16_t>(rHeights[i]);
    }

    std::cout << " Min: " << min << std::endl; // -49.77
    std::cout << " Max: " << max << std::endl; // 357.84

    std::fstream file("Heightmap.raw", std::ios::out | std::ios::binary);
    file.write((char*)output.data(), output.size() * sizeof(uint16_t));
}

EDIT: My goal is to export heightmap made in the application to an image.

Gediminas
  • 1,830
  • 3
  • 27
  • 47
  • 3
    Why `std::vector output((256 * 256) * sizeof(uint16_t), 0);`? Why not simply `std::vector(256 * 256, 0)`? – Scheff's Cat Nov 01 '18 at 14:55
  • 3
    Indeed, you should have a std::vector. Other questionm if it's an image, it's already normalized between 0 and 1? – Matthieu Brucher Nov 01 '18 at 14:57
  • Casting a `float` to `uint16_t` does mean losing all fractional parts - it just keeps the integral parts. Is this intended? – Scheff's Cat Nov 01 '18 at 14:57
  • Which range do you expect for `float` values of height map? – Scheff's Cat Nov 01 '18 at 15:12
  • [standard-rgb-to-grayscale-conversion](https://stackoverflow.com/questions/17615963/standard-rgb-to-grayscale-conversion) ? – Victor Gubin Nov 01 '18 at 15:13
  • I would say the optimum format would be a 16-bit greyscale PGM file, starting `P5 255 255 65535` so that it can be viewed, read, written and analysed by GIMP, ImageMagick, Pillow, Photoshop, `feh` etc. https://en.wikipedia.org/wiki/Netpbm_format – Mark Setchell Nov 01 '18 at 15:16
  • @Gediminas `std::vector::data()` is not dereferencable only if the vector is empty. – Daniel Langr Nov 01 '18 at 15:19
  • I've updated my code example. @Scheff, my heightmap values are from -49.77 to 357.84 – Gediminas Nov 01 '18 at 15:32
  • Further to my suggestion above, you could also embed a comment in the PGM file that tells you the scaling, e.g. `# 0=-49.77, 65535=357.84` – Mark Setchell Nov 01 '18 at 16:07

1 Answers1

1

I assume, OP wants to use the whole range of uint16_t i.e. 0 ... 65535.

In this case, the height values to be shifted and scaled to the new range i.e. scale (max - min) -> 65535, and translate min -> 0.

This could look like this:

        value - min
pixel = ----------- * 65535
         max - min

In code:

#include <cstdint>
#include <iomanip>
#include <iostream>

std::uint16_t floatToUInt16(float value, float min, float max)
{
  return (std::uint16_t)((value - min) / (max - min) * 65535.0f);
}

int main()
{
  float min = -49.77f, max = 357.84f;
  // some test values
  float values[] = { 0.0f, min, max, min / 2, max / 2 };
  // test conversion
  for (float value : values) {
    std::cout << std::fixed << std::setw(10) << value
      << " -> "
      << std::setw(5) << floatToUInt16(value, min, max)
      << '\n';
  }
  return 0;
}

Output:

  0.000000 ->  8001
-49.770000 ->     0
357.839996 -> 65535
-24.885000 ->  4000
178.919998 -> 36768

Live Demo on coliru

If this is done in a loop, I would optimize it a bit. Hence, 66535.0f / (max - min) is a fix factor applied to all height values. So, it's worth to compute this factor before entering the loop.

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56