C ++将32位bmp图像转换为24位bmp和16位bmp

时间:2018-07-19 19:41:43

标签: windows winapi

尝试将32位图像B8R8G8A8转换为24位图像R8G8B8和16位R5G5B5。 但是结果很奇怪,也许我不明白如何正确转换图像。如何正确执行并修复颜色?

输入图片:

enter image description here

之后 Convert32to16()

enter image description here

之后 Convert32to24()

enter image description here

stdafx.h

// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//

#pragma once

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>
#include <iostream>
#include <fstream>
#include <windows.h>


// TODO: reference additional headers your program requires here

ImageConverter.cpp

#include "stdafx.h"


using std::cout;
using std::endl;
using std::ofstream;
using std::ifstream;

void Convert32to24(void* B8G8R8A8, BYTE* R8G8B8, int width, int height)
{
    long B8G8R8A8Size = (width * height * 4);
    long j = 0;
    for (long i = 0; i < (B8G8R8A8Size - 3); i = i + 4)
    {
        BYTE Red = ((PBYTE)B8G8R8A8)[i + 2];
        BYTE Green = ((PBYTE)B8G8R8A8)[i + 1];
        BYTE Blue = ((PBYTE)B8G8R8A8)[i];
        BYTE Alpha = ((PBYTE)B8G8R8A8)[i + 3];

        R8G8B8[j] = Red;
        R8G8B8[j + 1] = Green;
        R8G8B8[j + 2] = Blue;
        j = j + 3;
    }
}

void Convert32to16(void* B8G8R8A8, BYTE* R5G5B5, int width, int height)
{
    long B8G8R8A8Size = (width * height * 4);

    long j = 0;
    for (long i = 0; i < (B8G8R8A8Size - 3); i = i + 4)
    {
        BYTE Red = ((PBYTE)B8G8R8A8)[i + 2] >> 3;
        BYTE Green = ((PBYTE)B8G8R8A8)[i + 1] >> 3;
        BYTE Blue = ((PBYTE)B8G8R8A8)[i] >> 3;
        BYTE Alpha = ((PBYTE)B8G8R8A8)[i + 3];

        uint16_t RGB565 = ((Red >> 3) << 11) | ((Green >> 2) << 5) | (Blue >> 3);

        R5G5B5[j] = RGB565 >> 8;
        R5G5B5[j + 1] = RGB565 & 0xFF;
        j = j + 2;
    }
}

void WriteDataToBmp(const WCHAR *filename, void *imageData, int width, int height, int BitCount, int bytesPerPixel)
{
    HANDLE hdl = INVALID_HANDLE_VALUE;
    DWORD bytesWritten;
    BITMAPFILEHEADER fileHeader;
    BITMAPINFOHEADER fileInfo;

    fileInfo.biSize = sizeof(BITMAPINFOHEADER);
    fileInfo.biBitCount = BitCount;
    fileInfo.biCompression = BI_RGB;
    fileInfo.biWidth = width;
    fileInfo.biHeight = 0 - height;
    fileInfo.biPlanes = 1;
    fileInfo.biSizeImage = (width * height * bytesPerPixel);

    fileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + fileInfo.biSizeImage;
    fileHeader.bfType = 'MB';
    fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    hdl = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);

    if (hdl == INVALID_HANDLE_VALUE)
    {
        return;
    }

    WriteFile(hdl, &fileHeader, sizeof(fileHeader), &bytesWritten, NULL);
    WriteFile(hdl, &fileInfo, sizeof(fileInfo), &bytesWritten, NULL);
    WriteFile(hdl, imageData, fileInfo.biSizeImage, &bytesWritten, NULL);

    CloseHandle(hdl);
}

unsigned char* ReadDataFromBmp(char* filename)
{
    FILE* f = fopen(filename, "rb");
    unsigned char info[54];
    fread(info, sizeof(unsigned char), 54, f);
    int width = *(int*)&info[18];
    int height = *(int*)&info[22];

    int size = abs(4 * width * height);
    unsigned char* data = new unsigned char[size];
    fread(data, sizeof(unsigned char), size, f);
    fclose(f);
    return data;
}

int main(int args, char** cat) {

    int width = 1440;
    int height = 900;

    int bytesOnPixel;
    BYTE *OutputImage24Bit = new BYTE[width * height * 3];
    BYTE *OutputImage16Bit = new BYTE[width * height * 2];
    unsigned char* inputImage32Bit = ReadDataFromBmp((char*)"E:/TestImage.bmp");

    bytesOnPixel = 2;
    Convert32to16(inputImage32Bit, OutputImage16Bit, width, height);
    WriteDataToBmp(L"E:/TestImage16bit.bmp", OutputImage16Bit, width, height, 8 * bytesOnPixel, bytesOnPixel);

    bytesOnPixel = 3;
    Convert32to24(inputImage32Bit, OutputImage24Bit, width, height);
    WriteDataToBmp(L"E:/TestImage24bit.bmp", OutputImage24Bit, width, height, 8 * bytesOnPixel, bytesOnPixel);
    return 1;
}

1 个答案:

答案 0 :(得分:1)

  

