首页 > 其他分享 >将24位BMP图像转换为8位BMP图像的实现方法

将24位BMP图像转换为8位BMP图像的实现方法

时间:2024-04-06 19:14:12浏览次数:27  
标签:24 std imageData color colors BMP vector 图像

项目说明
周末想起一个项目需要使用agg与8bits的BMP进行绘制,因agg不支持8bits BMP进行绘制,故自己实现了一个基于agg+rgb88进行绘制,绘制结果转为8bits的BMP.
从24位BMP图像到8位BMP图像的转换过程。转换后的图像文件将只使用256种颜色,但仍保留了原始图像的基本信息。

主要原理:

  1. 读取24位BMP图像数据:首先从给定的24位BMP图像文件中读取像素数据。在BMP文件中,像素数据紧跟在文件头之后。
  2. 统计颜色及其数量:对于读取到的每个像素,统计其RGB颜色值及其出现的次数。这样就得到了所有不同颜色的数量统计。
  3. 创建8位颜色表:根据统计结果,选择数量最多的256种颜色作为8位BMP的颜色表。对颜色表按颜色数量进行排序,然后选择前256种颜色作为颜色表。
  4. 转换为8位索引:遍历原始图像的每个像素,将其RGB颜色值映射到颜色表中,得到对应的索引值。这样就将24位的颜色值转换为8位的索引值。
  5. 写入8位BMP图像文件:根据8位索引值和颜色表,将转换后的像素数据写入到新的8位BMP图像文件中。首先写入BMP文件头信息,然后写入颜色表,最后写入像素数据。

效果展示
使用 AGG 绘制的图像,并将其转换为 8 位 BMP 格式。由于博客不支持BMP文件上传,使用 PNG 格式代替的展示图。

input_24bit.bmp 是直接使用Windows绘图随手画的一个,保存为24bits的BMP,这个默认是bgr排序,所以中间做了两次颜色bgr互转rgb,agg需要rgb排序,bmp需要bgr排序,读写bmp是简单实现的,也可以通过第三方库STB Image进行读写.

ps :STB Image 不支持8bits的BMP的读写,所以自己实现了8bits的BMP读写.
原理:统计每个像素点rgb值的个数,选取数量最多的256个颜色值,作为8bits的颜色表,并且将原像素数据转通过该颜色表,进行重新计算新的index值,并将结果写入24/8bits的BMP中.
cnblog不支持bmp上传,暂时使用png代替.

agg_output_image8bits.bmp

简单将代码与效果展示.
bitmapconverter.cpp


#include <fstream>
#include <iostream>
#include <algorithm>
#include <limits>
#include "bitmapconverter.h"

std::unordered_map<RGBAColor, size_t> BitmapConverterTo8bits::count_colors(const std::vector<RGBAColor> &image) {
    std::unordered_map<RGBAColor, size_t> color_counts;
    for (const auto& color : image) {
        color_counts[color]++;
    }
    return color_counts;
}

std::vector<RGBAColor> BitmapConverterTo8bits::select_palette(const std::unordered_map<RGBAColor, size_t> &color_counts) {
    std::vector<RGBAColor> palette;
    palette.reserve(256);

    // Sort colors by frequency
    std::vector<std::pair<RGBAColor, size_t>> sorted_colors(color_counts.begin(), color_counts.end());
    std::sort(sorted_colors.begin(), sorted_colors.end(), [](const auto& a, const auto& b) {
        return a.second > b.second;
    });

    // Select top 256 colors as palette
    for (size_t i = 0; i < std::min(256ULL, sorted_colors.size()); ++i) {
        palette.push_back(sorted_colors[i].first);
    }

    return palette;
}

double BitmapConverterTo8bits::color_distance_squared(const RGBAColor &c1, const RGBAColor &c2) {
    double dr = c1.r - c2.r;
    double dg = c1.g - c2.g;
    double db = c1.b - c2.b;
    return dr * dr + dg * dg + db * db;
}

