diff --git a/OneLoneCoder_Webcam.cpp b/OneLoneCoder_Webcam.cpp new file mode 100644 index 0000000..7894d56 --- /dev/null +++ b/OneLoneCoder_Webcam.cpp @@ -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 +#include +#include +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; +} \ No newline at end of file