fileInfo.biCompression = BI_RGB;

16位位图使用BI_BITFIELDS压缩。此外,必须使用16位位图填充颜色表,以显示它是使用555格式,565格式还是其他格式。

24位和16位位图需要填充。但是,如果以字节为单位的宽度是4的倍数,那么这不是问题。通常,您无法逐像素读取/写入像素,因为填充会丢弃所有内容。而是进行2个循环以遍历高度和宽度。像素大小还取决于填充。

请注意,您可以使用或GDI +或WIC进行相同的操作。您可以将位图更改为不同的格式PixelFormat16bppRGB555, PixelFormat16bppRGB565, PixelFormat16bppARGB1555, PixelFormat24bppRGB...

GDI +示例:

int main()
{
    Gdiplus::GdiplusStartupInput tmp;
    ULONG_PTR token;
    Gdiplus::GdiplusStartup(&token, &tmp, NULL);

    auto *source = Gdiplus::Bitmap::FromFile(L"test.bmp");
    auto *destination = source->Clone(0, 0, source->GetWidth(), source->GetHeight(),
        PixelFormat16bppRGB565);

    CLSID clsid_bmp;
    CLSIDFromString(L"{557cf400-1a04-11d3-9a73-0000f81ef32e}", &clsid_bmp);
    destination->Save(L"copy.bmp", &clsid_bmp);
    delete destination;
    delete source;

    Gdiplus::GdiplusShutdown(token);
    return 0;
}

自制版本:(使用std::vector代替new/delete来存储)

void Convert32to24(const wchar_t* file, std::vector<BYTE> &src, int width, int height)
{
    int width_in_bytes_32 = width * 4;
    int width_in_bytes_24 = ((width * 24 + 31) / 32) * 4;
    DWORD size = width_in_bytes_24 * height;
    std::vector<BYTE> dst(size);

    for(int h = 0; h < height; h++)
        for(int w = 0; w < width; w++)
        {
            int i = h * width_in_bytes_32 + w * 4;
            int j = h * width_in_bytes_24 + w * 3;
            dst[j + 0] = src[i + 0];
            dst[j + 1] = src[i + 1];
            dst[j + 2] = src[i + 2];
        }

    BITMAPFILEHEADER bf = { 'MB', 54 + size, 0, 0, 54 };
    BITMAPINFOHEADER bi = { sizeof(bi), width, height, 1, 24, BI_RGB };
    std::ofstream fout(file, std::ios::binary);
    fout.write((char*)&bf, sizeof(bf));
    fout.write((char*)&bi, sizeof(bi));
    fout.write((char*)&dst[0], size);
}

void Convert32to16(const wchar_t* file, std::vector<BYTE> &src, int width, int height)
{
    int width_in_bytes_32 = width * 4;
    int width_in_bytes_16 = ((width * 16 + 31) / 32) * 4;
    DWORD size = width_in_bytes_16 * height;
    std::vector<BYTE> dst(size);

    for(int h = 0; h < height; h++)
        for(int w = 0; w < width; w++)
        {
            int i = h * width_in_bytes_32 + w * 4;
            int j = h * width_in_bytes_16 + w * 2;

            //555 format, each color is from 0 to 32, instead of 0 to 256
            uint16_t blu = (uint16_t)(src[i + 0] * 31.f / 255.f);
            uint16_t grn = (uint16_t)(src[i + 1] * 31.f / 255.f);
            uint16_t red = (uint16_t)(src[i + 2] * 31.f / 255.f);
            uint16_t sum = (red) | (grn << 5) | (blu << 10);
            memcpy(&dst[j], &sum, 2);
        }

    BITMAPFILEHEADER bf = { 'MB', 54 + size, 0, 0, 54 };
    BITMAPINFOHEADER bi = { sizeof(bi), width, height, 1, 16, BI_BITFIELDS };
    std::ofstream fout(file, std::ios::binary);
    fout.write((char*)&bf, sizeof(bf));
    fout.write((char*)&bi, sizeof(bi));

    //555 format
    COLORREF color[]{
        0b0000000000011111,//31
        0b0000001111100000,//31 << 5
        0b0111110000000000 //31 << 10
    };
    fout.write((char*)&color, sizeof(color));
    fout.write((char*)&dst[0], size);
}


int main() 
{
    const wchar_t* file_32 = L"E:\\TestImage.bmp";
    const wchar_t* file_16 = L"E:\\OutputImage16Bit.bmp";
    const wchar_t* file_24 = L"E:\\OutputImage24Bit.bmp";

    BITMAPFILEHEADER bh;
    BITMAPINFOHEADER bi;
    std::ifstream fin(file_32, std::ios::binary);
    if(!fin)
        return 0;
    fin.read((char*)&bh, sizeof(bh));
    fin.read((char*)&bi, sizeof(bi));
    if(bi.biBitCount != 32)
        return 0;
    std::vector<BYTE> source(bh.bfSize);
    fin.read((char*)&source[0], bh.bfSize);
    Convert32to16(file_16, source, bi.biWidth, bi.biHeight);
    Convert32to24(file_24, source, bi.biWidth, bi.biHeight);
    return 0;
}