c++imageimage-processingvisual-c++cimg

Save exr/pfm as little endian


I am load a bmp file into a CImg object and I save it into pfm file. Successful. And this .pfm file I am using it into another library, but this library doesn't accept big-endian, just little endian.

    CImg<float> image;
    image.load_bmp(_T("D:\\Temp\\memorial.bmp"));
    image.normalize(0.0, 1.0);
    image.save_pfm(_T("D:\\Temp\\memorial.pfm"));

So, how can I save bmp file to pfm file as little endian, not big endian .. it is possible ?

Later edit:

I have checked first 5 elements from .pfm header file. This is the result without invert_endianness:

CImg<float> image;
image.load_bmp(_T("D:\\Temp\\memorial.bmp"));
image.normalize(0.0, 1.0);
image.save_pfm(_T("D:\\Temp\\memorial.pfm"));

PF
512
768
1.0
=øøù=€€=‘>

and this is the result with invert_endianness:

CImg<float> image;
image.load_bmp(_T("D:\\Temp\\memorial.bmp"));
image.invert_endianness();
image.normalize(0.0, 1.0);
image.save_pfm(_T("D:\\Temp\\memorial.pfm"));

PF
512
768
1.0
?yôx?!ù=‚ì:„ç‹?

Result is the same.


Solution

  • This is definitely not a proper answer but might work as a workaround for the time being.

    I didn't find out how to properly invert the endianness using CImgs functions, so I modified the resulting file instead. It's a hack. The result opens fine in GIMP an looks very close to the original image, but I can't say if it works with the library you are using. It may be worth a try.

    Comments in the code:

    #include "CImg/CImg.h"
    
    #include <algorithm>
    #include <filesystem> // >= C++17 must be selected as Language Standard
    #include <ios>
    #include <iostream>
    #include <iterator>
    #include <fstream>
    #include <string>
    
    using namespace cimg_library;
    namespace fs = std::filesystem;
    
    // a class to remove temporary files
    class remove_after_use {
    public:
        remove_after_use(const std::string& filename) : name(filename) {}
        remove_after_use(const remove_after_use&) = delete;
        remove_after_use& operator=(const remove_after_use&) = delete;
    
        const char* c_str() const { return name.c_str(); }
        operator std::string const& () const { return name; }
    
        ~remove_after_use() {
            try {
                fs::remove(name);
            }
            catch (const std::exception & ex) {
                std::cerr << "remove_after_use: " << ex.what() << "\n";
            }
        }
    private:
        std::string name;
    };
    
    // The function to hack the file saved by CImg
    template<typename T>
    bool save_pfm_endianness_inverted(const T& img, const std::string& filename) {
        remove_after_use tempfile("tmp.pfm");
    
        // get CImg's endianness inverted image and save it to a temporary file
        img.get_invert_endianness().save_pfm(tempfile.c_str());
    
        // open the final file
        std::ofstream os(filename, std::ios::binary);
    
        // read "tmp.pfm" and modify
        // The Scale Factor / Endianness line
        if (std::ifstream is; os && (is = std::ifstream(tempfile, std::ios::binary))) {
            std::string lines[3];
            // Read the 3 PFM header lines as they happen to be formatted by
            // CImg. Will maybe not work with another library.
            size_t co = 0;
            for (; co < std::size(lines) && std::getline(is, lines[co]); ++co);
    
            if (co == std::size(lines)) { // success
                // write the first two lines back unharmed:
                os << lines[0] << '\n' << lines[1] << '\n';
    
                if (lines[2].empty()) {
                    std::cerr << "something is wrong with the pfm header\n";
                    return false;
                }
    
                // add a '-' if it's missing, remove it if it's there: 
                if (lines[2][0] == '-') {       // remove the - to invert
                    os << lines[2].substr(1);
                }
                else {                          // add a - to invert
                    os << '-' << lines[2] << '\n';
                }
    
                // copy all the rest as-is:
                std::copy(std::istreambuf_iterator<char>(is),
                    std::istreambuf_iterator<char>{},
                    std::ostreambuf_iterator<char>(os));
            }
            else {
                std::cerr << "failed reading pfm header\n";
                return false;
            }
        }
        else {
            std::cerr << "opening files failed\n";
            return false;
        }
        return true;
    }
    
    int main()
    {
        CImg<float> img("memorial.bmp");
        img.normalize(0.f, 1.f);
        std::cout << "saved ok: " << std::boolalpha
                  << save_pfm_endianness_inverted(img, "memorial.pfm") << "\n";
    }