skip to Main Content

I just need some clarification on how to properly convert rgb pixel values in the range [0,1] to be in the right range for a HDR format like openEXR.

So I know for instance that when working with low dynamic range formats like PNG or JPG that only have 8 bits per channel, you simply have to multiply each rgb value by 2^8-1 = 255 and clamp so that all your values are in the range [0, 255].

OpenEXR and other HDR formats use a half precision format with 16 bits per channel. So do I just do the same thing as before and multiply each channel by 2^16-1 = 65535 so that my new range becomes [0, 65535]?

EDIT

So I tried doing what I wrote above but when I try to display the openEXR file in photoshop it is completely white. It seems like any value greater than around 10 or so is too bright for photoshop to display properly which I find very odd. So this doesn’t seem like the correct way to do it unless there is something wrong with photoshops display.

3

Answers


  1. We are dealing with three related but different issues at the same time.

    The first issue is that of range: are channels stored as values in the range [0, 1] or [0, 255] or [0, 65535]. Scaling (multiplying and possibly clamping) is what you do to convert from one range to another.

    The second issue is that of raw sample size: how many bits do you use to store the value. This doesn’t necessarily have to be the binary logarithm of the size of the range. For example, if your range is [0, 255], you could store the values in 8 bits where the least significant bits represents increments of 1, or in 6 bits where the least significant bit represents increments of 4, or in 10 bits where the least significant bit represents increments of 0.25. In fact, as we will see in the next issue, the increments do not have to be fixed.

    The third and final issue is that of encoding: fixed point or floating point. When we say that we store values of [0, 255] in 8 bits or values of [0, 65535] in 16 bits, we usually mean integer encoding (a special case of fixed point where the least significant bit represents fixed increments of 1). When values are stored in the range [0, 1] however, regardless of the raw sample size, usually this implies floating point storage (where most bits are used to store the significand, while a few bits are reserved to store the size of the increment associated with the least significant bit). When we speak of “half precision”, “single precision”, “double precision”, “extended precision” and so forth we also invariably mean floating point encoding.

    So here is the catch: OpenEXR uses floating point encoding, in a format that is not built in to most programming languages. Most modern languages only have 64-bit floats, and if they offer anything else it’s usually 32-bit floats (respectively double and float in the C family), but 16-bit floats are almost never available out of the box.

    Half precision can represent values in the range [-65505, 65504], with 11 bits (slightly better than 3 decimal digits) of precision, while also being able to represent values as small as 2^-14. However, given that OpenEXR is a HDR format, you are probably not really expected to use the entire range because the number encoding is chosen to accomodate for (extreme) over- or underexposure. That is, unless your camera actually produces such an enormous dynamic range of values.

    So you might not actually need to scale your channel values. However, given that you already start with values in [0, 1], you probably have floating point numbers stored in single or double precision and you’ll have to transcode them to half precision. Depending on the programming language, the libraries and even the hardware platform that you use, there might be an off-the-shelf solution or you might need to do some bit-fiddling of your own. As a starting point I can only offer you this DuckDuckGo search.

    Login or Signup to reply.
  2. I’m too tired to think straight, but this may help you work it all out. I used ImageMagick to create three OpenEXR images, one white, one black and one red, all 1 pixel x 1 pixel.

    convert -size 1x1! xc:white white.exr
    convert -size 1x1! xc:black black.exr
    convert -size 1x1! xc:red red.exr
    

    Then I hexdumped them all:

    xxd white.exr > white.txt
    xxd black.exr > black.txt
    xxd red.exr > red.txt
    

    Here are the files:

    White.txt

    0000000: 762f 3101 0200 0000 6368 616e 6e65 6c73  v/1.....channels
    0000010: 0063 686c 6973 7400 4900 0000 4100 0100  .chlist.I...A...
    0000020: 0000 0000 0000 0100 0000 0100 0000 4200  ..............B.
    0000030: 0100 0000 0000 0000 0100 0000 0100 0000  ................
    0000040: 4700 0100 0000 0000 0000 0100 0000 0100  G...............
    0000050: 0000 5200 0100 0000 0000 0000 0100 0000  ..R.............
    0000060: 0100 0000 0063 6f6d 7072 6573 7369 6f6e  .....compression
    0000070: 0063 6f6d 7072 6573 7369 6f6e 0001 0000  .compression....
    0000080: 0000 6461 7461 5769 6e64 6f77 0062 6f78  ..dataWindow.box
    0000090: 3269 0010 0000 0000 0000 0000 0000 0000  2i..............
    00000a0: 0000 0000 0000 0064 6973 706c 6179 5769  .......displayWi
    00000b0: 6e64 6f77 0062 6f78 3269 0010 0000 0000  ndow.box2i......
    00000c0: 0000 0000 0000 0000 0000 0000 0000 006c  ...............l
    00000d0: 696e 654f 7264 6572 006c 696e 654f 7264  ineOrder.lineOrd
    00000e0: 6572 0001 0000 0000 7069 7865 6c41 7370  er......pixelAsp
    00000f0: 6563 7452 6174 696f 0066 6c6f 6174 0004  ectRatio.float..
    0000100: 0000 0000 0080 3f73 6372 6565 6e57 696e  ......?screenWin
    0000110: 646f 7743 656e 7465 7200 7632 6600 0800  dowCenter.v2f...
    0000120: 0000 0000 0000 0000 0000 7363 7265 656e  ..........screen
    0000130: 5769 6e64 6f77 5769 6474 6800 666c 6f61  WindowWidth.floa
    0000140: 7400 0400 0000 0000 803f 0053 0100 0000  t........?.S....
    0000150: 0000 0000 0000 0008 0000 0000 3c00 3c00  ............<.<.
    0000160: 3c00 3c                                  <.<
    

    Black.txt

    0000000: 762f 3101 0200 0000 6368 616e 6e65 6c73  v/1.....channels
    0000010: 0063 686c 6973 7400 4900 0000 4100 0100  .chlist.I...A...
    0000020: 0000 0000 0000 0100 0000 0100 0000 4200  ..............B.
    0000030: 0100 0000 0000 0000 0100 0000 0100 0000  ................
    0000040: 4700 0100 0000 0000 0000 0100 0000 0100  G...............
    0000050: 0000 5200 0100 0000 0000 0000 0100 0000  ..R.............
    0000060: 0100 0000 0063 6f6d 7072 6573 7369 6f6e  .....compression
    0000070: 0063 6f6d 7072 6573 7369 6f6e 0001 0000  .compression....
    0000080: 0000 6461 7461 5769 6e64 6f77 0062 6f78  ..dataWindow.box
    0000090: 3269 0010 0000 0000 0000 0000 0000 0000  2i..............
    00000a0: 0000 0000 0000 0064 6973 706c 6179 5769  .......displayWi
    00000b0: 6e64 6f77 0062 6f78 3269 0010 0000 0000  ndow.box2i......
    00000c0: 0000 0000 0000 0000 0000 0000 0000 006c  ...............l
    00000d0: 696e 654f 7264 6572 006c 696e 654f 7264  ineOrder.lineOrd
    00000e0: 6572 0001 0000 0000 7069 7865 6c41 7370  er......pixelAsp
    00000f0: 6563 7452 6174 696f 0066 6c6f 6174 0004  ectRatio.float..
    0000100: 0000 0000 0080 3f73 6372 6565 6e57 696e  ......?screenWin
    0000110: 646f 7743 656e 7465 7200 7632 6600 0800  dowCenter.v2f...
    0000120: 0000 0000 0000 0000 0000 7363 7265 656e  ..........screen
    0000130: 5769 6e64 6f77 5769 6474 6800 666c 6f61  WindowWidth.floa
    0000140: 7400 0400 0000 0000 803f 0053 0100 0000  t........?.S....
    0000150: 0000 0000 0000 0008 0000 0000 3c00 0000  ............<...
    0000160: 0000 00                                  ...
    

    Red.txt

    0000000: 762f 3101 0200 0000 6368 616e 6e65 6c73  v/1.....channels
    0000010: 0063 686c 6973 7400 4900 0000 4100 0100  .chlist.I...A...
    0000020: 0000 0000 0000 0100 0000 0100 0000 4200  ..............B.
    0000030: 0100 0000 0000 0000 0100 0000 0100 0000  ................
    0000040: 4700 0100 0000 0000 0000 0100 0000 0100  G...............
    0000050: 0000 5200 0100 0000 0000 0000 0100 0000  ..R.............
    0000060: 0100 0000 0063 6f6d 7072 6573 7369 6f6e  .....compression
    0000070: 0063 6f6d 7072 6573 7369 6f6e 0001 0000  .compression....
    0000080: 0000 6461 7461 5769 6e64 6f77 0062 6f78  ..dataWindow.box
    0000090: 3269 0010 0000 0000 0000 0000 0000 0000  2i..............
    00000a0: 0000 0000 0000 0064 6973 706c 6179 5769  .......displayWi
    00000b0: 6e64 6f77 0062 6f78 3269 0010 0000 0000  ndow.box2i......
    00000c0: 0000 0000 0000 0000 0000 0000 0000 006c  ...............l
    00000d0: 696e 654f 7264 6572 006c 696e 654f 7264  ineOrder.lineOrd
    00000e0: 6572 0001 0000 0000 7069 7865 6c41 7370  er......pixelAsp
    00000f0: 6563 7452 6174 696f 0066 6c6f 6174 0004  ectRatio.float..
    0000100: 0000 0000 0080 3f73 6372 6565 6e57 696e  ......?screenWin
    0000110: 646f 7743 656e 7465 7200 7632 6600 0800  dowCenter.v2f...
    0000120: 0000 0000 0000 0000 0000 7363 7265 656e  ..........screen
    0000130: 5769 6e64 6f77 5769 6474 6800 666c 6f61  WindowWidth.floa
    0000140: 7400 0400 0000 0000 803f 0053 0100 0000  t........?.S....
    0000150: 0000 0000 0000 0008 0000 0000 3c00 0000  ............<...
    0000160: 0000 3c                                  ..<
    

    diff white.txt black.txt

    22,23c22,23
    < 0000150: 0000 0000 0000 0008 0000 0000 3c00 3c00  ............<.<.
    < 0000160: 3c00 3c                                  <.<
    ---
    > 0000150: 0000 0000 0000 0008 0000 0000 3c00 0000  ............<...
    > 0000160: 0000 00
    
    Login or Signup to reply.
  3. The short answer is that this will probably not give you a useful result.

    The moderately-long answer is that this approach makes a number of assumptions that are not at all "safe" with regard to image processing.

    The primary assumption is that a Low Dynamic Range value of 255 represents an HDR value of 65535. The primary problem that HDR intends to solve is that real-world signals have to be compressed to a limited scope. Imagine taking a digital photograph of the sun directly: the input value for that light intensity is much greater than any imaging software supports, so it has to be compressed somehow. With LDR, the center of the sun’s disk and much of the bloom around it will all be clamped to 255. With HDR, you’re still clamping, but only to 65535. If you have a real-world signal which is giving values like 200, 255, 300 and 100,000, and then that gets clamped to 255, it should make sense that you cannot simply scale 255 to 65535 and get a reasonable result. That 255 might have been clamped from 256, or from 300, or from 100000; there’s no way to know. (This is a radical oversimplification of imaging, but it should be sufficient for understanding this limitation in particular).

    A secondary assumption is that what you see on your monitor has anything to do with the values in your image. The 0 – 65535 range of values in the image must be represented on your display, which is almost certainly limited to the sRGB spectrum (255). Mostly likely, if you’ve simply scaled 0-255 to 0-65535, then 99% of your range is above 255. So regardless of how much information is in your image, it’s easy for it all to get clipped to "white."

    Furthermore, depending on how Photoshop (or the GPU driver or the OS or the display panel) is translating your HDR 65535 into sRGB, it’s just as likely that it’s AGAIN being clamped to 255, or perhaps scaled "dumbly," or perhaps scaled "smartly," or any combination thereof. If Photoshop shows your image as solid white, you’ll have to confirm a number of steps in the process to determine "who" in particular is transforming your color values, and how. Photoshop might be clamping before it sends to the OS. The OS might be clamping before it sends to the GPU. The GPU might be clamping when it sends to the display. The display might be clamping when it turns on the pixel. …or any combination of clamping, scaling, and transforming at any of those steps (I know, not all of those combinations are actually possible, but the point is that there is a lot going on between the file and your eyeball).

    Transforming LDR values to an HDR color space (and vice versa) is a non-trivial pursuit that represents a discipline unto itself. Depending on your application, you may need to do a great deal more research before you come to a good solution.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search