Flaviu_
Flaviu_

Reputation: 1346

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.

Upvotes: 0

Views: 531

Answers (2)

Flaviu_
Flaviu_

Reputation: 1346

Wanting to solve the same issue in classic C++ style (as language sake), I wrote:

BOOL CMyDoc::SavePfmEndiannessInverted(CImg<float>& img, const CString sFileName)
{
CString sDrive, sDir;
_splitpath(sFileName, sDrive.GetBuffer(), sDir.GetBuffer(), NULL, NULL);

CString sTemp;
sTemp.Format(_T("%s%sTemp.tmp"), sDrive, sDir);

sDrive.ReleaseBuffer();
sDir.ReleaseBuffer();

CRemoveAfterUse TempFile(sTemp);
img.get_invert_endianness().save_pfm(TempFile.c_str());

CFile fileTemp;
if (! fileTemp.Open(sTemp, CFile::typeBinary))
    return FALSE;

char c;
UINT nRead = 0;
int nCount = 0;
ULONGLONG nPosition = 0;
CString sScale;
CByteArray arrHeader, arrData;
do
{
    nRead = fileTemp.Read((char*)&c, sizeof(char));
    switch (nCount)
    {
    case 0:
    case 1:
        arrHeader.Add(static_cast<BYTE>(c));
        break;
    case 2:             // retrieve the '1.0' string
        sScale += c;
        break;
    }
    if ('\n' == c)      // is new line
    {
        nCount++;
    }
    if (nCount >= 3)    // read the header, so go out
    {
        nPosition = fileTemp.GetPosition();
        break;
    }
}while (nRead > 0);

if (nPosition > 1)
{
    arrData.SetSize(fileTemp.GetLength() - nPosition);
    fileTemp.Read(arrData.GetData(), (UINT)arrData.GetSize());
}

fileTemp.Close();

CFile file;
if (! file.Open(sFileName, CFile::typeBinary | CFile::modeCreate | CFile::modeReadWrite))
    return FALSE;

CByteArray arrTemp;
ConvertCStringToByteArray(sScale, arrTemp);
arrHeader.Append(arrTemp);
arrHeader.Append(arrData);
file.Write(arrHeader.GetData(), (UINT)arrHeader.GetSize());
file.Close();

return TRUE;
}

But seem to not do the job, because the image result is darker

enter image description here

What I have done wrong here ? The code seem to me very clear, still, is not work as expected ...

Of course, this approach is more inefficient, I know, but as I said before, just for language sake.

I think it is nothing wrong with my code :)

Here is the trial:

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

and the memorial.pfm looks like this:

memorial.pfm

and memorial_inverted.pfm looks like this:

enter image description here

Upvotes: 0

Ted Lyngmo
Ted Lyngmo

Reputation: 117158

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";
}

Upvotes: 1

Related Questions