You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
379 lines
11 KiB
379 lines
11 KiB
/*
|
|
OneLoneCoder.com - Command Line Video
|
|
"Yuck..." - @Javidx9
|
|
|
|
License
|
|
~~~~~~~
|
|
Copyright (C) 2018 Javidx9
|
|
This program comes with ABSOLUTELY NO WARRANTY.
|
|
This is free software, and you are welcome to redistribute it
|
|
under certain conditions; See license for details.
|
|
Original works located at:
|
|
https://www.github.com/onelonecoder
|
|
https://www.onelonecoder.com
|
|
https://www.youtube.com/javidx9
|
|
|
|
GNU GPLv3
|
|
https://github.com/OneLoneCoder/videos/blob/master/LICENSE
|
|
|
|
From Javidx9 :)
|
|
~~~~~~~~~~~~~~~
|
|
Hello! Ultimately 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. You acknowledge
|
|
that I am not responsible for anything bad that happens as a result of
|
|
your actions. However this code is protected by GNU GPLv3, see the license in the
|
|
github repo. This means you must attribute me if you use it. You can view this
|
|
license here: https://github.com/OneLoneCoder/videos/blob/master/LICENSE
|
|
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;
|
|
}
|
|
|