parent
a87e4abf4b
commit
94a092011d
@ -0,0 +1,362 @@ |
||||
/*
|
||||
OneLoneCoder.com - Command Line Video |
||||
"Yuck..." - @Javidx9 |
||||
|
||||
Disclaimer |
||||
~~~~~~~~~~ |
||||
I don't care what you use this for. It's intended to be educational, and perhaps |
||||
to the oddly minded - a little bit of fun. Please hack this, change it and use it |
||||
in any way you see fit. BUT, you acknowledge that I am not responsible for anything |
||||
bad that happens as a result of your actions. However, if good stuff happens, I |
||||
would appreciate a shout out, or at least give the blog some publicity for me. |
||||
Cheers! |
||||
|
||||
Background |
||||
~~~~~~~~~~ |
||||
I was interested in trying to recreate video that is displayed using font |
||||
characters at the command line. Mixing the colours is quite challenging |
||||
given the constraints of the palette and characters. |
||||
|
||||
You'll need the ESCAPI libary binaries! |
||||
https://github.com/jarikomppa/escapi
|
||||
|
||||
|
||||
Author |
||||
~~~~~~ |
||||
Twitter: @javidx9 |
||||
Blog: www.onelonecoder.com |
||||
|
||||
Video: |
||||
~~~~~~ |
||||
https://youtu.be/pk1Y_26j1Y4
|
||||
|
||||
Last Updated: 02/10/2017 |
||||
*/ |
||||
|
||||
#include <iostream> |
||||
#include <string> |
||||
#include <algorithm> |
||||
using namespace std; |
||||
|
||||
#include "olcConsoleGameEngine.h" |
||||
#include "escapi.h" |
||||
|
||||
class OneLoneCoder_Video : public olcConsoleGameEngine |
||||
{ |
||||
public: |
||||
OneLoneCoder_Video() |
||||
{ |
||||
m_sAppName = L"Video"; |
||||
} |
||||
|
||||
private: |
||||
int nCameras = 0; |
||||
SimpleCapParams capture; |
||||
|
||||
protected: |
||||
virtual bool OnUserCreate() |
||||
{ |
||||
nCameras = setupESCAPI(); |
||||
|
||||
if (nCameras == 0) |
||||
return false; |
||||
|
||||
capture.mWidth = ScreenWidth(); |
||||
capture.mHeight = ScreenHeight(); |
||||
capture.mTargetBuf = new int[ScreenWidth() * ScreenHeight()]; |
||||
|
||||
if (initCapture(0, &capture) == 0) |
||||
return false; |
||||
|
||||
return true; |
||||
} |
||||
|
||||
void ClassifyPixel_Grey(float r, float g, float b, wchar_t &sym, short &fg_col, short &bg_col) |
||||
{ |
||||
float luminance = 0.2987f * r + 0.5870f * g + 0.1140f * b; |
||||
int pixel_bw = (int)(luminance * 13.0f); |
||||
switch (pixel_bw) |
||||
{ |
||||
case 0: bg_col = BG_BLACK; fg_col = FG_BLACK; sym = PIXEL_SOLID; break; |
||||
|
||||
case 1: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_QUARTER; break; |
||||
case 2: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_HALF; break; |
||||
case 3: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_THREEQUARTERS; break; |
||||
case 4: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_SOLID; break; |
||||
|
||||
case 5: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_QUARTER; break; |
||||
case 6: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_HALF; break; |
||||
case 7: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_THREEQUARTERS; break; |
||||
case 8: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_SOLID; break; |
||||
|
||||
case 9: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_QUARTER; break; |
||||
case 10: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_HALF; break; |
||||
case 11: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_THREEQUARTERS; break; |
||||
case 12: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_SOLID; break; |
||||
} |
||||
} |
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Taken from https://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both
|
||||
typedef struct
|
||||
{ |
||||
float r; // a fraction between 0 and 1
|
||||
float g; // a fraction between 0 and 1
|
||||
float b; // a fraction between 0 and 1
|
||||
} rgb; |
||||
|
||||
typedef struct
|
||||
{ |
||||
float h; // angle in degrees
|
||||
float s; // a fraction between 0 and 1
|
||||
float v; // a fraction between 0 and 1
|
||||
} hsv; |
||||
|
||||
hsv rgb2hsv(rgb in) |
||||
{ |
||||
hsv out; |
||||
float min, max, delta; |
||||
|
||||
min = in.r < in.g ? in.r : in.g; |
||||
min = min < in.b ? min : in.b; |
||||
|
||||
max = in.r > in.g ? in.r : in.g; |
||||
max = max > in.b ? max : in.b; |
||||
|
||||
out.v = max; // v
|
||||
delta = max - min; |
||||
if (delta < 0.00001f) |
||||
{ |
||||
out.s = 0; |
||||
out.h = 0; // undefined, maybe nan?
|
||||
return out; |
||||
} |
||||
if (max > 0.0) { // NOTE: if Max is == 0, this divide would cause a crash
|
||||
out.s = (delta / max); // s
|
||||
} |
||||
else { |
||||
// if max is 0, then r = g = b = 0
|
||||
// s = 0, h is undefined
|
||||
out.s = 0.0; |
||||
out.h = NAN; // its now undefined
|
||||
return out; |
||||
} |
||||
if (in.r >= max) // > is bogus, just keeps compilor happy
|
||||
out.h = (in.g - in.b) / delta; // between yellow & magenta
|
||||
else |
||||
if (in.g >= max) |
||||
out.h = 2.0 + (in.b - in.r) / delta; // between cyan & yellow
|
||||
else |
||||
out.h = 4.0 + (in.r - in.g) / delta; // between magenta & cyan
|
||||
|
||||
out.h *= 60.0; // degrees
|
||||
|
||||
if (out.h < 0.0) |
||||
out.h += 360.0; |
||||
|
||||
return out; |
||||
} |
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ClassifyPixel_HSL(float r, float g, float b, wchar_t &sym, short &fg_col, short &bg_col) |
||||
{ |
||||
hsv col = rgb2hsv({ r, g, b }); |
||||
|
||||
const struct { wchar_t c; short fg; short bg; } hues[] = |
||||
{ |
||||
{ PIXEL_SOLID, FG_RED | BG_RED }, |
||||
{ PIXEL_QUARTER, FG_YELLOW | BG_RED }, |
||||
{ PIXEL_HALF, FG_YELLOW | BG_RED }, |
||||
{ PIXEL_THREEQUARTERS, FG_YELLOW | BG_RED }, |
||||
|
||||
{ PIXEL_SOLID, FG_GREEN | BG_YELLOW }, |
||||
{ PIXEL_QUARTER, FG_GREEN | BG_YELLOW }, |
||||
{ PIXEL_HALF, FG_GREEN | BG_YELLOW }, |
||||
{ PIXEL_THREEQUARTERS, FG_GREEN | BG_YELLOW }, |
||||
|
||||
{ PIXEL_SOLID, FG_CYAN | BG_GREEN }, |
||||
{ PIXEL_QUARTER, FG_CYAN | BG_GREEN }, |
||||
{ PIXEL_HALF, FG_CYAN | BG_GREEN }, |
||||
{ PIXEL_THREEQUARTERS, FG_CYAN | BG_GREEN }, |
||||
|
||||
{ PIXEL_SOLID, FG_BLUE | BG_CYAN }, |
||||
{ PIXEL_QUARTER, FG_BLUE | BG_CYAN }, |
||||
{ PIXEL_HALF, FG_BLUE | BG_CYAN }, |
||||
{ PIXEL_THREEQUARTERS, FG_BLUE | BG_CYAN }, |
||||
|
||||
{ PIXEL_SOLID, FG_MAGENTA | BG_BLUE }, |
||||
{ PIXEL_QUARTER, FG_MAGENTA | BG_BLUE }, |
||||
{ PIXEL_HALF, FG_MAGENTA | BG_BLUE }, |
||||
{ PIXEL_THREEQUARTERS, FG_MAGENTA | BG_BLUE }, |
||||
|
||||
{ PIXEL_SOLID, FG_RED | BG_MAGENTA }, |
||||
{ PIXEL_QUARTER, FG_RED | BG_MAGENTA }, |
||||
{ PIXEL_HALF, FG_RED | BG_MAGENTA }, |
||||
{ PIXEL_THREEQUARTERS, FG_RED | BG_MAGENTA }, |
||||
|
||||
}; |
||||
|
||||
int index = (int)((col.h / 360.0f) * 24.0f); |
||||
|
||||
if (col.s > 0.2f) |
||||
{ |
||||
sym = hues[index].c; |
||||
fg_col = hues[index].fg; |
||||
bg_col = hues[index].bg; |
||||
} |
||||
else |
||||
ClassifyPixel_Grey(r, g, b, sym, fg_col, bg_col); |
||||
} |
||||
|
||||
void ClassifyPixel_OLC(float r, float g, float b, wchar_t &sym, short &fg_col, short &bg_col) |
||||
{ |
||||
// Is pixel coloured (i.e. RGB values exhibit significant variance)
|
||||
float fMean = (r + g + b) / 3.0f; |
||||
float fRVar = (r - fMean)*(r - fMean); |
||||
float fGVar = (g - fMean)*(g - fMean); |
||||
float fBVar = (b - fMean)*(b - fMean); |
||||
float fVariance = fRVar + fGVar + fBVar; |
||||
|
||||
if (fVariance < 0.0001f) |
||||
{ |
||||
ClassifyPixel_Grey(r, g, b, sym, fg_col, bg_col); |
||||
} |
||||
else |
||||
{ |
||||
// Pixel has colour so get dominant colour
|
||||
float y = min(r, g);// (r * g);
|
||||
float c = min(g, b);// (g * b);
|
||||
float m = min(b, r);// (b * r);
|
||||
|
||||
float fMean2 = (y + c + m) / 3.0f; |
||||
float fYVar = (y - fMean2)*(y - fMean2); |
||||
float fCVar = (c - fMean2)*(c - fMean2); |
||||
float fMVar = (m - fMean2)*(m - fMean2); |
||||
|
||||
float fMaxPrimaryVar = max(fRVar, fGVar); |
||||
fMaxPrimaryVar = max(fMaxPrimaryVar, fBVar); |
||||
|
||||
float fMaxSecondaryVar = max(fCVar, fYVar); |
||||
fMaxSecondaryVar = max(fMaxSecondaryVar, fMVar); |
||||
|
||||
float fShading = 0.5f; |
||||
|
||||
auto compare = [&](float fV1, float fV2, float fC1, float fC2, short FG_LIGHT, short FG_DARK, short BG_LIGHT, short BG_DARK) |
||||
{ |
||||
if (fV1 >= fV2) |
||||
{ |
||||
// Primary Is Dominant, Use in foreground
|
||||
fg_col = fC1 > 0.5f ? FG_LIGHT : FG_DARK; |
||||
|
||||
if (fV2 < 0.0001f) |
||||
{ |
||||
// Secondary is not variant, Use greyscale in background
|
||||
if (fC2 >= 0.00f && fC2 < 0.25f) bg_col = BG_BLACK; |
||||
if (fC2 >= 0.25f && fC2 < 0.50f) bg_col = BG_DARK_GREY; |
||||
if (fC2 >= 0.50f && fC2 < 0.75f) bg_col = BG_GREY; |
||||
if (fC2 >= 0.75f && fC2 <= 1.00f) bg_col = BG_WHITE; |
||||
} |
||||
else |
||||
{ |
||||
// Secondary is varient, Use in background
|
||||
bg_col = fC2 > 0.5f ? BG_LIGHT : BG_DARK; |
||||
} |
||||
|
||||
// Shade dominant over background (100% -> 0%)
|
||||
fShading = ((fC1 - fC2) / 2.0f) + 0.5f; |
||||
} |
||||
|
||||
if (fShading >= 0.00f && fShading < 0.20f) sym = L' '; |
||||
if (fShading >= 0.20f && fShading < 0.40f) sym = PIXEL_QUARTER; |
||||
if (fShading >= 0.40f && fShading < 0.60f) sym = PIXEL_HALF; |
||||
if (fShading >= 0.60f && fShading < 0.80f) sym = PIXEL_THREEQUARTERS; |
||||
if (fShading >= 0.80f) sym = PIXEL_SOLID; |
||||
}; |
||||
|
||||
if (fRVar == fMaxPrimaryVar && fYVar == fMaxSecondaryVar) |
||||
compare(fRVar, fYVar, r, y, FG_RED, FG_DARK_RED, BG_YELLOW, BG_DARK_YELLOW); |
||||
|
||||
if (fRVar == fMaxPrimaryVar && fMVar == fMaxSecondaryVar) |
||||
compare(fRVar, fMVar, r, m, FG_RED, FG_DARK_RED, BG_MAGENTA, BG_DARK_MAGENTA); |
||||
|
||||
if (fRVar == fMaxPrimaryVar && fCVar == fMaxSecondaryVar) |
||||
compare(fRVar, fCVar, r, c, FG_RED, FG_DARK_RED, BG_CYAN, BG_DARK_CYAN); |
||||
|
||||
if (fGVar == fMaxPrimaryVar && fYVar == fMaxSecondaryVar) |
||||
compare(fGVar, fYVar, g, y, FG_GREEN, FG_DARK_GREEN, BG_YELLOW, BG_DARK_YELLOW); |
||||
|
||||
if (fGVar == fMaxPrimaryVar && fCVar == fMaxSecondaryVar) |
||||
compare(fGVar, fCVar, g, c, FG_GREEN, FG_DARK_GREEN, BG_CYAN, BG_DARK_CYAN); |
||||
|
||||
if (fGVar == fMaxPrimaryVar && fMVar == fMaxSecondaryVar) |
||||
compare(fGVar, fMVar, g, m, FG_GREEN, FG_DARK_GREEN, BG_MAGENTA, BG_DARK_MAGENTA); |
||||
|
||||
if (fBVar == fMaxPrimaryVar && fMVar == fMaxSecondaryVar) |
||||
compare(fBVar, fMVar, b, m, FG_BLUE, FG_DARK_BLUE, BG_MAGENTA, BG_DARK_MAGENTA); |
||||
|
||||
if (fBVar == fMaxPrimaryVar && fCVar == fMaxSecondaryVar) |
||||
compare(fBVar, fCVar, b, c, FG_BLUE, FG_DARK_BLUE, BG_CYAN, BG_DARK_CYAN); |
||||
|
||||
if (fBVar == fMaxPrimaryVar && fYVar == fMaxSecondaryVar) |
||||
compare(fBVar, fYVar, b, y, FG_BLUE, FG_DARK_BLUE, BG_YELLOW, BG_DARK_YELLOW); |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
|
||||
virtual bool OnUserUpdate(float fElapsedTime) |
||||
{ |
||||
// Capture webcam image
|
||||
doCapture(0); while (isCaptureDone(0) == 0) {} |
||||
|
||||
for (int x = 0; x < capture.mWidth; x++) |
||||
for (int y = 0; y < capture.mHeight; y++) |
||||
{ |
||||
// Get pixel
|
||||
union RGBint |
||||
{ |
||||
int rgb; |
||||
unsigned char c[4]; |
||||
}; |
||||
|
||||
RGBint col; |
||||
int id = y * capture.mWidth + x; |
||||
|
||||
col.rgb = capture.mTargetBuf[id]; |
||||
int r = col.c[2]; |
||||
int g = col.c[1]; |
||||
int b = col.c[0]; |
||||
|
||||
float fR = (float)r / 255.0f; |
||||
float fG = (float)g / 255.0f; |
||||
float fB = (float)b / 255.0f; |
||||
|
||||
// Convert into character/colour combo
|
||||
wchar_t sym; |
||||
short bg_col = 0; |
||||
short fg_col = 0; |
||||
|
||||
//ClassifyPixel_Grey(fR, fG, fB, sym, fg_col, bg_col);
|
||||
//ClassifyPixel_HSL(fR, fG, fB, sym, fg_col, bg_col);
|
||||
ClassifyPixel_OLC(fR, fG, fB, sym, fg_col, bg_col); |
||||
|
||||
// Draw pixel
|
||||
Draw(x, y, sym, bg_col | fg_col); |
||||
|
||||
|
||||
} |
||||
|
||||
|
||||
return true; |
||||
} |
||||
|
||||
}; |
||||
|
||||
int main() |
||||
{ |
||||
OneLoneCoder_Video game; |
||||
game.ConstructConsole(320, 240, 4, 4); |
||||
game.Start(); |
||||
return 0; |
||||
} |
Loading…
Reference in new issue