void BitmapConverterTo8bits::convert_24bits_to_8bits(std::vector<unsigned char> &imageData)
{
    std::vector<RGBAColor> colors = convert_24bit_color(imageData);
    // Count color frequencies
    auto color_counts = count_colors(colors);

    // Select top 256 colors as palette
    m_palette = select_palette(color_counts);

    // Convert 24-bit color image to 8-bit color image
    m_indexed_image = convert_to_8bit(colors);
}

void BitmapConverterTo8bits::convert_16bits_to_8bits(std::vector<unsigned char> &imageData)
{
    std::vector<RGBAColor> colors;
    if(m_is_rgb1555){
        colors = convert_1555_color(imageData);
    }else{
        colors = convert_565_color(imageData);
    }

    // Count color frequencies
    auto color_counts = count_colors(colors);

    // Select top 256 colors as palette
    m_palette = select_palette(color_counts);

    // Convert 24-bit color image to 8-bit color image
    m_indexed_image = convert_to_8bit(colors);
}

void BitmapConverterTo8bits::convert_to_8bits(std::vector<unsigned char> &imageData)
{
    if(m_channels == 3){
        convert_24bits_to_8bits(imageData);
    }
    if(m_channels == 2){
        convert_16bits_to_8bits(imageData);
    }
}

std::vector<unsigned char> BitmapConverterTo8bits::load_bmp(const std::string &filename, bool is_rgb1555) {
    std::vector<unsigned char> imageData;
    bool ret = false;
    do {
        std::ifstream file(filename, std::ios::binary);
        if (!file.is_open()) {
            std::cerr << "Failed to open file." << std::endl;
            break;
        }
        BitmapFileHeader header;
        BitmapInfoHeader infoHeader;

        // Read file header
        file.read(reinterpret_cast<char*>(&header), sizeof(BitmapFileHeader));
        if (header.bfType != 0x4D42) { // Check if it's a valid bitmap file
            std::cerr << "File is not a valid bitmap." << std::endl;
            break;
        }
        // Read info header
        file.read(reinterpret_cast<char*>(&infoHeader), sizeof(BitmapInfoHeader));

        m_width  = infoHeader.biWidth;
        m_height = infoHeader.biHeight;
        m_channels = infoHeader.biBitCount  / 8;
        m_is_rgb1555 = is_rgb1555;
        // Check if file size matches image data size
        size_t expectedSize = m_width * m_height * m_channels;
        if (header.bfSize - header.bfOffBits != expectedSize) {
            std::cerr << "Invalid image data size." << std::endl;
            std::cerr << header.bfSize<<":" << header.bfOffBits<<":" << expectedSize << std::endl;
            std::cerr << header.bfSize<<":" << header.bfOffBits<<":" << expectedSize/3 << std::endl;
            break;
        }

        // Skip bitmap file header and info header
        file.seekg(header.bfOffBits, std::ios::beg);

        // Read image data
        imageData.resize(expectedSize);
        file.read(reinterpret_cast<char*>(imageData.data()), expectedSize);
        ret = true;
    } while (false);
    printf_info();
    return imageData;
}

void BitmapConverterTo8bits::swap_bmp_for_agg(std::vector<unsigned char> &imageData)
{
    size_t len = imageData.size();
    for (size_t i = 0; i < len - 2; i += 3) {
        std::swap(imageData[i], imageData[i + 2]);
    }
}

void BitmapConverterTo8bits::swap_agg_for_bmp(std::vector<unsigned char> &imageData)
{
    swap_bmp_for_agg(imageData);
}


bool BitmapConverterTo8bits::save_8bits_bmp(const std::string &filename) {
    std::ofstream file(filename, std::ios::binary);
    if (!file.is_open()) {
        std::cerr << "Failed to open output file." << std::endl;
        return false;
    }

    BitmapFileHeader fileHeader;
    BitmapInfoHeader infoHeader;
    // Fill file header information
    fileHeader.bfSize = sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader) + m_palette.size() * sizeof(RGBAColor) + m_indexed_image.size();
    fileHeader.bfOffBits = sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader) + m_palette.size() * sizeof(RGBAColor);

    // Fill info header information
    infoHeader.biSize = sizeof(BitmapInfoHeader);
    infoHeader.biWidth = m_width;
    infoHeader.biHeight = m_height;
    infoHeader.biBitCount = 8; // 8-bit indexed color bitmap
    infoHeader.biSizeImage = m_indexed_image.size();
    infoHeader.biClrUsed = m_palette.size();

    // Write file header and info header
    file.write(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader));
    file.write(reinterpret_cast<char*>(&infoHeader), sizeof(infoHeader));
    for (const auto& color : m_palette) {
        file.put(color.r);
        file.put(color.g);
        file.put(color.b);
        file.put(0);
    }
    file.write(reinterpret_cast<const char*>(&m_indexed_image[0]), m_indexed_image.size());
    std::cout << "Bitmap image saved successfully." << std::endl;
    return true;
}

