Created
May 25, 2016 17:43
-
-
Save sysint64/f95f1c016f2aa091820919afd590784a to your computer and use it in GitHub Desktop.
bmp_palette.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <fstream> | |
#include <vector> | |
#include <string> | |
#include <array> | |
#include <math.h> | |
// Палитры цветов | |
#include "palettes.h" | |
class Bitmap { | |
public: | |
uint32_t width, height; | |
uint16_t bitsPerPixel; | |
std::vector<uint8_t> pixels; | |
std::vector<uint8_t> fileInfo; | |
Bitmap() {} | |
Bitmap (const std::string &fileName) { | |
loadFromFile(fileName); | |
} | |
void loadFromFile (const std::string &fileName); | |
void saveToFile (const std::string &fileName); | |
void changePalette (uint8_t palette[][3], size_t size); | |
}; | |
void Bitmap::loadFromFile (const std::string &fileName) { | |
std::fstream hFile (fileName, std::ios::in | std::ios::binary); | |
if (!hFile.is_open()) | |
throw std::invalid_argument ("Error: File Not Found."); | |
hFile.seekg (0, std::ios::end); | |
size_t length = hFile.tellg(); | |
hFile.seekg (0, std::ios::beg); | |
fileInfo.resize (length); | |
hFile.read (reinterpret_cast<char*>(fileInfo.data()), 54); | |
if (fileInfo[0] != 'B' && fileInfo[1] != 'M') { | |
hFile.close(); | |
throw std::invalid_argument("Error: Invalid File Format. Bitmap Required."); | |
} | |
bitsPerPixel = fileInfo[28]; | |
width = fileInfo[18] + (fileInfo[19] << 8); | |
height = fileInfo[22] + (fileInfo[23] << 8); | |
uint32_t pixelsOffset = fileInfo[10] + (fileInfo[11] << 8); | |
uint32_t size = ((width * bitsPerPixel + 31) / 32) * 4 * height; | |
pixels.resize (size); | |
hFile.seekg (pixelsOffset, std::ios::beg); | |
hFile.read (reinterpret_cast<char*>(pixels.data()), size); | |
hFile.close (); | |
} | |
void Bitmap::saveToFile (const std::string &fileName) { | |
std::fstream hFile (fileName, std::ios::out | std::ios::binary); | |
fileInfo[28] = bitsPerPixel; | |
uint32_t pixelsOffset = fileInfo[10] + (fileInfo[11] << 8); | |
hFile.write (reinterpret_cast<char*>(fileInfo.data()), pixelsOffset); | |
hFile.write (reinterpret_cast<char*>(pixels .data()), pixels .size()); | |
hFile.close (); | |
} | |
std::array<double, 3> RGBtoXYZ (uint8_t* color) { | |
double R = static_cast<double>(color[0]) / 255.0; | |
double G = static_cast<double>(color[1]) / 255.0; | |
double B = static_cast<double>(color[2]) / 255.0; | |
if (R > 0.04045 ) R = pow((R + 0.055) / 1.055, 2.4); else R = R / 12.92; | |
if (G > 0.04045 ) G = pow((G + 0.055) / 1.055, 2.4); else G = G / 12.92; | |
if (B > 0.04045 ) B = pow((B + 0.055) / 1.055, 2.4); else B = B / 12.92; | |
R *= 100; G *= 100; B *= 100; | |
double X = R * 0.4124 + G * 0.3576 + B * 0.1805; | |
double Y = R * 0.2126 + G * 0.7152 + B * 0.0722; | |
double Z = R * 0.0193 + G * 0.1192 + B * 0.9505; | |
return { X, Y, Z }; | |
} | |
std::array<double, 3> XYZtoLab (const std::array<double, 3> &color) { | |
double X = color[0] / 95.047; | |
double Y = color[1] / 100.000; | |
double Z = color[2] / 108.883; | |
if (X > 0.008856) X = pow(X, 1.0/3.0); else X = (7.787 * X) + (16.0 / 116.0); | |
if (Y > 0.008856) Y = pow(Y, 1.0/3.0); else Y = (7.787 * Y) + (16.0 / 116.0); | |
if (Z > 0.008856) Z = pow(Z, 1.0/3.0); else Z = (7.787 * Z) + (16.0 / 116.0); | |
double L = (116.0 * Y) - 16.0; | |
double a = 500.0 * (X - Y); | |
double b = 200.0 * (Y - Z); | |
return { L, a, b }; | |
} | |
inline std::array<double, 3> RGBtoLab (uint8_t* color) { | |
auto XYZ = RGBtoXYZ (color); | |
return XYZtoLab (XYZ); | |
} | |
inline double sqr (double x) { return x*x; } | |
// Использовал саму простую формулу CIE76 | |
// https://en.wikipedia.org/wiki/Color_difference | |
inline double deltaE (std::array<double, 3> c1, std::array<double, 3> c2) { | |
const int L = 0; const int a = 1; const int b = 2; | |
return sqrt(sqr(c2[L]-c1[L]) + sqr(c2[a]-c1[a]) + sqr(c2[b]-c1[b])); | |
} | |
uint8_t getColor (uint8_t* color, uint8_t palette[][3], size_t size) { | |
auto LabTarget = RGBtoLab (color); | |
double minDeltaE = 999.0; | |
int target = 0; | |
for (int i = 0; i < size; ++i) { | |
auto LabPalette = RGBtoLab(&palette[i][0]); | |
auto cpDeltaE = deltaE (LabTarget, LabPalette); | |
if (minDeltaE > cpDeltaE) { | |
minDeltaE = cpDeltaE; | |
target = i; | |
} | |
} | |
return target; | |
} | |
void Bitmap::changePalette (uint8_t palette[][3], size_t size) { | |
std::vector<uint8_t> newPixels; | |
uint8_t byte = 0; | |
uint8_t c = 0; | |
uint8_t c1 = 0; | |
uint8_t c2 = 0; | |
for (int i = 0; i < pixels.size(); i += 3) { | |
int paletteIdx = getColor (&pixels[i], palette, size); | |
newPixels.push_back (palette[paletteIdx][0]); | |
newPixels.push_back (palette[paletteIdx][1]); | |
newPixels.push_back (palette[paletteIdx][2]); | |
} | |
std::cout << std::endl; | |
pixels = newPixels; | |
} | |
int main() { | |
Bitmap bmp ("turtles.bmp"); | |
bmp.changePalette (RGB4_Palette, 16); // 4 битная палтира | |
// bmp.changePalette (RGB8_Palette, 256); // 8 битная палитра | |
bmp.saveToFile ("test.bmp"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment