The official distribution of olcConsoleGameEngine, a tool used in javidx9's YouTube videos and projects
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.
videos/OneLoneCoder_Webcam.cpp

380 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;
}