Skip to content

Commit b0230d0

Browse files
authored
Merge pull request #7 from ToyB-Chan/master
Increased cout buffer size and reworked algorithm for 24bit color drawing which is now about twice as fast
2 parents b53a783 + 87d8465 commit b0230d0

File tree

3 files changed

+237
-94
lines changed

3 files changed

+237
-94
lines changed

cmdplaypp/src/Asciifier.cpp

+202-90
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#include "ColorConverter.hpp"
33
#include <iostream>
44
#include <algorithm>
5+
#include <sstream>
6+
#include <unordered_map>
57

68
cmdplay::Asciifier::Asciifier(const std::string& brightnessLevels, int frameWidth, int frameHeight,
79
bool useColors, bool useColorDithering, bool useTextDithering, bool useAccurateColors, bool useAccurateColorsFullPixel) :
@@ -32,6 +34,15 @@ cmdplay::Asciifier::Asciifier(const std::string& brightnessLevels, int frameWidt
3234

3335
m_frameWidthWithStride = m_frameWidth * m_pixelStride;
3436
m_targetFramebufferSize = (m_frameWidthWithStride + 1) * m_frameHeight;
37+
38+
m_frameBuffer = (Pixel*)malloc(m_frameWidth * m_frameHeight * sizeof(Pixel));
39+
m_backBuffer = (Pixel*)malloc(m_frameWidth * m_frameHeight * sizeof(Pixel));
40+
}
41+
42+
cmdplay::Asciifier::~Asciifier()
43+
{
44+
free(m_frameBuffer);
45+
free(m_backBuffer);
3546
}
3647

3748
inline int16_t cmdplay::Asciifier::MapByteToArray(int16_t value)
@@ -129,9 +140,57 @@ inline std::string cmdplay::Asciifier::ByteAsPaddedString(uint8_t i)
129140
return out;
130141
}
131142

132-
inline bool cmdplay::Asciifier::ColorComponentNearlyEquals(uint8_t value, uint8_t other)
143+
inline bool cmdplay::Asciifier::ColorComponentNearlyEquals(uint8_t value, uint8_t other, uint8_t tolerance)
133144
{
134-
return std::abs(value - other) <= COLOR_BATCHING_TOLERANCE;
145+
return std::abs(value - other) <= tolerance;
146+
}
147+
148+
float cmdplay::Asciifier::CalculateFrameBufferNoisiness()
149+
{
150+
std::unordered_map<uint32_t, uint32_t> colorToOccurences;
151+
for (int i = 0; i < m_frameWidth * m_frameHeight; i++)
152+
{
153+
const uint8_t colorBatchedR = (m_frameBuffer[i].r / COLOR_BATCHING_TOLERANCE) * COLOR_BATCHING_TOLERANCE;
154+
const uint8_t colorBatchedG = (m_frameBuffer[i].g / COLOR_BATCHING_TOLERANCE) * COLOR_BATCHING_TOLERANCE;
155+
const uint8_t colorBatchedB = (m_frameBuffer[i].b / COLOR_BATCHING_TOLERANCE) * COLOR_BATCHING_TOLERANCE;
156+
uint32_t colorInt = colorBatchedR << 24 + colorBatchedG << 16 + colorBatchedB << 8;
157+
colorToOccurences[colorInt]++;
158+
}
159+
160+
double entropy = 0.f;
161+
for (auto& kvp : colorToOccurences)
162+
{
163+
double p = (double)(kvp.second) / (m_frameWidth * m_frameHeight);
164+
if (p > 0)
165+
{
166+
entropy -= p * log2(p);
167+
}
168+
}
169+
170+
const double linearAlpha = entropy / log2(2 ^ 24); // divide through the maximum amount of colors;
171+
const double expoAlpha = 1 - pow(1 - linearAlpha, 1.5f);
172+
return expoAlpha;
173+
}
174+
175+
float cmdplay::Asciifier::CalulateFrameBufferToBackBufferDifference()
176+
{
177+
double differenceR = 0.f;
178+
double differenceG = 0.f;
179+
double differenceB = 0.f;
180+
for (int i = 0; i < m_frameWidth * m_frameHeight; i++)
181+
{
182+
differenceR += std::abs(m_frameBuffer[i].r - m_backBuffer[i].r);
183+
differenceG += std::abs(m_frameBuffer[i].g - m_backBuffer[i].g);
184+
differenceB += std::abs(m_frameBuffer[i].b - m_backBuffer[i].b);
185+
}
186+
187+
differenceR /= m_frameWidth * m_frameHeight * 255;
188+
differenceG /= m_frameWidth * m_frameHeight * 255;
189+
differenceB /= m_frameWidth * m_frameHeight * 255;
190+
191+
double totalDifference = differenceR + differenceG + differenceB;
192+
totalDifference /= 3;
193+
return totalDifference;
135194
}
136195

