├── CMakeLists.txt ├── README.md ├── cmake └── modules │ └── FindOpenImageIO.cmake ├── compare.sh ├── compile.sh ├── rgbx.cpp └── rgbx.sh /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(rgbx) 3 | 4 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake/modules) 5 | 6 | find_package(OpenImageIO REQUIRED) 7 | include_directories(${OIIO_INCLUDE_DIR}) 8 | 9 | set(Boost_USE_STATIC_LIBS OFF) 10 | set(Boost_USE_MULTITHREADED ON) 11 | set(Boost_USE_STATIC_RUNTIME OFF) 12 | 13 | find_package(Boost COMPONENTS system ) 14 | 15 | add_executable(rgbx rgbx.cpp) 16 | target_link_libraries(rgbx ${OIIO_LIBRARY} ${Boost_LIBRARIES} ) 17 | 18 | install(TARGETS rgbx 19 | RUNTIME DESTINATION bin 20 | ) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **RGBX** 2 | 3 | rgbx is a tool to try different encoding hdr to rgba8 format 4 | it supports: 5 | - rgbe 6 | - rgbm 7 | - rgbd 8 | - rgbd2 ( rgbd with range ) 9 | 10 | **usage** 11 | 12 | // encode 13 | rgbx -m method [-r range] inputImage_hdr.tif outputImage_rgba8.png 14 | 15 | // decode 16 | rgbx -d -m method [-r range] inputImage_rgba8.png outputImage_hdr.tif 17 | 18 | **dependancies** 19 | - openimageio 20 | - cmake 21 | 22 | **builds** 23 | 24 | mkdir build 25 | cmake ../ 26 | make 27 | 28 | 29 | **Links** 30 | - http://iwasbeingirony.blogspot.fr/2010/06/difference-between-rgbm-and-rgbd.html 31 | - http://graphicrants.blogspot.fr/2009/04/rgbm-color-encoding.html 32 | - https://gist.github.com/aras-p/1199797 33 | - http://mynameismjp.wordpress.com/2008/12/12/logluv-encoding-for-hdr/ 34 | - http://lousodrome.net/blog/light/2013/05/26/gamma-correct-and-hdr-rendering-in-a-32-bits-buffer/ 35 | - http://vemberaudio.se/graphics/RGBdiv8.pdf 36 | -------------------------------------------------------------------------------- /cmake/modules/FindOpenImageIO.cmake: -------------------------------------------------------------------------------- 1 | FIND_PATH(OIIO_INCLUDE_DIR OpenImageIO/imageio.h 2 | $ENV{OIIO_DIR}/include 3 | $ENV{OIIO_DIR} 4 | ${OIIO_DIR}/include 5 | ${OIIO_DIR} 6 | ~/Library/Frameworks 7 | /Library/Frameworks 8 | /usr/local/include 9 | /usr/include 10 | /sw/include 11 | /opt/local/include 12 | /opt/csw/include 13 | /opt/include 14 | /usr/freeware/include 15 | ) 16 | 17 | FIND_LIBRARY(OIIO_LIBRARY 18 | NAMES OpenImageIO 19 | PATHS 20 | $ENV{OIIO_DIR}/lib 21 | ${OIIO_DIR}/lib 22 | /usr/lib 23 | /usr/local/lib 24 | ) 25 | 26 | IF (OIIO_INCLUDE_DIR AND OIIO_LIBRARY) 27 | SET(OIIO_FOUND TRUE) 28 | SET(OIIO_LIBRARY_DIR ${OIIO_LIBRARY}) 29 | ELSE (OIIO_INCLUDE_DIR AND OIIO_LIBRARY) 30 | SET(OIIO_FOUND) 31 | SET(OIIO_LIBRARY_DIR) 32 | ENDIF (OIIO_INCLUDE_DIR AND OIIO_LIBRARY) 33 | -------------------------------------------------------------------------------- /compare.sh: -------------------------------------------------------------------------------- 1 | 2 | 3 | list="../tests/Alexs_Apt_2k.hdr 4 | ../tests/Arches_E_PineTree_3k.hdr 5 | ../tests/GCanyon_C_YumaPoint_3k.hdr 6 | ../tests/Mans_Outside_2k.hdr 7 | ../tests/Milkyway_small.hdr" 8 | 9 | #list="../tests/Alexs_Apt_2k.hdr" 10 | 11 | methods="rgbm rgbe rgbd rgbd2" 12 | for method in ${methods} 13 | do 14 | for file in ${list} 15 | do 16 | 17 | filename=$(basename "$file") 18 | extension="${filename##*.}" 19 | filename="${filename%.*}" 20 | 21 | range="$(iinfo --stats ${file} | grep Max | sed 's/ *Stats Max://' | sed 's/(float)//g' | tr ' ' '\n' | sort -r | head -1 )" 22 | 23 | ./rgbx -m ${method} -r ${range} ${file} "${filename}_${method}_${range}.png" 24 | ./rgbx -d -m ${method} -r ${range} "${filename}_${method}_${range}.png" "${filename}_${method}_${range}_decode.tif" 25 | 26 | done 27 | done 28 | -------------------------------------------------------------------------------- /compile.sh: -------------------------------------------------------------------------------- 1 | CXX=$(which g++-4.9) 2 | CC=$(which gcc-4.9) 3 | if [[ -z "${CXX}" ]] 4 | then 5 | CXX=g++ 6 | CC=gcc 7 | fi 8 | 9 | 10 | #cmake ../ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=${CXX} -DCMAKE_C_COMPILER=${CC} 11 | cmake ../ -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=${CXX} -DCMAKE_C_COMPILER=${CC} 12 | -------------------------------------------------------------------------------- /rgbx.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | OIIO_NAMESPACE_USING 13 | 14 | 15 | struct Operator { 16 | virtual ~Operator() {} 17 | virtual void encode( float rgb[3], uint8_t rgbm[4]) const = 0; 18 | virtual void decode( uint8_t rgbm[4], float rgb[3]) const = 0; 19 | virtual const char* getName() const = 0; 20 | }; 21 | 22 | 23 | struct RGBM : Operator { 24 | const double _range; 25 | 26 | RGBM( double range ) : _range( range ) { 27 | } 28 | 29 | const char* getName() const { return "rgbm"; } 30 | void encode( float rgb[3], uint8_t rgbm[4]) const { 31 | 32 | const double oneOverRange = 1.0 / _range; 33 | 34 | double r = rgb[0] * oneOverRange; 35 | double g = rgb[1] * oneOverRange; 36 | double b = rgb[2] * oneOverRange; 37 | 38 | double maxRGB = std::max( r , std::max( g, b ) ); 39 | double a = ceil(maxRGB * 255.0) / 255.0; 40 | 41 | rgbm[0] = round(255.0*std::min(r / a, 1.0)); 42 | rgbm[1] = round(255.0*std::min(g / a, 1.0)); 43 | rgbm[2] = round(255.0*std::min(b / a, 1.0)); 44 | rgbm[3] = round(255.0*std::min(a, 1.0)); 45 | } 46 | 47 | void decode( uint8_t rgbm[4], float rgb[3] ) const { 48 | double a = rgbm[3] * _range / ( 255.0 * 255.0 ); 49 | rgb[0] = rgbm[0] * a; 50 | rgb[1] = rgbm[1] * a; 51 | rgb[2] = rgbm[2] * a; 52 | } 53 | }; 54 | 55 | 56 | //http://vemberaudio.se/graphics/RGBdiv8.pdf 57 | struct RGBD : Operator { 58 | 59 | const char* getName() const { return "rgbd"; } 60 | 61 | void encode( float rgb[3], uint8_t rgbm[4]) const { 62 | 63 | double maxRGB = std::max( std::max( rgb[0], 1.0f) , std::max( rgb[1], rgb[2] ) ); 64 | if ( maxRGB > 8.0 ) { 65 | //std::cout << maxRGB << std::endl; 66 | } 67 | double f = 255.0/maxRGB; 68 | rgbm[0] = rgb[0] * f; 69 | rgbm[1] = rgb[1] * f; 70 | rgbm[2] = rgb[2] * f; 71 | rgbm[3] = f; 72 | } 73 | 74 | void decode( uint8_t rgbm[4], float rgb[3] ) const { 75 | double f = 1.0 / rgbm[3]; 76 | rgb[0] = rgbm[0] * f; 77 | rgb[1] = rgbm[1] * f; 78 | rgb[2] = rgbm[2] * f; 79 | } 80 | }; 81 | 82 | 83 | //http://iwasbeingirony.blogspot.fr/2010/06/difference-between-rgbm-and-rgbd.html 84 | struct RGBDRange : Operator { 85 | 86 | const double _range; 87 | 88 | RGBDRange( double range): _range( range ) { 89 | } 90 | 91 | const char* getName() const { return "rgbd2"; } 92 | 93 | void encode( float rgb[3], uint8_t rgbm[4]) const { 94 | 95 | double maxRGB = std::max( std::max( rgb[0], 1.0f ) , std::max( rgb[1], rgb[2] ) ); 96 | 97 | double D = std::max( _range / maxRGB, 1.0); 98 | D = std::min( floor( D ) / 255.0, 1.0); 99 | double f = D * (255.0/_range); 100 | rgbm[0] = 255.0 * rgb[0] * f; 101 | rgbm[1] = 255.0 * rgb[1] * f; 102 | rgbm[2] = 255.0 * rgb[2] * f; 103 | rgbm[3] = 255.0 * D; 104 | // return float4(rgb.rgb * (D * (255.0 / MaxRange)), D); 105 | } 106 | 107 | void decode( uint8_t rgbm[4], float rgb[3] ) const { 108 | //return rgbd.rgb * ((MaxRange / 255.0) / rgbd.a); 109 | double f = _range / 255.0 / (rgbm[3]/255.0); 110 | rgb[0] = rgbm[0] * f / 255.0; 111 | rgb[1] = rgbm[1] * f / 255.0; 112 | rgb[2] = rgbm[2] * f / 255.0; 113 | } 114 | }; 115 | 116 | 117 | /* Converts a float RGB pixel into byte RGB with a common exponent. 118 | * Source :http://www.graphics.cornell.edu/~bjw/rgbe.html 119 | */ 120 | struct RGBE : Operator { 121 | 122 | const char* getName() const { return "rgbe"; } 123 | void encode( float rgb[3], uint8_t rgbe[4]) const { 124 | 125 | double maxRGB = std::max( rgb[0], std::max( rgb[1], rgb[2] ) ); 126 | 127 | if(maxRGB < 1e-32) { 128 | rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; 129 | } else { 130 | int e; 131 | double v = frexp(maxRGB, &e) * 256.0 / maxRGB; 132 | rgbe[0] = (unsigned char)(rgb[0] * v); 133 | rgbe[1] = (unsigned char)(rgb[1] * v); 134 | rgbe[2] = (unsigned char)(rgb[2] * v); 135 | rgbe[3] = (unsigned char)(e + 128); 136 | } 137 | } 138 | 139 | void decode( uint8_t rgbe[4], float rgb[3] ) const { 140 | double a = rgbe[3]; 141 | double f = ldexp(1.0, a - (128.0 + 8.0)); 142 | rgb[0] = rgbe[0] * f; 143 | rgb[1] = rgbe[1] * f; 144 | rgb[2] = rgbe[2] * f; 145 | } 146 | 147 | }; 148 | 149 | 150 | 151 | struct Process { 152 | 153 | Operator* _operator; 154 | bool _generateError; 155 | 156 | Process() { 157 | _generateError = true; 158 | } 159 | 160 | void setRGBM( double range ) { 161 | _operator= new RGBM( range ); 162 | std::cout << "use range " << range << " for " << _operator->getName() << std::endl; 163 | } 164 | 165 | void setRGBD2( double range ) { 166 | _operator = new RGBDRange( range ); 167 | std::cout << "use range " << range << " for " << _operator->getName() << std::endl; 168 | } 169 | void setRGBD() { 170 | _operator = new RGBD(); 171 | //std::cout << "use range " << range << " for " << _operator->getName() << std::endl; 172 | } 173 | 174 | void setRGBE() { 175 | _operator= new RGBE(); 176 | } 177 | 178 | void encode( const std::string& filenameIn, const std::string& filenameOut) { 179 | 180 | std::cout << "encode " << filenameIn << " to " << filenameOut << " with method " << _operator->getName() << std::endl; 181 | 182 | ImageBuf src(filenameIn); 183 | src.read(); 184 | ImageSpec specIn = src.spec(); 185 | 186 | ImageSpec specOut(specIn.width, specIn.height, 4, TypeDesc::UINT8); 187 | specOut.attribute("oiio:UnassociatedAlpha", 1); 188 | ImageBuf dst(filenameOut, specOut); 189 | 190 | 191 | ImageSpec spec = src.spec(); 192 | int width = spec.width, 193 | height = spec.height; 194 | 195 | ImageBuf::Iterator iteratorSrc(src, 0, width, 0, height); 196 | ImageBuf::Iterator iteratorDst(dst, 0, width, 0, height); 197 | 198 | float result[3]; 199 | float inTmp[3]; 200 | float* in; 201 | float biggest = 0.0; 202 | for (; iteratorDst.valid(); iteratorDst++, iteratorSrc++) { 203 | iteratorSrc.pos( iteratorDst.x(), iteratorDst.y(), iteratorDst.z()); 204 | 205 | float* inRaw = (float*)iteratorSrc.rawptr(); 206 | uint8_t* out = (uint8_t*)iteratorDst.rawptr(); 207 | 208 | // we assume to have at least 3 channel in inputs, but it could be greyscale 209 | if ( specIn.nchannels < 3 ) { 210 | inTmp[0] = inRaw[0]; 211 | inTmp[1] = inRaw[0]; 212 | inTmp[2] = inRaw[0]; 213 | in = inTmp; 214 | } else { 215 | in = inRaw; 216 | } 217 | 218 | (*_operator).encode(in, out ); 219 | (*_operator).decode(out, result ); 220 | 221 | biggest = std::max( 222 | std::max( double(biggest), fabs(result[0]-in[0]) ), 223 | std::max( fabs(result[1]-in[1]), fabs(result[2]-in[2]) ) ); 224 | 225 | } 226 | std::cout << "error max " << biggest << std::endl; 227 | dst.write(filenameOut); 228 | 229 | } 230 | 231 | void decode( const std::string& filenameIn, const std::string& filenameOut) { 232 | 233 | std::cout << "decode " << filenameIn << " to " << filenameOut << " with method " << _operator->getName() << std::endl; 234 | 235 | ImageInput* image = ImageInput::create(filenameIn); 236 | ImageSpec config = ImageSpec(); 237 | ImageSpec specIn = ImageSpec(); 238 | config.attribute("oiio:UnassociatedAlpha", 1); 239 | image->open(filenameIn, specIn, config ); 240 | uint8_t* data = new uint8_t[specIn.width * specIn.height * specIn.nchannels ]; 241 | image->read_image( TypeDesc::UINT8, data ); 242 | image->close(); 243 | 244 | ImageBuf src(specIn, data); 245 | 246 | 247 | int width = specIn.width, 248 | height = specIn.height; 249 | 250 | ImageSpec specOut(specIn.width, specIn.height, 3, TypeDesc::FLOAT ); 251 | ImageBuf dst(filenameOut, specOut); 252 | 253 | ImageBuf::Iterator iteratorSrc(src, 0, width, 0, height); 254 | ImageBuf::Iterator iteratorDst(dst, 0, width, 0, height); 255 | 256 | for (; iteratorDst.valid(); iteratorDst++, iteratorSrc++) { 257 | iteratorSrc.pos( iteratorDst.x(), iteratorDst.y(), iteratorDst.z()); 258 | 259 | uint8_t* in = (uint8_t*)iteratorSrc.rawptr(); 260 | float* out = (float*)iteratorDst.rawptr(); 261 | 262 | (*_operator).decode(in, out ); 263 | } 264 | 265 | dst.write(filenameOut); 266 | } 267 | 268 | 269 | void error(ImageBuf& src, ImageBuf& encoded, const std::string& errorFilename ) { 270 | 271 | ImageSpec spec = src.spec(); 272 | int width = spec.width, 273 | height = spec.height; 274 | 275 | //ImageSpec specError(spec.width, spec.height, 3, TypeDesc::FLOAT); 276 | ImageBuf error( errorFilename, spec); 277 | 278 | ImageBuf::Iterator iteratorSrc(src, 0, width, 0, height); 279 | ImageBuf::Iterator iteratorDst(encoded, 0, width, 0, height); 280 | ImageBuf::Iterator iteratorError(error, 0, width, 0, height); 281 | 282 | float decoded[3]; 283 | for (; iteratorDst.valid(); iteratorDst++, iteratorSrc++, iteratorError++) { 284 | iteratorDst.pos( iteratorDst.x(), iteratorDst.y(), iteratorDst.z()); 285 | 286 | float* in = (float*)iteratorSrc.rawptr(); 287 | uint8_t* encoded = (uint8_t*)iteratorDst.rawptr(); 288 | float* error = (float*)iteratorError.rawptr(); 289 | 290 | (*_operator).decode(encoded, decoded ); 291 | error[0] = fabs(in[0] - decoded[0] ); 292 | error[1] = fabs(in[1] - decoded[1] ); 293 | error[2] = fabs(in[2] - decoded[2] ); 294 | } 295 | 296 | error.write(errorFilename); 297 | } 298 | 299 | 300 | }; 301 | 302 | 303 | int usage( char* command) { 304 | std::cout << "usage: " << command << " [-d] [-m method] [-r range] input output" << std::endl; 305 | std::cout << "encode/decode image into rgbm/rgbd/rgbe png" << std::endl; 306 | return 1; 307 | } 308 | 309 | int main(int argc, char* argv[]) { 310 | 311 | std::string input; 312 | std::string output; 313 | std::string method = "rgbm"; 314 | double range = 8.0; 315 | bool encode = true; 316 | int c; 317 | 318 | while ((c = getopt(argc, argv, "dm:r:")) != -1) { 319 | switch (c) { 320 | case 'm': 321 | method = optarg; break; 322 | case 'r': 323 | range = atof(optarg); break; 324 | case 'd': 325 | encode = false; break; 326 | default: 327 | return usage(argv[0]); 328 | } 329 | } 330 | 331 | if ( optind < argc-1 ) { 332 | 333 | input = std::string( argv[optind] ); 334 | output = std::string( argv[optind+1] ); 335 | 336 | Process process; 337 | if ( method == "rgbm" ) { 338 | process.setRGBM( range ); 339 | } else if ( method == "rgbd2" ) { 340 | process.setRGBD2( range ); 341 | } else if ( method == "rgbd" ) { 342 | process.setRGBD(); 343 | } else { 344 | process.setRGBE(); 345 | } 346 | 347 | if ( encode ) process.encode( input, output ); 348 | else process.decode( input, output ); 349 | 350 | } else { 351 | 352 | return usage(argv[0]); 353 | 354 | } 355 | 356 | return 0; 357 | } 358 | -------------------------------------------------------------------------------- /rgbx.sh: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/bash 2 | filename=$1 3 | range=$2 4 | methods="rgbe rgbm rgbd rgbd2" 5 | 6 | if [ "$#" == 3 ] 7 | then 8 | methods="$3" 9 | fi 10 | 11 | for method in ${methods} 12 | do 13 | ./rgbx -m ${method} -r ${range} ${filename} "${filename}_${method}_${range}.png" 14 | ./rgbx -d -m ${method} -r ${range} "${filename}_${method}_${range}.png" "${filename}_${method}_${range}_decode.tif" 15 | iinfo --stats "${filename}_${method}_${range}_decode.tif" 16 | done 17 | --------------------------------------------------------------------------------