I am writing a program that converts a 24 bit bmp file into ascii text and saves it to a txt file. I am at the very end of the project and I have a problem with saving the result to a file. I'm trying to convert a "heart" with different shades of gray to an ascii file, but the result is like it's cut in half diagonally and the right part is displayed first, then the left. I upload my code, the bmp file I'm trying to convert and the result. Please help.
I#include <iostream>
#include <fstream>
#include <cstdint>
using namespace std;
#pragma pack(push, 1)
struct BMPHeader {
char signature[2];
uint32_t fileSize;
uint16_t reserved1;
uint16_t reserved2;
uint32_t dataOffset;
};
struct DIBHeader {
uint32_t headerSize;
int32_t width;
int32_t height;
uint16_t colorPlanes;
uint16_t bitsPerPixel;
uint32_t compression;
uint32_t dataSize;
};
struct RGBPixel {
uint8_t blue;
uint8_t green;
uint8_t red;
};
#pragma pack(pop)
BMPHeader bmpHeader;
DIBHeader dibHeader;
void readPixels(ifstream& file, const DIBHeader& dibHeader, RGBPixel* pixels) {
// chagne coursor to the front
file.seekg(bmpHeader.dataOffset, ios::beg);
// load pixels
const int liczbaPikseli = dibHeader.width * dibHeader.height;
file.read(reinterpret_cast<char*>(pixels), sizeof(RGBPixel) * liczbaPikseli);
}
void saveBrightnessInfo(const char* filename, const uint8_t* brightnessValues, int width, int height) {
ofstream outFile(filename);
if (!outFile.is_open()) {
cerr << "Nie można otworzyć pliku do zapisu informacji o jasności." << endl;
return;
}
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
outFile << "Piksel " << i * width + j + 1 << ": ";
outFile << "Jasność: " << static_cast<int>(brightnessValues[i * width + j]) << endl;
}
}
outFile.close();
}
void convertToAscii(const uint8_t* brightnessValues, char* asciiArt, int width, int height) {
const char asciiChars[] = {'@', '#', '%', 'x', 'o', ';', ':', ',', '.', ' '};
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
// Normalize brightness to a range [0, 9]
int index = static_cast<int>((brightnessValues[i * width + j] / 255.0) * 9.0);
// Assign the corresponding ASCI character
asciiArt[i * width + j] = asciiChars[index];
}
}
}
void saveAsciiArt(const char* filename, const char* asciiArt, int width, int height) {
ofstream outFile(filename);
if (!outFile.is_open()) {
cerr << "Can't open file to save ASCII art." << endl;
return;
}
for (int i = height - 1; i >= 0; --i) { // Start from the last row and move upward
for (int j = 0; j < width; ++j) {
outFile << asciiArt[i * width + j];
}
outFile << "\r\n"; // carriage return and new line
}
outFile.close();
}
// Change RGB to brigthness (waga: 0.299*R + 0.587*G + 0.114*B)
uint8_t calculateBrightness(const RGBPixel& pixel) {
return static_cast<uint8_t>(0.299 * pixel.red + 0.587 * pixel.green + 0.114 * pixel.blue);
}
int main() {
ifstream file("test3.bmp", ios::binary);
if (!file.is_open()) {
cerr << "Can't open file." << endl;
return 1;
}
// HEADER
file.read(reinterpret_cast<char*>(&bmpHeader), sizeof(BMPHeader));
cout << "Signature: " << bmpHeader.signature[0] << bmpHeader.signature[1] << endl;
cout << "File size: " << bmpHeader.fileSize << endl;
if ((bmpHeader.signature[0] != 'B') || (bmpHeader.signature[1] != 'M')) {
file.close();
return 0;
}
// DIB
file.seekg(14, std::ios::beg);
file.read(reinterpret_cast<char*>(&dibHeader), sizeof(DIBHeader));
cout << "Header size: " << dibHeader.headerSize << " pikseli" << endl;
cout << "Width: " << dibHeader.width << " pikseli" << endl;
cout << "Height: " << dibHeader.height << " pikseli" << endl;
cout << "Bits per pixel: " << dibHeader.bitsPerPixel << endl;
cout << "Compression: " << dibHeader.compression << endl;
if ((dibHeader.bitsPerPixel != 24) || (dibHeader.compression != 0) || (dibHeader.headerSize != 40)) {
file.close();
return 0;
}
const int liczbaPikseli = dibHeader.width * dibHeader.height;
RGBPixel* pixels = new RGBPixel[liczbaPikseli];
readPixels(file, dibHeader, pixels);
uint8_t* brightnessValues = new uint8_t[liczbaPikseli];
for (int i = 0; i < liczbaPikseli; ++i) {
brightnessValues[i] = calculateBrightness(pixels[i]);
}
saveBrightnessInfo("brightness_info.txt", brightnessValues, dibHeader.width, dibHeader.height);
char* asciiArt = new char[liczbaPikseli];
convertToAscii(brightnessValues, asciiArt, dibHeader.width, dibHeader.height);
saveAsciiArt("ascii_wynik.txt", asciiArt, dibHeader.width, dibHeader.height);
delete[] pixels;
delete[] brightnessValues;
delete[] asciiArt;
file.close();
return 0;
}
BMP file: BMP file
ascii output: Ascii output
Thank you Raymond Chen, it is working fine now :)
Change to code that I did:
void readPixels(ifstream& file, const DIBHeader& dibHeader, RGBPixel* pixels) {
file.seekg(bmpHeader.dataOffset, ios::beg);
const int liczbaPikseli = dibHeader.width * dibHeader.height;
const int stride = (dibHeader.bitsPerPixel * dibHeader.width + 7) / 8;
const int paddedStride = (stride + 3) & ~3; // zaokraglanie
char padding[3];
for (int i = 0; i < dibHeader.height; ++i) {
file.read(reinterpret_cast<char*>(&pixels[i * dibHeader.width]), stride);
if (stride != paddedStride) {
file.read(padding, paddedStride - stride);
}
}
}