bool BitmapConverterTo8bits::save_24bits_bmp(const std::string &filename, std::vector<unsigned char> &imageData)
{
    std::ofstream file(filename, std::ios::binary);
    if (!file.is_open()) {
        std::cerr << "Failed to open output file." << std::endl;
        return false;
    }

    BitmapFileHeader fileHeader;
    BitmapInfoHeader infoHeader;
    // Fill file header information
    fileHeader.bfSize = sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader)  + imageData.size();
    fileHeader.bfOffBits = sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader);

    // Fill info header information
    infoHeader.biSize = sizeof(BitmapInfoHeader);
    infoHeader.biWidth = m_width;
    infoHeader.biHeight = m_height;
    // Write file header and info header
    file.write(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader));
    file.write(reinterpret_cast<char*>(&infoHeader), sizeof(infoHeader));
    file.write(reinterpret_cast<const char*>(&imageData[0]), imageData.size());
    std::cout << "Bitmap image saved successfully." << std::endl;
    return true;
}

std::vector<unsigned char> BitmapConverterTo8bits::convert_to_8bit(const std::vector<RGBAColor> &image) {
    std::vector<unsigned char> indexed_image(m_width * m_height);
    for (uint32_t y = 0; y < m_height; ++y) {
        for (uint32_t x = 0; x < m_width; ++x) {
            const RGBAColor& color = image[y * m_width + x];
            double min_distance = std::numeric_limits<double>::max();
            uint8_t nearest_index = 0;
            for (size_t i = 0; i < m_palette.size(); ++i) {
                double dist = color_distance_squared(color, m_palette[i]);
                if (dist < min_distance) {
                    min_distance = dist;
                    nearest_index = i;
                }
            }
            indexed_image[y * m_width + x] = nearest_index;
        }
    }
    return indexed_image;
}

std::vector<RGBAColor> BitmapConverterTo8bits::convert_24bit_color(std::vector<unsigned char> &imageData)
{
    std::vector<RGBAColor> colors(m_width * m_height);
    for (int i = 0; i < m_width * m_height; ++i) {
        colors[i].r = imageData[i * 3];
        colors[i].g = imageData[i * 3 + 1];
        colors[i].b = imageData[i * 3 + 2];
    }
    return colors;
}

std::vector<RGBAColor> BitmapConverterTo8bits::convert_565_color(std::vector<unsigned char> &imageData)
{
    std::vector<RGBAColor> colors(m_width * m_height);
    for (int i = 0; i < m_width * m_height; i+=2) {
        int16_t rgb565 = (imageData[i] << 8) | imageData[i + 1];
        // Extract color channel values, using bitwise masks instead of multiplication operations.
        colors[i / 2].r = (rgb565 >> 11) & 0x1F;
        colors[i / 2].g = (rgb565 >> 5) & 0x3F;
        colors[i / 2].b = rgb565 & 0x1F;
        // Extend 5-bit color values to 8 bits.
        colors[i / 2].r = (colors[i / 2].r << 3) | (colors[i / 2].r >> 2);
        colors[i / 2].g = (colors[i / 2].g << 2) | (colors[i / 2].g >> 4);
        colors[i / 2].b = (colors[i / 2].b << 3) | (colors[i / 2].b >> 2);
    }
    return colors;
}

