Иногда битый TGA-файл при записи из памяти

Я наблюдаю иногда странное поведение, когда пишу буфер изображения из памяти (содержащий RGBA-пиксельные данные, загруженные из tga-файла) в файл обратно как tga.

Буфер изображения загружается из TGA-файла с помощью алгоритма, похищенного и адаптированного из этого потока:

Загрузка файла tga / bmp в C ++ / OpenGL

Метод письма, который я украл и адаптировал по этому адресу:

http://www.paulbourke.net/dataformats/tga/

Вот минимальный скомпилируемый пример, где tileA.tga правильно сохраняется обратно на диск как tileA_new.tga, но TileB_new.tga не работает (рисунок выглядит странно с ложными пикселями)! Почему TileB_new.tga сломан?

Два исходных tga-файла отличаются, но их можно правильно просматривать в gimp и irfanview, и я дважды проверил алгоритм загрузки. Это работает, потому что, когда я рендерину буферы изображений обеих загруженных плиток (с OpenGL) на экран, они выглядят правильно! Но запись raw-буферов на диск ведет себя иначе, почему? Я сравнил заголовки исходных tga-файлов в hex-редакторе, но они равны. Также записанные tga-файлы имеют одинаковые заголовки.
Я вижу, что размер tileB.tga в 5 раз больше, чем tileA.tga, но это кажется правильным, потому что gimp / irfanview показывает их правильно. Может быть, вы видите ошибку, которую я сделал здесь?

// Небольшой visual-studio-проект, включающий оба tga-файла, можно скачать здесь
https://www.file-upload.net/download-13208817/StackOverflowTGA.zip.html

Минимальный пример:

#include <vector>
#include <fstream>

//special-sausage for microsoft
#ifdef _MSC_VER
#pragma warning(disable:4996)
#endif

//=========================================
// Code for loading a TGA-file
//=========================================
typedef union PixelInfo
{
std::uint32_t Colour;
struct
{
std::uint8_t R, G, B, A;
};
} *PPixelInfo;

class Tga
{
private:
std::vector<std::uint8_t> Pixels;
bool ImageCompressed;
std::uint32_t width, height, size, BitsPerPixel;

public:
Tga(const char* FilePath);
std::vector<std::uint8_t> GetPixels() { return this->Pixels; }
std::uint32_t GetWidth() const { return this->width; }
std::uint32_t GetHeight() const { return this->height; }
std::uint32_t GetBitsPerPixel() const { return this->BitsPerPixel; }
bool HasAlphaChannel() { return BitsPerPixel == 32; }
};

