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 toput()
, truncating it to 8 bits when converted tochar
.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.