std::vector<RGBAColor> BitmapConverterTo8bits::convert_1555_color(std::vector<unsigned char> &imageData)
{
    std::vector<RGBAColor> colors(m_width * m_height);
    for (int i = 0; i < m_width * m_height; i+=2) {
        int16_t rgb1555 = (static_cast<int16_t>(imageData[i]) << 8) | imageData[i + 1];
        colors[i].r = (rgb1555 >> 10) & 0x1F;
        colors[i].g = (rgb1555 >> 5) & 0x1F;
        colors[i].b = rgb1555 & 0x1F;
        colors[i].a = (rgb1555 >> 15) ? 255 : 0;
    }
    return colors;
}

bitmapconverter.h

#ifndef BITMAPCONVERTER_H
#define BITMAPCONVERTER_H

#include <vector>
#include <unordered_map>
#include <string>
#include <iostream>

// Bitmap file header structure
#pragma pack(push, 1) // Ensure byte alignment

struct BitmapFileHeader {
    uint16_t bfType      = 0x4D42;
    uint32_t bfSize      = 0;
    uint16_t bfReserved1 = 0;
    uint16_t bfReserved2 = 0;
    uint32_t bfOffBits   = 0;

    void printInfo() const {
        std::cout << "Bitmap File Header:" << std::endl;
        std::cout << "  bfType: " << bfType << std::endl;
        std::cout << "  bfSize: " << bfSize << std::endl;
        std::cout << "  bfReserved1: " << bfReserved1 << std::endl;
        std::cout << "  bfReserved2: " << bfReserved2 << std::endl;
        std::cout << "  bfOffBits: " << bfOffBits << std::endl;
    }
};

// Bitmap information header structure

struct BitmapInfoHeader {
    uint32_t biSize          = 40;
    uint32_t biWidth         = 0;
    uint32_t biHeight        = 0;
    uint16_t biPlanes        = 1;
    uint16_t biBitCount      = 24;
    uint32_t biCompression   = 0;
    uint32_t biSizeImage     = 0;
    uint32_t biXPelsPerMeter = 0;
    uint32_t biYPelsPerMeter = 0;
    uint32_t biClrUsed       = 0;
    uint32_t biClrImportant  = 0;

    void printInfo() const {
        std::cout << "Bitmap Info Header:" << std::endl;
        std::cout << "  biSize: " << biSize << std::endl;
        std::cout << "  biWidth: " << biWidth << std::endl;
        std::cout << "  biHeight: " << biHeight << std::endl;
        std::cout << "  biPlanes: " << biPlanes << std::endl;
        std::cout << "  biBitCount: " << biBitCount << std::endl;
        std::cout << "  biCompression: " << biCompression << std::endl;
        std::cout << "  biSizeImage: " << biSizeImage << std::endl;
        std::cout << "  biXPelsPerMeter: " << biXPelsPerMeter << std::endl;
        std::cout << "  biYPelsPerMeter: " << biYPelsPerMeter << std::endl;
        std::cout << "  biClrUsed: " << biClrUsed << std::endl;
        std::cout << "  biClrImportant: " << biClrImportant << std::endl;
    }
};

#pragma pack(pop)

struct RGBAColor {
    unsigned char r = 0;
    unsigned char g = 0;
    unsigned char b = 0;
    unsigned char a = 255;
    bool operator==(const RGBAColor& other) const {
        return r == other.r && g == other.g && b == other.b && a == other.a;
    }
};

namespace std {
template<> struct hash<RGBAColor> {
    size_t operator()(const RGBAColor& color) const {
        return hash<int>()(color.r) ^ hash<int>()(color.g) ^ hash<int>()(color.b) ^ hash<int>()(color.a);
    }
};
}

class BitmapConverterTo8bits {
public:
    uint32_t m_width,m_height,m_channels;
    bool m_is_rgb1555 = false;
    std::vector<RGBAColor> m_palette;
    std::vector<unsigned char> m_indexed_image;
private:
    std::unordered_map<RGBAColor, size_t> count_colors(const std::vector<RGBAColor>& image);

    std::vector<RGBAColor> select_palette(const std::unordered_map<RGBAColor, size_t>& color_counts);

    double color_distance_squared(const RGBAColor& c1, const RGBAColor& c2);

    void convert_24bits_to_8bits(std::vector<unsigned char> &imageData);

    void convert_16bits_to_8bits(std::vector<unsigned char> &imageData);