Tga::Tga(const char* FilePath)
{
std::fstream hFile(FilePath, std::ios::in | std::ios::binary);
if (!hFile.is_open()) { throw std::invalid_argument("File Not Found."); }

std::uint8_t Header[18] = { 0 };
std::vector<std::uint8_t> ImageData;
static std::uint8_t DeCompressed[12] = { 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
static std::uint8_t IsCompressed[12] = { 0x0, 0x0, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };

hFile.read(reinterpret_cast<char*>(&Header), sizeof(Header));

if (!std::memcmp(DeCompressed, &Header, sizeof(DeCompressed)))
{
BitsPerPixel = Header[16];
width = Header[13] * 256 + Header[12];
height = Header[15] * 256 + Header[14];
size = ((width * BitsPerPixel + 31) / 32) * 4 * height;

if ((BitsPerPixel != 24) && (BitsPerPixel != 32))
{
hFile.close();
throw std::invalid_argument("Invalid File Format. Required: 24 or 32 Bit Image.");
}

ImageData.resize(size);
ImageCompressed = false;
hFile.read(reinterpret_cast<char*>(ImageData.data()), size);
}
else if (!std::memcmp(IsCompressed, &Header, sizeof(IsCompressed)))
{
BitsPerPixel = Header[16];
width = Header[13] * 256 + Header[12];
height = Header[15] * 256 + Header[14];
size = ((width * BitsPerPixel + 31) / 32) * 4 * height;

if ((BitsPerPixel != 24) && (BitsPerPixel != 32))
{
hFile.close();
throw std::invalid_argument("Invalid File Format. Required: 24 or 32 Bit Image.");
}

PixelInfo Pixel = { 0 };
int CurrentByte = 0;
std::size_t CurrentPixel = 0;
ImageCompressed = true;
std::uint8_t ChunkHeader = { 0 };
int BytesPerPixel = (BitsPerPixel / 8);
ImageData.resize(static_cast<size_t>(width) * static_cast<size_t>(height) * sizeof(PixelInfo));

do
{
hFile.read(reinterpret_cast<char*>(&ChunkHeader), sizeof(ChunkHeader));

if (ChunkHeader < 128)
{
++ChunkHeader;
for (int I = 0; I < ChunkHeader; ++I, ++CurrentPixel)
{
hFile.read(reinterpret_cast<char*>(&Pixel), BytesPerPixel);

ImageData[CurrentByte++] = Pixel.B;
ImageData[CurrentByte++] = Pixel.G;
ImageData[CurrentByte++] = Pixel.R;
if (BitsPerPixel > 24) ImageData[CurrentByte++] = Pixel.A;
}
}
else
{
ChunkHeader -= 127;
hFile.read(reinterpret_cast<char*>(&Pixel), BytesPerPixel);

for (int I = 0; I < ChunkHeader; ++I, ++CurrentPixel)
{
ImageData[CurrentByte++] = Pixel.B;
ImageData[CurrentByte++] = Pixel.G;
ImageData[CurrentByte++] = Pixel.R;
if (BitsPerPixel > 24) ImageData[CurrentByte++] = Pixel.A;
}
}
} while (CurrentPixel < (static_cast<size_t>(width) * static_cast<size_t>(height)));
}
else
{
hFile.close();
throw std::invalid_argument("Invalid File Format. Required: 24 or 32 Bit TGA File.");
}

hFile.close();
this->Pixels = ImageData;
}

//=========================================
// code for writing a TGA-file
//=========================================
void writeTGA(const std::string &refFile, Tga &refTGA)
{
unsigned short width = static_cast<unsigned short>(refTGA.GetWidth());
unsigned short height = static_cast<unsigned short>(refTGA.GetWidth());
unsigned char bitsPerPixel = static_cast<unsigned char>(refTGA.GetBitsPerPixel());
unsigned char bitsAlphaChannel = (bitsPerPixel == 32 ? 8 : 0);

FILE * fptr = fopen(refFile.c_str(), "w");

putc(0, fptr);
putc(0, fptr);
putc(2, fptr);                          /* uncompressed RGB */
putc(0, fptr); putc(0, fptr);
putc(0, fptr); putc(0, fptr);
putc(0, fptr);
putc(0, fptr); putc(0, fptr);           /* X origin */
putc(0, fptr); putc(0, fptr);           /* y origin */
putc((width & 0x00FF), fptr);
putc((width & 0xFF00) / 256, fptr);
putc((height & 0x00FF), fptr);
putc((height & 0xFF00) / 256, fptr);
putc(bitsPerPixel, fptr);               /* 24/32 bit bitmap */
putc(bitsAlphaChannel, fptr);           /* When 32 bit, write 8, else 0 */

auto pixelData = refTGA.GetPixels();

for (size_t i = 0; i < static_cast<size_t>(width) * static_cast<size_t>(height) * (bitsPerPixel/8); i += (bitsPerPixel/8))
{
unsigned char r = pixelData[i];
unsigned char g = pixelData[i + 1];
unsigned char b = pixelData[i + 2];
unsigned char a = (bitsAlphaChannel == 8 ? pixelData[i + 3] : 0);

putc(b, fptr);
putc(g, fptr);
putc(r, fptr);

if (bitsAlphaChannel == 8)
putc(a, fptr);
}

fclose(fptr);
}

//=========================================
// main
//=========================================
int main()
{
Tga oTgaA("tileA.tga");
writeTGA("tileA_new.tga", oTgaA);  // works correct as aspected

Tga oTgaB("tileB.tga");
writeTGA("tileB_new.tga", oTgaB); // graphic-file has artefacts, why?
}

0

Решение

Поскольку мой комментарий, похоже, решил проблему (см. Выше ^^^^). Я расширю здесь.

В Windows существует значительная разница (по крайней мере, с помощью CRT от Microsoft) между записью файла в текстовом режиме и записью в двоичном режиме.

В частности, любой символ, который соответствует «\ n», будет расширен до двухсимвольной последовательности «\ r \ n». Далее некоторые функции применяют преобразования между символами MB и Unicode. Более подробную информацию можно найти в документации MSDN по fopen доступны по адресу: https://msdn.microsoft.com/en-us/library/yeby3zcb.aspx

Поэтому при чтении / записи нетекстовых данных обязательно передайте "rb" или же "wb" флаги для fopen по мере необходимости.

В системах Posix это соображение не применимо, однако, все же это хорошая практика, чтобы прояснить ваше намерение.

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]