Home > Back-end >  How to write binary into a file using std::ostream::put?
How to write binary into a file using std::ostream::put?

Time:07-11

I'm trying to implement TGA-Compression using run-length-encoding algorithm. One problem that I'm having is outputting the bytes, that give information how many times a color is repeated, into a file. I have already read https://en.cppreference.com/w/cpp/io/basic_ostream/put but it didn't help me much.

void encode(const TGAImage& inputImage, std::ostream& os){
        TGAImageHeader header = inputImage.getHeader();
        header.setCompressed();
        os << header;

        std::vector<ARGB32> data = inputImage.getData();
        //it1 where it starts repeating and it2 where it ends, both are iterators, repeat if the color repeats
        auto lambda = [&](const auto& it1, const auto& it2, const bool& repeat){
            auto dist = std::distance(it1, it2);
            if(dist > 0 || repeat){
                os.put(dist);
                os << *it1;
        };   

        auto i = data.begin();
        while(i != data.end()){
            auto start = std::adjacent_find(i, data.end(),
                [&](const ARGB32& a, const ARGB32& b){return a == b;});

            auto end = std::find_if(start, data.end(),
                [&](const auto& a){ return a != *start; });

            lambda(start, end, start != end);
            i = end;
        }
    }

when i try os.put(dist) i get the following warning:

src/runlengthencoding.cpp:23:24: warning: conversion from ‘long int’ to ‘std::basic_ostream<char>::char_type’ {aka ‘char’} may change value [-Wconversion]
   23 |                 os.put(dist);
      |                        ^~~~

The only thing that i could find was this thread: C Using "putchar" to output a word in Binary and i tried doing it the way the second comments suggests but i get a warnings as well and i don't get how os.put(dist >> 24); works.

src/runlengthencoding.cpp:42:44:   required from here
src/runlengthencoding.cpp:20:29: warning: conversion from ‘long int’ to ‘std::basic_ostream<char>::char_type’ {aka ‘char’} may change value [-Wconversion]
   20 |                 os.put(dist >> 24);
      |                        ~~~~~^~~~~

src/runlengthencoding.cpp:21:29: warning: conversion from ‘long int’ to ‘std::basic_ostream<char>::char_type’ {aka ‘char’} may change value [-Wconversion]
   21 |                 os.put(dist >> 16);
      |                        ~~~~~^~~~~

src/runlengthencoding.cpp:22:29: warning: conversion from ‘long int’ to ‘std::basic_ostream<char>::char_type’ {aka ‘char’} may change value [-Wconversion]
   22 |                 os.put(dist >> 8);
      |                        ~~~~~^~~~

src/runlengthencoding.cpp:23:24: warning: conversion from ‘long int’ to ‘std::basic_ostream<char>::char_type’ {aka ‘char’} may change value [-Wconversion]
   23 |                 os.put(dist);
      |                        ^~~~

CodePudding user response:

I'm going off of https://www.fileformat.info/format/tga/egff.htm (search for "run-length encoding") as I'm not familiar with the TGA format. It says that it expects a single byte for the run length, with a max value of 127. So, you need to make sure you're limiting your runs to 127. Then, since you know that your value is small enough to fit in a byte, you can use static_cast< char >( dist ) to tell the compiler you're not worried about the conversion.

That page also says that "The high bit of the pixel count value is always set to 1 to indicate that this is a run-length encoded packet." To set the high bit of the byte, you can use a binary "or" with 0x80 (a byte value with only the high bit set): static_cast< char >( dist ) | 0x80. The compiler might complain that 0x80 is too big to fit in a signed char, because 0x80 = 128 and signed chars hold numbers from -128 to 127. If it does complain, you can force it with static_cast< char >( 0x80 ).

CodePudding user response:

Here's some code that given two iterators it1 and it2 writes a TGA run-length encoded sequence of identical pixels.

size_t dist = std::distance(it1, it2);
while (dist > 0)
{
    size_t length = std::min(dist, 128); // run lengths are limited to 128
    uint8_t byte = (uint8_t)length;
    --byte;         // subtract one from length
    byte |= 128;    // and set the high bit
    os.put(byte);   // output length byte
    os << *it1;     // output pixel
    dist -= length; // adjust distance left to output
}

CodePudding user response:

ostream::put() takes a single char, but you are giving it a long int instead, which is larger than a char. So the compiler is warning you that the value of the integer is being truncated, thus it will lose precision for any value greater than CHAR_MAX (which, on most systems where char is 8 bits, is 127 if char is signed, or 255 if unsigned).

Which is fine in the bit-shifting case, as it is put()'ing the individual bytes of the integer, which each fit within a char:

  • dist >> 24 is shifting the value of bits 24..31 (the 4th highest byte) into bits 0..7 (the lowest byte). The result is then passed to put(), truncating it to 8 bits when converted to char.

  • dist >> 16 is shifting bits 16..23 (the 3rd highest byte) into bits 0..7.

  • dist >> 8 is shifting bits 8..15 (the 2nd highest byte) into bits 0..7.

  • passing dist as-is does not shift anything, so bits 0..7 already contain the value of the 1st highest byte, aka the lowest byte.

Otherwise, you can use ostream::write() instead:

os.write((char*)&dist, sizeof(dist));

Just be aware that this approach is affected by the size and endian (byte order) of the integer. The bit-shifting approach is not affected by that.

  • Related