    void printf_info(){
        std::cout <<"m_width:" << m_width<<std::endl;
        std::cout <<"m_height:"<< m_height<<std::endl;
        std::cout <<"m_channels:"<< m_channels<<std::endl;
        std::cout <<"m_is_rgb1555:"<< m_is_rgb1555<<std::endl;
    }

public:

    void convert_to_8bits(std::vector<unsigned char> &imageData);

    bool save_8bits_bmp(const std::string& filename);

    bool save_24bits_bmp(const std::string& filename,std::vector<unsigned char> &imageData);

    std::vector<unsigned char> load_bmp(const std::string &filename, bool is_rgb1555 = false);

    void swap_bmp_for_agg(std::vector<unsigned char> &imageData);
    void swap_agg_for_bmp(std::vector<unsigned char> &imageData);
private:
    std::vector<unsigned char> convert_to_8bit(const std::vector<RGBAColor>& image);
    std::vector<RGBAColor> convert_24bit_color(std::vector<unsigned char> &imageData);
    std::vector<RGBAColor> convert_565_color(std::vector<unsigned char> &imageData);
    std::vector<RGBAColor> convert_1555_color(std::vector<unsigned char> &imageData);
};

#endif // BITMAPCONVERTER_H

main.cpp

#include <agg_pixfmt_rgb.h>
#include <agg_pixfmt_rgb_packed.h>
#include <agg_renderer_base.h>
#include <agg_renderer_scanline.h>
#include <agg_rasterizer_scanline_aa.h>
#include <agg_scanline_p.h>
#include <agg_scanline_u.h>
#include <agg_conv_stroke.h>
#include <agg_ellipse.h>
#include <agg_rendering_buffer.h>

#include "bitmapconverter.h"

int main() {
    BitmapConverterTo8bits converter;

    std::vector<unsigned char> imageData = converter.load_bmp("input_24bit.bmp") ;
    // Load 24-bit color image data

    if (converter.load_bmp("input_24bit.bmp").empty()) {
        return -1;
    }

    converter.swap_bmp_for_agg(imageData);
    // 创建 AGG 渲染器和画布
    using renderer_base = agg::renderer_base<agg::pixfmt_rgb24>;
    agg::rendering_buffer rbuf;
    rbuf.attach(imageData.data(), converter.m_width, converter.m_height,  converter.m_width * converter.m_channels);
    agg::pixfmt_rgb24 pixf(rbuf);
    renderer_base rb(pixf);

    // 设置绘制参数
    agg::rasterizer_scanline_aa<> ras;
    agg::scanline_p8 sl;
    agg::renderer_scanline_aa_solid<renderer_base> ren(rb);
    ras.auto_close(true);

    // 绘制椭圆
    agg::ellipse ellipse(converter.m_width / 2.0, converter.m_height / 2.0, converter.m_width / 3.0, converter.m_height / 4.0);
    agg::conv_stroke<agg::ellipse> stroke(ellipse);
    stroke.width(2.0);
    ras.add_path(stroke);
    ren.color(agg::rgba8(255, 0, 0));  // 设置椭圆颜色为红色
    agg::render_scanlines(ras, sl, ren);

    converter.swap_agg_for_bmp(imageData);
    converter.convert_to_8bits(imageData); //=====>

    // Save 8-bit color image as BMP file
    if (!converter.save_8bits_bmp("agg_output_image8bits.bmp")) {
        return -1;
    }
    // Save 24-bit color image as BMP file
    if (!converter.save_24bits_bmp("agg_output_image24bits.bmp",imageData)) {
        return -1;
    }

    return 0;
}

标签:24,std,imageData,color,colors,BMP,vector,图像
From: https://www.cnblogs.com/yan1345/p/18117758/BitmapConverterTo8bits

