snippetcppMinor
Convert an image into characters and colors for the windows console
Viewed 0 times
imagetheconvertintocolorscharactersforwindowsandconsole
Problem
I've written a function to convert an image into characters and colors for the windows console. At the moment the calculation takes about 13 seconds with a 700x700 pixel image but that time is undesirable especially when I plan on making the function more complex in order to account for character shapes.
What are some methods to speed up heavy calculations and loops like below in C++? I've been recommended multiple threads, SIMD, and inline assembly but how would I go about improving a function like below with those methods?
This is the current code I'm using.
```
unsigned char characterValues[256] = { 0 };
// This operation can be done ahead of time when the program is started up
{
ResourceInputStream in = ResourceInputStream();
// This image is the font for the console. The background color is black while the foreground color is white
in.open(BMP_FONT, 2); // 2 is for RT_BITMAP, BMP_FONT is a resource
if (in.isOpen()) {
auto bmp = readBitmap(&in, true);
in.close();
for (int x = 0; x size.x; x++) {
for (int y = 0; y size.y; y++) {
int charIndex = (x / 8) + (y / 12) * 16;
if (bmp->pixels[x][y].r == 255)
characterValues[charIndex]++;
}
}
}
}
// This operation is for asciifying the image
{
FileInputStream in = FileInputStream();
in.open(R"(image-path.bmp)");
if (in.isOpen()) {
auto bmp = readBitmap(&in, false);
in.close();
// The size of the image in characters
Point2I imageSize = (Point2I)GMath::ceil((Point2F)bmp->size / Point2F(8.0f, 12.0f));
int totalImageSize = imageSize.x * imageSize.y;
auto palette = / get palette of 16 colors here /
// Iterate through each (character area)
for (int imgx = 0; imgx size) {
r += bmp->pixels[p.x][p.y].r;
g += bmp->pixels[p.x][p.y].g;
b += bm
What are some methods to speed up heavy calculations and loops like below in C++? I've been recommended multiple threads, SIMD, and inline assembly but how would I go about improving a function like below with those methods?
This is the current code I'm using.
```
unsigned char characterValues[256] = { 0 };
// This operation can be done ahead of time when the program is started up
{
ResourceInputStream in = ResourceInputStream();
// This image is the font for the console. The background color is black while the foreground color is white
in.open(BMP_FONT, 2); // 2 is for RT_BITMAP, BMP_FONT is a resource
if (in.isOpen()) {
auto bmp = readBitmap(&in, true);
in.close();
for (int x = 0; x size.x; x++) {
for (int y = 0; y size.y; y++) {
int charIndex = (x / 8) + (y / 12) * 16;
if (bmp->pixels[x][y].r == 255)
characterValues[charIndex]++;
}
}
}
}
// This operation is for asciifying the image
{
FileInputStream in = FileInputStream();
in.open(R"(image-path.bmp)");
if (in.isOpen()) {
auto bmp = readBitmap(&in, false);
in.close();
// The size of the image in characters
Point2I imageSize = (Point2I)GMath::ceil((Point2F)bmp->size / Point2F(8.0f, 12.0f));
int totalImageSize = imageSize.x * imageSize.y;
auto palette = / get palette of 16 colors here /
// Iterate through each (character area)
for (int imgx = 0; imgx size) {
r += bmp->pixels[p.x][p.y].r;
g += bmp->pixels[p.x][p.y].g;
b += bm
Solution
Here's pretty much my end result for an algorithm. It takes about 20 seconds for a 1024x1280 with (4x6) pixel precision. I switched from primarily using Euclidean color difference to LAB color difference which also happens to run faster since most of the calculations can be done ahead of time.
For those of you who are curious to see what the results look like:
Here's the final output for the algorithm:
```
#define SPLIT_X split.x
#define SPLIT_Y split.y
#define SPLIT_MAG (SPLIT_X * SPLIT_Y)
#define TOTAL_CHARACTER_VALUE (96 / SPLIT_MAG)
#define NUM_CHARACTERS 256
#define NUM_COLORS 256
AsciiAnimationSPtr Asciifier::asciifyImagePrecision(const std::string& path, Point2I split, ColorMetrics metric, ProgressDialogSPtr progressDialog) {
#pragma region Declarations
int totalTime = clock();
BitmapData2* bmp = nullptr;
int** characterCounts = nullptr;
ColorI** characterValues = nullptr;
ColorI** characterFValues = nullptr;
ColorI** characterBValues = nullptr;
ColorF** characterLABs = nullptr;
ColorI* imageValues = nullptr;
ColorF* imageLABs = nullptr;
init2DArray(characterCounts, int, NUM_CHARACTERS, SPLIT_MAG);
if (metric == ColorMetrics::Euclidean) {
init2DArray( characterValues, ColorI, NUM_CHARACTERS * NUM_COLORS, SPLIT_MAG);
init2DArray(characterFValues, ColorI, NUM_CHARACTERS * NUM_COLORS, SPLIT_MAG);
init2DArray(characterBValues, ColorI, NUM_CHARACTERS * NUM_COLORS, SPLIT_MAG);
init1DArray(imageValues, ColorI, SPLIT_MAG);
}
else {
init2DArray(characterLABs, ColorF, NUM_CHARACTERS * NUM_COLORS, SPLIT_MAG);
init1DArray(imageLABs, ColorF, SPLIT_MAG);
}
#pragma endregion
#pragma region Init A
progressDialog->setActionMessage("Reading console font...");
updateProgress();
CoInitialize(nullptr);
IStream* stream = CreateStreamOnResource(PNG_FONT, "PNG");
IWICBitmapSource* bitmap = LoadBitmapFromStream(stream);
bmp = readBitmap(bitmap);
stream->Release();
#pragma endregion
#pragma region Read Character Values
progressDialog->setActionMessage("Reading image...");
if (progressDialog->update()) {
return nullptr;
}
for (int x = 0; x size.x; x++) {
for (int y = 0; y size.y; y++) {
if (bmp->pixels[xyToIndex(x, y, bmp->size.x)].r == 255) {
int charIndex = (x / 8) + (y / 12) * 16;
int subIndex = (x % 8 SPLIT_X / 8) + (y % 12 SPLIT_Y / 12) * SPLIT_X;
characterCounts[charIndex][subIndex]++;
}
}
}
delete bmp;
bmp = nullptr;
#pragma endregion
#pragma region Init B
stream = CreateStreamOnFile(path);
bitmap = LoadBitmapFromStream(stream);
bmp = readBitmap(bitmap);
stream->Release();
Point2I imageSize = (Point2I)GMath::ceil((Point2F)bmp->size / Point2F(8.0f, 12.0f));
int totalImageSize = imageSize.x * imageSize.y;
auto image = std::make_shared(imageSize, ImageFormats::Basic, Pixel());
image->resize(imageSize);
auto graphics = image->createGraphics();
ColorI palette[16] = {
ColorI(0, 0, 0),
ColorI(0, 0, 128),
ColorI(0, 128, 0),
ColorI(0, 128, 128),
ColorI(128, 0, 0),
ColorI(128, 0, 128),
ColorI(128, 128, 0),
ColorI(192, 192, 192),
ColorI(128, 128, 128),
ColorI(0, 0, 255),
ColorI(0, 255, 0),
ColorI(0, 255, 255),
ColorI(255, 0, 0),
ColorI(255, 0, 255),
ColorI(255, 255, 0),
ColorI(255, 255, 255)
};
#pragma endregion
#pragma region Init C
progressDialog->setActionMessage("Calculating character LABs...");
updateProgress();
for (int col = 1; col = bmp->size.x)
continue;
for (int py = 0; py = bmp->size.y)
continue;
imageValue += bmp->pixels[xyToIndex(p_x, p_y, bmp->size.x)];
totalRead++;
}
}
if (totalRead > 0) {
if (metric == ColorMetrics::Euclidean)
imageValues[s] = imageValue / totalRead;
else
imageLABs[s] = RGB2LAB(imageValue / totalRead);
}
else {
if (metric == ColorMetrics::Euclidean)
imageValues[s] = ColorI();
else
imageLABs[s] = ColorF();
}
}
#pragma endregion
Pixel closestPixel = Pixel();
float closestScore = std::numeric_limits().max();
for (int col = 1;
For those of you who are curious to see what the results look like:
- LAB Delta E (4x6) precision
- LAB Delta E (8x12), (4x6), (2x3), & (1x1) precision (It's very picky about only working with pixels in its range.
- Euclidean (4x6) precision (This one actually looks worse with LAB)
Here's the final output for the algorithm:
```
#define SPLIT_X split.x
#define SPLIT_Y split.y
#define SPLIT_MAG (SPLIT_X * SPLIT_Y)
#define TOTAL_CHARACTER_VALUE (96 / SPLIT_MAG)
#define NUM_CHARACTERS 256
#define NUM_COLORS 256
AsciiAnimationSPtr Asciifier::asciifyImagePrecision(const std::string& path, Point2I split, ColorMetrics metric, ProgressDialogSPtr progressDialog) {
#pragma region Declarations
int totalTime = clock();
BitmapData2* bmp = nullptr;
int** characterCounts = nullptr;
ColorI** characterValues = nullptr;
ColorI** characterFValues = nullptr;
ColorI** characterBValues = nullptr;
ColorF** characterLABs = nullptr;
ColorI* imageValues = nullptr;
ColorF* imageLABs = nullptr;
init2DArray(characterCounts, int, NUM_CHARACTERS, SPLIT_MAG);
if (metric == ColorMetrics::Euclidean) {
init2DArray( characterValues, ColorI, NUM_CHARACTERS * NUM_COLORS, SPLIT_MAG);
init2DArray(characterFValues, ColorI, NUM_CHARACTERS * NUM_COLORS, SPLIT_MAG);
init2DArray(characterBValues, ColorI, NUM_CHARACTERS * NUM_COLORS, SPLIT_MAG);
init1DArray(imageValues, ColorI, SPLIT_MAG);
}
else {
init2DArray(characterLABs, ColorF, NUM_CHARACTERS * NUM_COLORS, SPLIT_MAG);
init1DArray(imageLABs, ColorF, SPLIT_MAG);
}
#pragma endregion
#pragma region Init A
progressDialog->setActionMessage("Reading console font...");
updateProgress();
CoInitialize(nullptr);
IStream* stream = CreateStreamOnResource(PNG_FONT, "PNG");
IWICBitmapSource* bitmap = LoadBitmapFromStream(stream);
bmp = readBitmap(bitmap);
stream->Release();
#pragma endregion
#pragma region Read Character Values
progressDialog->setActionMessage("Reading image...");
if (progressDialog->update()) {
return nullptr;
}
for (int x = 0; x size.x; x++) {
for (int y = 0; y size.y; y++) {
if (bmp->pixels[xyToIndex(x, y, bmp->size.x)].r == 255) {
int charIndex = (x / 8) + (y / 12) * 16;
int subIndex = (x % 8 SPLIT_X / 8) + (y % 12 SPLIT_Y / 12) * SPLIT_X;
characterCounts[charIndex][subIndex]++;
}
}
}
delete bmp;
bmp = nullptr;
#pragma endregion
#pragma region Init B
stream = CreateStreamOnFile(path);
bitmap = LoadBitmapFromStream(stream);
bmp = readBitmap(bitmap);
stream->Release();
Point2I imageSize = (Point2I)GMath::ceil((Point2F)bmp->size / Point2F(8.0f, 12.0f));
int totalImageSize = imageSize.x * imageSize.y;
auto image = std::make_shared(imageSize, ImageFormats::Basic, Pixel());
image->resize(imageSize);
auto graphics = image->createGraphics();
ColorI palette[16] = {
ColorI(0, 0, 0),
ColorI(0, 0, 128),
ColorI(0, 128, 0),
ColorI(0, 128, 128),
ColorI(128, 0, 0),
ColorI(128, 0, 128),
ColorI(128, 128, 0),
ColorI(192, 192, 192),
ColorI(128, 128, 128),
ColorI(0, 0, 255),
ColorI(0, 255, 0),
ColorI(0, 255, 255),
ColorI(255, 0, 0),
ColorI(255, 0, 255),
ColorI(255, 255, 0),
ColorI(255, 255, 255)
};
#pragma endregion
#pragma region Init C
progressDialog->setActionMessage("Calculating character LABs...");
updateProgress();
for (int col = 1; col = bmp->size.x)
continue;
for (int py = 0; py = bmp->size.y)
continue;
imageValue += bmp->pixels[xyToIndex(p_x, p_y, bmp->size.x)];
totalRead++;
}
}
if (totalRead > 0) {
if (metric == ColorMetrics::Euclidean)
imageValues[s] = imageValue / totalRead;
else
imageLABs[s] = RGB2LAB(imageValue / totalRead);
}
else {
if (metric == ColorMetrics::Euclidean)
imageValues[s] = ColorI();
else
imageLABs[s] = ColorF();
}
}
#pragma endregion
Pixel closestPixel = Pixel();
float closestScore = std::numeric_limits().max();
for (int col = 1;
Code Snippets
#define SPLIT_X split.x
#define SPLIT_Y split.y
#define SPLIT_MAG (SPLIT_X * SPLIT_Y)
#define TOTAL_CHARACTER_VALUE (96 / SPLIT_MAG)
#define NUM_CHARACTERS 256
#define NUM_COLORS 256
AsciiAnimationSPtr Asciifier::asciifyImagePrecision(const std::string& path, Point2I split, ColorMetrics metric, ProgressDialogSPtr progressDialog) {
#pragma region Declarations
int totalTime = clock();
BitmapData2* bmp = nullptr;
int** characterCounts = nullptr;
ColorI** characterValues = nullptr;
ColorI** characterFValues = nullptr;
ColorI** characterBValues = nullptr;
ColorF** characterLABs = nullptr;
ColorI* imageValues = nullptr;
ColorF* imageLABs = nullptr;
init2DArray(characterCounts, int, NUM_CHARACTERS, SPLIT_MAG);
if (metric == ColorMetrics::Euclidean) {
init2DArray( characterValues, ColorI, NUM_CHARACTERS * NUM_COLORS, SPLIT_MAG);
init2DArray(characterFValues, ColorI, NUM_CHARACTERS * NUM_COLORS, SPLIT_MAG);
init2DArray(characterBValues, ColorI, NUM_CHARACTERS * NUM_COLORS, SPLIT_MAG);
init1DArray(imageValues, ColorI, SPLIT_MAG);
}
else {
init2DArray(characterLABs, ColorF, NUM_CHARACTERS * NUM_COLORS, SPLIT_MAG);
init1DArray(imageLABs, ColorF, SPLIT_MAG);
}
#pragma endregion
#pragma region Init A
progressDialog->setActionMessage("Reading console font...");
updateProgress();
CoInitialize(nullptr);
IStream* stream = CreateStreamOnResource(PNG_FONT, "PNG");
IWICBitmapSource* bitmap = LoadBitmapFromStream(stream);
bmp = readBitmap(bitmap);
stream->Release();
#pragma endregion
#pragma region Read Character Values
progressDialog->setActionMessage("Reading image...");
if (progressDialog->update()) {
return nullptr;
}
for (int x = 0; x < bmp->size.x; x++) {
for (int y = 0; y < bmp->size.y; y++) {
if (bmp->pixels[xyToIndex(x, y, bmp->size.x)].r == 255) {
int charIndex = (x / 8) + (y / 12) * 16;
int subIndex = (x % 8 * SPLIT_X / 8) + (y % 12 * SPLIT_Y / 12) * SPLIT_X;
characterCounts[charIndex][subIndex]++;
}
}
}
delete bmp;
bmp = nullptr;
#pragma endregion
#pragma region Init B
stream = CreateStreamOnFile(path);
bitmap = LoadBitmapFromStream(stream);
bmp = readBitmap(bitmap);
stream->Release();
Point2I imageSize = (Point2I)GMath::ceil((Point2F)bmp->size / Point2F(8.0f, 12.0f));
int totalImageSize = imageSize.x * imageSize.y;
auto image = std::make_shared<AsciiAnimation>(imageSize, ImageFormats::Basic, Pixel());
image->resize(imageSize);
auto graphics = image->createGraphics();
ColorI palette[16] = {
ColorI(0, 0, 0),
ColorI(0, 0, 128),
ColorI(0, 128, 0),
ColorI(0, 128, 128),
ColorI(128, 0, 0),
ColorI(128, 0, 128),
ColorI(128, 128, 0),
ColorI(192, 192, 192),
Context
StackExchange Code Review Q#158574, answer score: 5
Revisions (0)
No revisions yet.