137196
void cmdplay::Asciifier::ClearDitherErrors(float* buffer)
@@ -161,111 +220,164 @@ void cmdplay::Asciifier::WriteDitherError(int x, int y, float error, float* buff
161220
}
162221
}
163222

164-
std::string cmdplay::Asciifier::BuildFrame(const uint8_t* rgbData)
223+
std::string cmdplay::Asciifier::BuildFrame(const uint8_t* rgbData, bool fullRedraw)
165224
{
166-
if (m_useColorDithering)
167-
ClearDitherErrors(m_hDitherErrors.get());
168-
169-
if (m_useTextDithering)
170-
ClearDitherErrors(m_textDitherErrors.get());
225+
if (m_useColors && m_useAccurateColors)
226+
{
227+
std::stringstream ss;
171228

172-
auto asciiData = std::make_unique<char[]>(m_targetFramebufferSize + 1);
173-
char* asciiDataArr = asciiData.get();
174-
for (int i = 0, scanX = 0; i < m_targetFramebufferSize; ++i)
175-
if (++scanX == m_frameWidthWithStride + 1)
176229
{
177-
scanX = 0;
178-
asciiDataArr[i] = '\n';
230+
Pixel* temp = m_frameBuffer;
231+
m_frameBuffer = m_backBuffer;
232+
m_backBuffer = temp;
179233
}
180-
181-
// Set null-terminator
182-
asciiData[m_targetFramebufferSize] = 0;
183-
184-
int scanX = 0;
185-
int rowOffset = 0;
186-
int col = 0;
187-
int row = 0;
188-
for (int i = 0; i < m_frameSubpixelCount; i += 3)
189-
{
190-
// Max value = 255.000
191-
int32_t pixelBrightness =
192-
rgbData[i] * PERCEIVED_LUMINANCE_R_FACTOR +
193-
rgbData[i + 1] * PERCEIVED_LUMINANCE_G_FACTOR +
194-
rgbData[i + 2] * PERCEIVED_LUMINANCE_B_FACTOR;
195234

196-
int16_t brightnessIndex;
197-
if (m_useTextDithering)
235+
bool repositionCursor = false;
236+
int lastRow = 0;
237+
for (int i = 0; i < m_frameWidth * m_frameHeight; i++)
198238
{
199-
pixelBrightness += static_cast<int>(1000 * m_textDitherErrors[col + row * m_frameWidth]);
200-
int16_t trueBrightnessByte = pixelBrightness / 1000;
201-
brightnessIndex = MapByteToArray(trueBrightnessByte);
202-
int actualBrightnessByte = brightnessIndex * 255 / (m_brightnessLevelCount - 1);
203-
float brightnessError = (static_cast<int>(trueBrightnessByte) - actualBrightnessByte) * DITHER_FACTOR;
204-
WriteDitherError(col, row, brightnessError, m_textDitherErrors.get());
239+
int32_t pixelBrightness =
240+
rgbData[i] * PERCEIVED_LUMINANCE_R_FACTOR +
241+
rgbData[i + 1] * PERCEIVED_LUMINANCE_G_FACTOR +
242+
rgbData[i + 2] * PERCEIVED_LUMINANCE_B_FACTOR;
243+
244+
int16_t brightnessIndex = MapByteToArray(pixelBrightness / 1000);
245+
246+
if (m_useAccurateColorsFullPixel)
247+
m_frameBuffer[i].c = ' ';
248+
else
249+
m_frameBuffer[i].c = ToCharUnchecked(brightnessIndex);
250+
251+
m_frameBuffer[i].r = rgbData[i * 3];
252+
m_frameBuffer[i].g = rgbData[i * 3 + 1];
253+
m_frameBuffer[i].b = rgbData[i * 3 + 2];
205254
}
206-
else
207-
brightnessIndex = MapByteToArray(pixelBrightness / 1000);
208255

209-
if (m_useColors && m_useAccurateColors)
256+
const double frameNoisinessAlpha = CalculateFrameBufferNoisiness();
257+
const double frameDifferenceAlpha = CalulateFrameBufferToBackBufferDifference();
258+
const double totalAlpha = frameNoisinessAlpha * frameDifferenceAlpha;
259+
260+
for (int i = 0; i < m_frameWidth * m_frameHeight; i++)
210261
{
211-
std::string outString;
212-
213-
// if the color we have last set rougly equals our current color,
214-
// we don't set our new color to reduce the stress on the console color interpreter
215-
if (ColorComponentNearlyEquals(rgbData[i], m_lastSetColor[0]) &&
216-
ColorComponentNearlyEquals(rgbData[i + 1], m_lastSetColor[1]) &&
217-
ColorComponentNearlyEquals(rgbData[i + 2], m_lastSetColor[2]))
218-
outString = "\x1BX000000000000000\x1B\\"; // string message that the console ignores
219-
else
262+
if (ColorComponentNearlyEquals(m_frameBuffer[i].r, m_backBuffer[i].r, COLOR_REDRAW_TOLERANCE * totalAlpha) &&
263+
ColorComponentNearlyEquals(m_frameBuffer[i].g, m_backBuffer[i].g, COLOR_REDRAW_TOLERANCE * totalAlpha) &&
264+
ColorComponentNearlyEquals(m_frameBuffer[i].b, m_backBuffer[i].b, COLOR_REDRAW_TOLERANCE * totalAlpha) &&
265+
!fullRedraw)
220266
{
221-
if (m_useAccurateColorsFullPixel)
222-
outString = "\x1B[48;2;" + ByteAsPaddedString(rgbData[i]) + ";" +
223-
ByteAsPaddedString(rgbData[i + 1]) + ";" + ByteAsPaddedString(rgbData[i + 2]) + "m";
224-
else
225-
outString = "\x1B[38;2;" + ByteAsPaddedString(rgbData[i]) + ";" +
226-
ByteAsPaddedString(rgbData[i + 1]) + ";" + ByteAsPaddedString(rgbData[i + 2]) + "m";
227-
228-
m_lastSetColor[0] = rgbData[i];
229-
m_lastSetColor[1] = rgbData[i + 1];
230-
m_lastSetColor[2] = rgbData[i + 2];
267+
// we skip this pixel as the color on the screen is close enough
268+
repositionCursor = true;
269+
270+
// set the color of the framebuffer back to the color that's on the screen
271+
m_frameBuffer[i].c = m_frameBuffer[i].c;
272+
m_frameBuffer[i].r = m_frameBuffer[i].r;
273+
m_frameBuffer[i].g = m_frameBuffer[i].g;
274+
m_frameBuffer[i].b = m_frameBuffer[i].b;
275+
276+
//ss << "B";
277+
continue;
231278
}
232279

233-
for (int i = 0; i < outString.size(); ++i)
234-
asciiDataArr[rowOffset + scanX + i] = outString.at(i);
280+
if (repositionCursor)
281+
{
282+
ss << "\x1B[" << std::to_string(i / m_frameWidth + 1) << ";" << std::to_string(i % m_frameWidth + 1) << "H";
283+
repositionCursor = false;
284+
}
235285

236-
if (m_useAccurateColorsFullPixel)
237-
asciiDataArr[rowOffset + scanX + outString.size()] = ' ';
238-
else
239-
asciiDataArr[rowOffset + scanX + outString.size()] = ToCharUnchecked(brightnessIndex);
286+
if (!(ColorComponentNearlyEquals(m_frameBuffer[i].r, m_lastSetColor[0], COLOR_BATCHING_TOLERANCE) &&
287+
ColorComponentNearlyEquals(m_frameBuffer[i].g, m_lastSetColor[1], COLOR_BATCHING_TOLERANCE) &&
288+
ColorComponentNearlyEquals(m_frameBuffer[i].b, m_lastSetColor[2], COLOR_BATCHING_TOLERANCE)))
289+
{
290+
ss << "\x1B[48;2;" << std::to_string(m_frameBuffer[i].r) << ";" << std::to_string(m_frameBuffer[i].g) << ";" << std::to_string(m_frameBuffer[i].b) << "m";
291+
m_lastSetColor[0] = m_frameBuffer[i].r;
292+
m_lastSetColor[1] = m_frameBuffer[i].g;
293+
m_lastSetColor[2] = m_frameBuffer[i].b;
294+
}
295+
296+
ss << m_frameBuffer[i].c;
297+
298+
if (i / m_frameWidth != lastRow)
299+
{
300+
repositionCursor = true;
301+
lastRow = i / m_frameWidth;
302+
}
240303
}
241-
else if (m_useColors)
304+
305+
return ss.str();
306+
}
307+
else // I cant port that shit over man
308+
{
309+
if (m_useColorDithering)
310+
ClearDitherErrors(m_hDitherErrors.get());
311+
312+
if (m_useTextDithering)
313+
ClearDitherErrors(m_textDitherErrors.get());
314+
315+
auto asciiData = std::make_unique<char[]>(m_targetFramebufferSize + 1);
316+
char* asciiDataArr = asciiData.get();
317+
for (int i = 0, scanX = 0; i < m_targetFramebufferSize; ++i)
318+
if (++scanX == m_frameWidthWithStride + 1)
319+
{
320+
scanX = 0;
321+
asciiDataArr[i] = '\n';
322+
}
323+
324+
// Set null-terminator
325+
asciiData[m_targetFramebufferSize] = 0;
326+
327+
int scanX = 0;
328+
int rowOffset = 0;
329+
int col = 0;
330+
int row = 0;
331+
for (int i = 0; i < m_frameSubpixelCount; i += 3)
242332
{
243-
auto color =
244-
m_useColorDithering ?
245-
GetColorDithered(rgbData[i], rgbData[i + 1], rgbData[i + 2], col, row) :
246-
GetColor(rgbData[i], rgbData[i + 1], rgbData[i + 2]);
247-
asciiDataArr[rowOffset + scanX] = '\x1B';
248-
asciiDataArr[rowOffset + scanX + 1] = '[';
249-
asciiDataArr[rowOffset + scanX + 2] = color[0];
250-
asciiDataArr[rowOffset + scanX + 3] = color[1];
251-
asciiDataArr[rowOffset + scanX + 4] = 'm';
252-
asciiDataArr[rowOffset + scanX + 5] = ToChar(brightnessIndex);
253-
}
254-
else
255-
asciiDataArr[rowOffset + scanX] = m_useTextDithering ?
333+
// Max value = 255.000
334+
int32_t pixelBrightness =
335+
rgbData[i] * PERCEIVED_LUMINANCE_R_FACTOR +
336+
rgbData[i + 1] * PERCEIVED_LUMINANCE_G_FACTOR +
337+
rgbData[i + 2] * PERCEIVED_LUMINANCE_B_FACTOR;
338+
339+
int16_t brightnessIndex;
340+
if (m_useTextDithering)
341+
{
342+
pixelBrightness += static_cast<int>(1000 * m_textDitherErrors[col + row * m_frameWidth]);
343+
int16_t trueBrightnessByte = pixelBrightness / 1000;
344+
brightnessIndex = MapByteToArray(trueBrightnessByte);
345+
int actualBrightnessByte = brightnessIndex * 255 / (m_brightnessLevelCount - 1);
346+
float brightnessError = (static_cast<int>(trueBrightnessByte) - actualBrightnessByte) * DITHER_FACTOR;
347+
WriteDitherError(col, row, brightnessError, m_textDitherErrors.get());
348+
}
349+
else
350+
brightnessIndex = MapByteToArray(pixelBrightness / 1000);
351+
352+
if (m_useColors)
353+
{
354+
auto color =
355+
m_useColorDithering ?
356+
GetColorDithered(rgbData[i], rgbData[i + 1], rgbData[i + 2], col, row) :
357+
GetColor(rgbData[i], rgbData[i + 1], rgbData[i + 2]);
358+
asciiDataArr[rowOffset + scanX] = '\x1B';
359+
asciiDataArr[rowOffset + scanX + 1] = '[';
360+
asciiDataArr[rowOffset + scanX + 2] = color[0];
361+
asciiDataArr[rowOffset + scanX + 3] = color[1];
362+
asciiDataArr[rowOffset + scanX + 4] = 'm';
363+
asciiDataArr[rowOffset + scanX + 5] = ToChar(brightnessIndex);
364+
}
365+
else
366+
asciiDataArr[rowOffset + scanX] = m_useTextDithering ?
256367
ToChar(brightnessIndex) : ToCharUnchecked(brightnessIndex);
257368

258-
scanX += m_pixelStride;
259-
++col;
260-
if (scanX == m_frameWidthWithStride)
261-
{
262-
scanX = 0;
263-
rowOffset += m_frameWidthWithStride + 1;
264-
++row;
265-
col = 0;
266-
m_lastBrightnessError = 0;
369+
scanX += m_pixelStride;
370+
++col;
371+
if (scanX == m_frameWidthWithStride)
372+
{
373+
scanX = 0;
374+
rowOffset += m_frameWidthWithStride + 1;
375+
++row;
376+
col = 0;
377+
m_lastBrightnessError = 0;
378+
}
267379
}
268-
}
269380

270-
return std::string(asciiDataArr);
381+
return std::string(asciiDataArr);
382+
}
271383
}

