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 < bmp->size.x; x++) {
for (int y = 0; y < bmp->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 < imageSize.x; imgx++) {
for (int imgy = 0; imgy < imageSize.y; imgy++) {
// Read image color value
int r = 0, g = 0, b = 0;
int totalRead = 0;
// Read each pixel inside the bounds of a single character
// 8x12 is the size of a character
for (int px = 0; px < 8; px++) {
for (int py = 0; py < 12; py++) {
Point2I p = Point2I(imgx * 8 + px, imgy * 12 + py);
if (p < bmp->size) {
r += bmp->pixels[p.x][p.y].r;
g += bmp->pixels[p.x][p.y].g;
b += bmp->pixels[p.x][p.y].b;
totalRead++;
}
}
}
Color imageValue = Color(r / totalRead, g / totalRead, b / totalRead);
// A combo of a character and foreground/background color
Pixel closestPixel = Pixel();
float closestScore = std::numeric_limits<float>().max();
for (int col = 1; col < 255; col++) {
unsigned char f = getFColor(col);
unsigned char b = getBColor(col);
for (int ch = 1; ch < 255; ch++) {
// Calculate values
Color value = Color(
(palette[f].r * characterValues[ch] + palette[b].r * (TOTAL_CHARACTER_VALUE - characterValues[ch])) / TOTAL_CHARACTER_VALUE,
(palette[f].g * characterValues[ch] + palette[b].g * (TOTAL_CHARACTER_VALUE - characterValues[ch])) / TOTAL_CHARACTER_VALUE,
(palette[f].b * characterValues[ch] + palette[b].b * (TOTAL_CHARACTER_VALUE - characterValues[ch])) / TOTAL_CHARACTER_VALUE
);
Color fvalue = Color(
(palette[f].r * characterValues[ch]) / TOTAL_CHARACTER_VALUE,
(palette[f].g * characterValues[ch]) / TOTAL_CHARACTER_VALUE,
(palette[f].b * characterValues[ch]) / TOTAL_CHARACTER_VALUE
);
Color bvalue = Color(
(palette[b].r * (TOTAL_CHARACTER_VALUE - characterValues[ch])) / TOTAL_CHARACTER_VALUE,
(palette[b].g * (TOTAL_CHARACTER_VALUE - characterValues[ch])) / TOTAL_CHARACTER_VALUE,
(palette[b].b * (TOTAL_CHARACTER_VALUE - characterValues[ch])) / TOTAL_CHARACTER_VALUE
);
// Add up score here
float score =
(float)((int)value.r - (int)imageValue.r) * (float)((int)value.r - (int)imageValue.r) +
(float)((int)value.g - (int)imageValue.g) * (float)((int)value.g - (int)imageValue.g) +
(float)((int)value.b - (int)imageValue.b) * (float)((int)value.b - (int)imageValue.b) +
(float)((int)fvalue.r - (int)imageValue.r) * (float)((int)fvalue.r - (int)imageValue.r) +
(float)((int)fvalue.g - (int)imageValue.g) * (float)((int)fvalue.g - (int)imageValue.g) +
(float)((int)fvalue.b - (int)imageValue.b) * (float)((int)fvalue.b - (int)imageValue.b) +
(float)((int)bvalue.r - (int)imageValue.r) * (float)((int)bvalue.r - (int)imageValue.r) +
(float)((int)bvalue.g - (int)imageValue.g) * (float)((int)bvalue.g - (int)imageValue.g) +
(float)((int)bvalue.b - (int)imageValue.b) * (float)((int)bvalue.b - (int)imageValue.b);
// More
if (score < closestScore) {
closestPixel = Pixel((unsigned char)ch, (unsigned char)col);
closestScore = score;
}
}
}
// Set the character/color combo here
}
}
}
}
As a bonus, this is the result of my calculation. There's definitely room for improvement with the scoring but at least you can see the shape and colors.
float
? It seems to me that your big calculation would work just fine if you left the score as anint
. Also, it would be even cooler if your program compared each pixel to each pixel (instead of block by block), because then you might even see details within each character show up (like lines and curves). Of course that would take 96x more time but I'd be interested in the result. \$\endgroup\$