C++ Bitmap to ASCII, the result is divided in half

130 views Asked by At

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

0

There are 0 answers