相关文章

  • P10245 Swimming Pool题解
    P10245SwimmingPool题意给你四条边\(abcd\),求这四条边是否可以组成梯形。思路这显然是一道简单的普通数学题。判断是否能构成梯形只需看四条边是否能满足,上底减下底的绝对值小于两腰之和且大于两腰之差。证明过程如图,\(AB=a\),\(BC=b\),\(CD=c\),\(AD=d\)。过点\(D\)......
  • P10244 String Minimization 题解
    P10244StringMinimization题意给你四个长度为\(n\)的字符串,分别是\(abcd\)。你可以选择一个\(i\)然后交换\(a[i]\)和\(c[i]\),并交换\(b[i]\)和\(d[i]\)。求在\(a\)的字典序尽量小的前提下,\(b\)字典序最小是什么。思路本题并不难。只需要在\(a[i]>c[i]\)......
  • 24.4.5C语言学习笔记|访问空间地址【之前一直迷惑的问题】
    1、如何访问一个空间?有名访问无名访问指针的大小跟你的编译器是x64系统还是x86系统有关,%p,打印地址(十六进制)C语言如何用地址来描述一个空间?C语言如何识别变量的属性?定位,先右看,再左看数组:有多少个?每一个怎么存的?高级变形第二个:定位---a5【一个指针,地址,门牌号】怎么访......
  • JetBrains IDE 2024.1 (macOS, Linux, Windows) 发版 - 开发者工具
    JetBrainsIDE2024.1(macOS,Linux,Windows)-开发者工具CLion,DataGrip,DataSpell,Fleet,GoLand,IntelliJIDEA,PhpStorm,PyCharm,Rider,RubyMine,WebStorm请访问原文链接:JetBrainsIDE2024.1(macOS,Linux,Windows)-开发者工具,查看最新版。原创作品,转载请......
  • 2024年PhotoVogue全球摄影公开征稿启事(截止2024年4月20日)免参赛费+总奖金10000美元
    2024年PhotoVogue全球摄影公开征稿启事赛事亮点:亮点一:免参赛费亮点二:作品入选可以参加意大利PhotoVogue摄影节亮点三:两位获奖者将分别获得5000美元创作基金 一、赛事介绍2024年PhotoVogue全球摄影公开征集活动邀请世界各地的艺术家提交关于人类与动物和大自然关系的作品......
  • lambda演算入门 (软件工程与计算 理论部分2)20240406
    此文章来源于网络,是学习lambda演算过程的总结与复习,着重于探讨“为什么(Why)”与“怎么做(How)”,也希望能对看到它的人学习了解这个形式系统有些微帮助。由于之前看了不少wiki、tutorial、introduction之流,绝大多数读过之后仅知其然而不知其所以然,我不知道为什么它们都不解释为什......
  • P2495 [SDOI2011] 消耗战
    P2495[SDOI2011]消耗战虚树优化dp模板题考虑\(m=1\)。只需要简单的树形dp,设\(f_i\)表示\(i\)子树中的关键点都到不了\(i\)点的最小代价。转移枚举子节点\(v\),有:若\(v\)点为关键点,\(f_u=f_u+w(u,v)\)。否则,\(f_u=f_u+\min(f_v,w(u,v))\)。如果每次询问都跑一遍......
  • 24.4.6 题解
    4.6模拟赛T1困难的图论题意:找出所有在且仅在1个简单环中的边,输出编号的异或和。一个错误的想法:找边双连通分量,看边数是否等于点数反例:正解是点双所以我在想,为什么是点双,不是边双呢?什么时候找点双,什么时候找边双呢?T2中等的字符串已知\(m\)个关键词\(s_i\),每出现......
  • CCF-CSP认证202403个人总结以及部分代码
    第一次参加,总分340,这个成绩个人觉得比较满意了,毕竟考前一直在划水,也很久没写算法题了。写到第四题,觉得还剩一个小时肯定写不完就又开始划水,暴力模拟完了就开始翻网页抄自己的提交记录,无所事事,想提前交卷。考试结束在网上一搜,第四题好像不是很难,瞬间觉得没写到最后亏了,开始后悔。......
  • 2024.4.6 - 4.12
    SatJOI2023Final宣传2\(n\)个人,每个人有住所位置\(X_i\)与影响力\(E_i\),一个人\(i\)拿到书后会号召另一个人\(j\)买书仅当\(|X-i-X_j|\leqE_i-E_j\),你最少送多少个人书才能使得所有人都会有书(送的或者被号召买书)。\(n\leq5\times10^5\)。拆一下绝对值,得:\[......