cmdplaypp/src/Asciifier.hpp

+19-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <array>
66
#include <vector>
77
#include <memory>
8+
#include <unordered_map>
89

910
namespace cmdplay
1011
{
@@ -16,14 +17,24 @@ namespace cmdplay
1617
constexpr int DITHER_NEIGHBOR_BOTTOM_LEFT_FACTOR = 3;
1718
constexpr int DITHER_NEIGHBOR_BOTTOM_FACTOR = 5;
1819
constexpr int DITHER_NEIGHBOR_BOTTOM_RIGHT_FACTOR = 1;
19-
constexpr uint8_t COLOR_BATCHING_TOLERANCE = 2;
20+
constexpr uint8_t COLOR_BATCHING_TOLERANCE = 4;
21+
constexpr uint8_t COLOR_REDRAW_TOLERANCE = 104;
22+
23+
struct Pixel
24+
{
25+
char c;
26+
uint8_t r;
27+
uint8_t g;
28+
uint8_t b;
29+
};
2030

2131
class Asciifier
2232
{
2333
public:
2434
Asciifier(const std::string& brightnessLevels, int frameWidth, int frameHeight,
2535
bool useColors = true, bool useColorDithering = true, bool useTextDithering = true, bool useAccurateColors = true, bool useAccurateColorsFullPixel = true);
2636

37+
~Asciifier();
2738
private:
2839
inline int16_t MapByteToArray(int16_t value);
2940
inline char ToChar(int16_t index);
@@ -32,7 +43,9 @@ namespace cmdplay
3243
inline std::string GetColor(uint8_t r, uint8_t g, uint8_t b);
3344
inline std::string GetColorDithered(uint8_t r, uint8_t g, uint8_t b, int x, int y);
3445
std::string ByteAsPaddedString(uint8_t i);
35-
bool ColorComponentNearlyEquals(uint8_t value, uint8_t other);
46+
bool ColorComponentNearlyEquals(uint8_t value, uint8_t other, uint8_t tolerance = COLOR_BATCHING_TOLERANCE);
47+
float CalculateFrameBufferNoisiness();
48+
float CalulateFrameBufferToBackBufferDifference();
3649
std::unique_ptr<float[]> m_hDitherErrors;
3750
bool m_useColorDithering = false;
3851
std::unique_ptr<float[]> m_textDitherErrors;
@@ -54,7 +67,10 @@ namespace cmdplay
5467
bool m_useAccurateColorsFullPixel = false;
5568
uint8_t m_lastSetColor[3] = { 255, 255, 255 };
5669

70+
Pixel* m_frameBuffer;
71+
Pixel* m_backBuffer;
72+
5773
public:
58-
std::string BuildFrame(const uint8_t* rgbData);
74+
std::string BuildFrame(const uint8_t* rgbData, bool fullRedraw);
5975
};
6076
}

0 commit comments

Comments
 (0)