This is an implementation of a RGB/XYZ pixel format commonly used to store HDR lumiance maps. It falls somewhere between visualization and modeling & simulation in subject area, and so I am posting it here, in the hope of some commentary.
"""
RealPixel{N}
HDR pixel format for radiance and luminance, as used in Radiance, and
widely used for HDR images.
This is an implementation of Greg Ward's "Real Pixels"[^rp] floating
point pixel compression scheme, intended for a compact representation
of absolute RGB or XYZ luminance values. It follows the *Radiance*
`color.c` code. The code has been extended to arbitrarily many
spectral bands, supporting the new *Radiance* multispectral format.
[^rp]: Ward, Greg. “Real Pixels.” In *Graphics Gems II*, edited by
Arvo, James, 80–83. Graphics Gems Series. Boston: Academic Press,
1991.
The structure defines an n-byte mantissa, followed by a single byte
exponent. Since physical values of luminance and radiance are always
positive and XYZ values are normalized to be positive the mantissas
are unsigned quantites. The exponent is an unsigned representation of
a signed quantity; it is the signed quantity + 128. In the important
special case of three spectral channels, RGB or XYZ, a RealPixel fits
in a 32-bit word.
"""
struct RealPixel{N} <: AbstractVector{Real}
mantissa::NTuple{N,UInt8}
exponent::UInt8
end
"""
RealPixel(v::Vector{<:Real})
Construct a RealPixel from a vector of Reals, by compressing the Reals
into the RealPixel format.
"""
function RealPixel(v::Vector{<:Real})
RealPixel(realpixelfromfloat(v))
end
"""
RealPixel(v::Vector{UInt8})
Construct a RealPixel from raw UInt8s. The last UInt8 is the
exponent.
"""
function RealPixel(v::Vector{UInt8})
length(v) < 1 && error("RealPixel needs at least one UInt8")
RealPixel(Tuple(v[begin:end-1]), v[end])
end
# AbstractVector interface
"""
size(px::RealPixel)
The dimensions of the vector.
"""
size(px::RealPixel) = (length(px.mantissa),)
"""
getindex(px::RealPixel,i::Int)::Float32
Subscript a RealPixel.
"""
function getindex(px::RealPixel,i::Int)::Float32
1 <= i <= length(px) || throw(BoundsError(px, i))
px.exponent == 0 && return 0.0
return ldexp(px.mantissa[i] + 0.5, px.exponent - 136)
end
"""
iterate(px::RealPixel, i=1)
Iterate through the elements of a RealPixel
"""
iterate(px::RealPixel, i=1) = (i <= length(px)) ? (px[i], i+1) : nothing
"""
convert(T::Type{Vector}, px::RealPixel)::AbstractVector{Float32}
Convert a RealPixel to a vector of Float32 by decompression.
"""
convert(T::Type{Vector}, px::RealPixel)::AbstractVector{Float32} =
floatfromrealpixel(vcat(collect(px.mantissa), px.exponent))
"""
floatfromrealpixel(px::AbstractVector{UInt8})
Expand an 8(n+1)-bit real pixel to a 32n-bit pixel.
"""
function floatfromrealpixel(px::AbstractVector{UInt8})::Vector{Float32}
exponent = px[end]
mantissas = px[1:end-1]
exponent == 0 && return zeros(Float32, length(mantissas))
return [ldexp(v + 0.5, exponent - 136) for v in mantissas]
end
"""
realpixelfromfloat(f::AbstractVector{<:Real})
Compress a pixel of some real type to an 8(n+1)-bit real pixel.
Returns a vector of UInt8, where the last value is the exponent.
"""
function realpixelfromfloat(f::AbstractVector{<:Real})
maxchannel = maximum(f)
maxchannel < 1e-32 && return zeros(UInt8, length(f)+1)
x,e = frexp(maxchannel)
d = x * 256.0 / maxchannel
rp = [trunc(UInt8, v * d) for v in f]
push!(rp, UInt8(e + 128))
return rp
end