Visualize RGB gradient going from red to blue in C++ - c++
I have a value between 0 and 1 which I'm trying to determine a color value for representing the size of the value. The higher the value, the more "blue" it should be, and the lower the value the more red it should be, with green in the middle. So the float value 1 in RGB should be (0,0,255), the value 0 in RGB should be (255,0,0), with (0,255,0) in the middle. I tried to implement it but in my implementation the highest value is white and the lowest is black, which is not what I'm trying to achieve.
Does anyone know how to implement this?
My attempt was :
unsigned int rgb[] = {0,0,0};
//16581375 = 255^3
unsigned long colorValue = floatBetweenZeroAndOne * 16581375;
rgb[0] = colorValue %255;
rgb[1] = (colorValue / 255) % 255;
rgb[2] = ((colorValue / 255) / 255) % 255;
Try this:
unsigned int rgb[] = { 0,0,0 };
float floatBetweenZeroAndOne = 0.25f;
if (floatBetweenZeroAndOne <= 0.5f)
{
floatBetweenZeroAndOne *= 2.0f;
rgb[0] = (unsigned int)(255 * (1.0f - floatBetweenZeroAndOne) + 0.5f);
rgb[1] = (unsigned int)(255 * (floatBetweenZeroAndOne) + 0.5f);
}
else
{
floatBetweenZeroAndOne = floatBetweenZeroAndOne * 2.0f - 1.0f;
rgb[1] = (unsigned int)(255 * (1.0f - floatBetweenZeroAndOne) + 0.5f);
rgb[2] = (unsigned int)(255 * (floatBetweenZeroAndOne) + 0.5f);
}
It will smoothly interpolates the float color value between R G B components.
So basically you are saying that the rgb[0] should be 0 if your float variable is 0 and it should be 255 if your variable is 1. Your float variable * 255 should do the trick for the red color. Let's just call your float variable floatValue from now on.
Now let's do the blue color. This is basically the opposite of red. We want the bluecolor to be 255 when floatValue is 0 and it to be 0 when the floatValue is 1. You could use it like this: rgb[2] = 255 - floatValue * 255.
The green one is the most tricky I guess. We want that value to rise if 0 < floatValue < 0.5 and degrade when 0.5 < floatValue < 1. I think an if-statement would do the trick.
In total I would code it something like this. I haven't tested it.
unsigned float floatValue = 0.236; // Completely random number between zero and one.
unsigned int rgb[] = {0,0,0};
rgb[0] = floatValue * 255;
rgb[2] = 255 - floatValue * 255;
if (floatValue > 0 && <= 0.5)
{
rgb[1] = floatValue * 512 // Since I guess you want it to be maxed out at 128.
}
else if (floatValue > 0.5 && floatValue <= 1)
{
rgb[1] = 255 - (floatValue - 0.5)*512;
}
else
{
cout << "floatValue has a value smaller than 0 or bigger than 1!";
return -1;
}
A function like this should do the job
void setColor(double value, int* rgb){
if(value > 1 || value < 0) return;
if(value > 0.5){
value -= 0.5;
rgb[0] = 0;
rgb[1] = (int)((1-2*value)*255);
rgb[2] = (int)(2*value*255);
}
if(value <= 0.5){
rgb[0] = (int)((1-2*value)*255);
rgb[1] = (int)(2*value*255);
rgb[2] = 0;
}
}
You could also try this version:
void rgbscalar(float scalar)
{
float ratio = 2 * scalar;
int r = 255 * (ratio - 1);
if ( r < 0 ) r = 0;
int b = 255 * (1 - ratio);
if ( b < 0 ) b = 0;
int g = 255 - b - r;
if ( g < 0 ) g = 0;
std::cout << r << " " << g << " " << b << " : " << scalar << std::endl;
}
This code is a general-purpose linear gradient from a Color 1 to a Color 2; you can use it to make multiple gradients, for example, red to green and green to blue, and populate a array with the variations in-between c1 and c2.
If anyone knows a better way of making this code, feel free to suggest :).
#include <cstdint> # Defines uint_fast32_t
#pragma GCC diagnostic push // GCC only
#pragma GCC diagnostic ignored "-Wconversion" // GCC only, disable bitfield warnings
//+------------------------------------------------------------------+
// Color
// Parameters c1 and c2 are ARGB Colors to Gradient into the return value
// Gradient and Alpha is a percentage from c1 towards c2, accepts any range, but anything out of [0; 1.0] is cut
// The return value of the function is guaranteed to be between [0xYY000000; 0xYYFFFFFF], even if the gradient is invalid, and YY equals to the Alpha between [0; 1.0] -> AA[0; 255] proportionally
//+------------------------------------------------------------------+
uint_fast32_t argbGradient(uint_fast32_t c1, uint_fast32_t c2, double gradient = 0.5, double alpha = 0.0)
{
uint_fast32_t c = 0x00000000;
// Red is at 0x000000##
// Green is at 0x0000##00
// Blue is at 0x00##0000
// Alpha is at 0x##000000
if(gradient > 1.0) gradient = 1.0;
else if (gradient < +0.0) gradient = +0.0;
if(alpha > 1.0) alpha = 1.0;
else if(alpha < +0.0) alpha = +0.0;
uint_fast32_t red1 = (c1 & 0xFF);
uint_fast32_t green1 = (c1 & 0xFF00) >> 8;
uint_fast32_t blue1 = (c1 & 0xFF0000) >> 16;
uint_fast32_t alpha1 = (c1 & 0xFF000000) >> 24;
uint_fast32_t red2 = (c2 & 0xFF);
uint_fast32_t green2 = (c2 & 0xFF00) >> 8;
uint_fast32_t blue2 = (c2 & 0xFF0000) >> 16;
uint_fast32_t alpha2 = (c2 & 0xFF000000) >> 24;
if (red1 > red2) c = ((uint_fast32_t) (red1 - ((double) (red1 - red2) * gradient))) & 0xFF;
else c = ((uint_fast32_t) (red1 + ((double) (red2 - red1) * gradient))) & 0xFF;
if (green1 > green2) c = ((((uint_fast32_t) (green1 - ((double) (green1 - green2) * gradient))) & 0xFF) << 8) + c;
else c = ((((uint_fast32_t) (green1 + ((double) (green2 - green1) * gradient))) & 0xFF) << 8) + c;
if (blue1 > blue2) c = ((((uint_fast32_t) (blue1 - ((double) (blue1 - blue2) * gradient))) & 0xFF) << 16) + c;
else c = ((((uint_fast32_t) (blue1 + ((double) (blue2 - blue1) * gradient))) & 0xFF) << 16) + c;
if (alpha1 > alpha2) c = ((((uint_fast32_t) (alpha1 - ((double) (alpha1 - alpha2) * alpha))) & 0xFF) << 24) + c;
else c = ((((uint_fast32_t) (alpha1 + ((double) (alpha2 - alpha1) * alpha))) & 0xFF) << 24) + c;
return c;
}
#pragma GCC diagnostic pop // GCC only, restore specifically disabled flags
Related
Using float type for intermediate variable makes program run slower than int type, why?
I'm currently writing a program for YUV420SP => RGB/BGR color space conversion, follow the floatint-point formula calculation, without any SIMD or multi-threading optimization. The function's input data is unsigned char type, the finally result's type is also unsigned char type. But for the intermediate variables, the formula itself requires float type(the expressions in the right of the =), but for the float => unsigned char conversion, there are two choices, one is using float r, g, b the other is int r, g, b: unsigned char y = 223; // mock for getting y value unsigned char u = 200; // mock for getting u value unsigned char v = 200; // mock for getting v value unsigned char* rgb0 = (unsigned char*)malloc(MAXN); // for finally result saving // the YUV=>RGB color conversion float r, g, b; // [!! choice1 !!] if using this line, code run slower int r, g, b; // [!! choice2 !!] if using this line, code run much faster y = std::max(16, (int)y_ptr0[0]); r = 1.164 * (y - 16) + 1.596 * (v - 128); g = 1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128); b = 1.164 * (y - 16) + 2.018 * (u - 128); rgb0[2-b_idx] = saturate_ucast(r); rgb0[1] = saturate_ucast(g); rgb0[b_idx] = saturate_ucast(b); rgb0 += 3; What makes me confusing is, for the actual test (convert a width=7680x4320 image), the float r,g,b is about much slower that using int r, g, b, on both Linux x86 and Android ARMv8 platform The full code for the color conversion is: #include <limits.h> inline uchar saturate_uchar(int v) { return (uchar)((unsigned int)v <= UCHAR_MAX ? v : v > 0 ? UCHAR_MAX : 0); } inline uchar saturate_uchar(float v) { int iv = round(v); return saturate_uchar(iv); } template<int u_idx, int b_idx> void yuv420sp2rgb_naive( const uchar* y_plane, int height, int width, int y_linebytes, const uchar* uv_plane, int uv_linebytes, uchar* rgb, int rgb_linebytes, const Option& opt ) { /// param checking assert (y_plane!=NULL && uv_plane!=NULL && rgb!=NULL); /// neon-specific param checking assert (width>=2 && height>=2); int w = width; int h = height; for (int i=0; i <= h-2; i+=2) { const unsigned char* y_ptr0 = y_plane + i * y_linebytes; const unsigned char* y_ptr1 = y_ptr0 + y_linebytes; unsigned char* rgb0 = rgb + i * rgb_linebytes; unsigned char* rgb1 = rgb0+ rgb_linebytes; const unsigned char* uv_ptr = uv_plane + (i/2) * uv_linebytes; for (size_t j=0; j <= width-2; j += 2) { int y; float r, g, b; // choice1 //int r, g, b; // choice2 // R = 1.164(Y - 16) + 1.596(V - 128) // G = 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128) // B = 1.164(Y - 16) + 2.018(U - 128) int u = uv_ptr[u_idx]; int v = uv_ptr[1 - u_idx]; // y00 y = std::max(16, (int)y_ptr0[0]); r = 1.164 * (y - 16) + 1.596 * (v - 128); g = 1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128); b = 1.164 * (y - 16) + 2.018 * (u - 128); rgb0[2-b_idx] = saturate_uchar(r); rgb0[1] = saturate_uchar(g); rgb0[b_idx] = saturate_uchar(b); rgb0 += 3; // y01 y = std::max(16, (int)y_ptr0[1]); r = 1.164 * (y - 16) + 1.596 * (v - 128); g = 1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128); b = 1.164 * (y - 16) + 2.018 * (u - 128); rgb0[2-b_idx] = saturate_uchar(r); rgb0[1] = saturate_uchar(g); rgb0[b_idx] = saturate_uchar(b); rgb0 += 3; // y10 y = std::max(16, (int)y_ptr1[0]); r = 1.164 * (y - 16) + 1.596 * (v - 128); g = 1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128); b = 1.164 * (y - 16) + 2.018 * (u - 128); rgb1[2-b_idx] = saturate_uchar(r); rgb1[1] = saturate_uchar(g); rgb1[b_idx] = saturate_uchar(b); rgb1 += 3; // y11 y = std::max(16, (int)y_ptr1[1]); r = 1.164 * (y - 16) + 1.596 * (v - 128); g = 1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128); b = 1.164 * (y - 16) + 2.018 * (u - 128); rgb1[2-b_idx] = saturate_uchar(r); rgb1[1] = saturate_uchar(g); rgb1[b_idx] = saturate_uchar(b); rgb1 += 3; y_ptr0 += 2; y_ptr1 += 2; uv_ptr += 2; } } } platform choice time cost linux x64 float r, g, b 140 ms linux x64 int r, g, b 107 ms armv8 float r, g, b 152 ms armv8 int r, g, b 111 ms Question: why changing variable r,g,b's type from float to int boost speed so much?
SDL2.0 screen nullptr on render of Window
Hey so I'm relatively new to the SDL library and just trying to get to grips with it. I found a C++ conversion for Minecraft4k but it was based on SDL1.x so I'm trying to convert it to SDL2.0 At present the build is successful, but when it gets to; plot(x, y, rgbmul(col, fxmul(br, ddist))); It throws a read access violation exception: screen was nullptr This is my code; // C++ port of Minecraft 4k JS (http://jsdo.it/notch/dB1E) // By The8BitPimp // See: the8bitpimp.wordpress.com #include <SDL.h> #include <math.h> #include <windows.h> #include <tchar.h> #include "plot.h" #include "llist.h" const int w = 320; const int h = 240; SDL_Surface *screen = nullptr; const float math_pi = 3.14159265359f; static inline float math_sin(float x) { return sinf(x); } static inline float math_cos(float x) { return cosf(x); } // the texture map int texmap[16 * 16 * 16 * 3]; // the voxel map char map[64 * 64 * 64]; static inline int random(int max) { return (rand() ^ (rand() << 16)) % max; } static inline void plot(int x, int y, int c) { int *p = (int*)screen->pixels; p[y * w + x] = c; } static void makeTextures(void) { // each texture for (int j = 0; j<16; j++) { int k = 255 - random(96); // each pixel in the texture for (int m = 0; m<16 * 3; m++) for (int n = 0; n<16; n++) { int i1 = 0x966C4A; int i2 = 0; int i3 = 0; if (j == 4) i1 = 0x7F7F7F; if ((j != 4) || (random(3) == 0)) k = 255 - random(96); if (j == 1) { if (m < (((n * n * 3 + n * 81) >> 2) & 0x3) + 18) i1 = 0x6AAA40; else if (m < (((n * n * 3 + n * 81) >> 2) & 0x3) + 19) k = k * 2 / 3; } if (j == 7) { i1 = 0x675231; if ((n > 0) && (n < 15) && (((m > 0) && (m < 15)) || ((m > 32) && (m < 47)))) { i1 = 0xBC9862; i2 = n - 7; i3 = (m & 0xF) - 7; if (i2 < 0) i2 = 1 - i2; if (i3 < 0) i3 = 1 - i3; if (i3 > i2) i2 = i3; k = 196 - random(32) + i2 % 3 * 32; } else if (random(2) == 0) k = k * (150 - (n & 0x1) * 100) / 100; } if (j == 5) { i1 = 0xB53A15; if (((n + m / 4 * 4) % 8 == 0) || (m % 4 == 0)) i1 = 0xBCAFA5; } i2 = k; if (m >= 32) i2 /= 2; if (j == 8) { i1 = 5298487; if (random(2) == 0) { i1 = 0; i2 = 255; } } // fixed point colour multiply between i1 and i2 i3 = ((((i1 >> 16) & 0xFF) * i2 / 255) << 16) | ((((i1 >> 8) & 0xFF) * i2 / 255) << 8) | ((i1 & 0xFF) * i2 / 255); // pack the colour away texmap[n + m * 16 + j * 256 * 3] = i3; } } } static void makeMap(void) { // add random blocks to the map for (int x = 0; x < 64; x++) { for (int y = 0; y < 64; y++) { for (int z = 0; z < 64; z++) { int i = (z << 12) | (y << 6) | x; float yd = (y - 32.5) * 0.4; float zd = (z - 32.5) * 0.4; map[i] = random(16); float th = random(256) / 256.0f; if (th > sqrtf(sqrtf(yd * yd + zd * zd)) - 0.8f) map[i] = 0; } } } } static void init(void) { makeTextures(); makeMap(); } // fixed point byte byte multiply static inline int fxmul(int a, int b) { return (a*b) >> 8; } // fixed point 8bit packed colour multiply static inline int rgbmul(int a, int b) { int _r = (((a >> 16) & 0xff) * b) >> 8; int _g = (((a >> 8) & 0xff) * b) >> 8; int _b = (((a)& 0xff) * b) >> 8; return (_r << 16) | (_g << 8) | _b; } static void render(void) { float now = (float)(SDL_GetTicks() % 10000) / 10000.f; float xRot = math_sin(now * math_pi * 2) * 0.4 + math_pi / 2; float yRot = math_cos(now * math_pi * 2) * 0.4; float yCos = math_cos(yRot); float ySin = math_sin(yRot); float xCos = math_cos(xRot); float xSin = math_sin(xRot); float ox = 32.5 + now * 64.0; float oy = 32.5; float oz = 32.5; // for each column for (int x = 0; x < w; x++) { // get the x axis delta float ___xd = ((float)x - (float)w / 2.f) / (float)h; // for each row for (int y = 0; y < h; y++) { // get the y axis delta float __yd = ((float)y - (float)h / 2.f) / (float)h; float __zd = 1; float ___zd = __zd * yCos + __yd * ySin; float _yd = __yd * yCos - __zd * ySin; float _xd = ___xd * xCos + ___zd * xSin; float _zd = ___zd * xCos - ___xd * xSin; int col = 0; int br = 255; float ddist = 0; float closest = 32.f; // for each principle axis x,y,z for (int d = 0; d < 3; d++) { float dimLength = _xd; if (d == 1) dimLength = _yd; if (d == 2) dimLength = _zd; float ll = 1.0f / (dimLength < 0.f ? -dimLength : dimLength); float xd = (_xd)* ll; float yd = (_yd)* ll; float zd = (_zd)* ll; float initial = ox - floor(ox); if (d == 1) initial = oy - floor(oy); if (d == 2) initial = oz - floor(oz); if (dimLength > 0) initial = 1 - initial; float dist = ll * initial; float xp = ox + xd * initial; float yp = oy + yd * initial; float zp = oz + zd * initial; if (dimLength < 0) { if (d == 0) xp--; if (d == 1) yp--; if (d == 2) zp--; } // while we are concidering a ray that is still closer then the best so far while (dist < closest) { // quantize to the map grid int tex = map[(((int)zp & 63) << 12) | (((int)yp & 63) << 6) | ((int)xp & 63)]; // if this voxel has a texture applied if (tex > 0) { // find the uv coordinates of the intersection point int u = ((int)((xp + zp) * 16.f)) & 15; int v = ((int)(yp * 16.f) & 15) + 16; // fix uvs for alternate directions? if (d == 1) { u = ((int)(xp * 16.f)) & 15; v = (((int)(zp * 16.f)) & 15); if (yd < 0) v += 32; } // find the colour at the intersection point int cc = texmap[u + v * 16 + tex * 256 * 3]; // if the colour is not transparent if (cc > 0) { col = cc; ddist = 255 - ((dist / 32 * 255)); br = 255 * (255 - ((d + 2) % 3) * 50) / 255; // we now have the closest hit point (also terminates this ray) closest = dist; } } // advance the ray xp += xd; yp += yd; zp += zd; dist += ll; } } plot(x, y, rgbmul(col, fxmul(br, ddist))); } } } int main(int argc, char *argv[]) { SDL_Init(SDL_INIT_EVERYTHING); SDL_Window *screen; screen = SDL_CreateWindow( "Minecraft4k", // window title SDL_WINDOWPOS_CENTERED, // initial x position SDL_WINDOWPOS_CENTERED, // initial y position 320, // width, in pixels 240, // height, in pixels SDL_WINDOW_OPENGL // flags - see below ); SDL_Renderer* renderer; renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_ACCELERATED); if (screen == nullptr) { return 1; } init(); bool running = true; while (running) { SDL_Event event; while (SDL_PollEvent(&event)) { running &= (event.type != SDL_QUIT); } SDL_RenderPresent(renderer); render(); } SDL_DestroyWindow(screen); SDL_Quit(); return 0; } When I actually run the code I do get a black screen, but the debugger lands on the line plot(x, y, rgbmul(col, fxmul(br, ddist))); in ; static void render(void) This is all just "for fun" so any information or guidance is appreciated.
You define screen twice (the first time as a global variable, the second time within your main), but you initialize it only once (within your main). Because of that, the global variable screen actually is set to nullptr and plot fails trying to use it, as the error message states.
1555 DXT1 decompression giving incorrect image output
I am trying, in C++, to decompress 1555 DXT1 textures into RGBA 8888, storing the output into a std::string. I have successfully decompressed 565 DXT1 to RGBA 8888 using the squish lib, but just can't seem to get 1555 working. The program isn't crashing, and the output image looks almost correct, but there are several pixels in random places that are strange colours, as you can see in the output image below. Here's the code. using namespace std; string CTexture::extractRGBAData(void) { string strPixels; strPixels.resize(m_usImageSize[0] * m_usImageSize[1] * 4); for (unsigned long i = 0, j = m_usImageSize[0] * m_usImageSize[1] * 4; i < j; i++) { strPixels[i] = 0; } if (m_strImageData.length() == 0) { return strPixels; } unsigned long uiDXTCompressionType; if (m_uiPlatformId == 8) // GTA III, VC { uiDXTCompressionType = m_ucDXTCompressionType; } else if (m_uiPlatformId == 9) // SA { //uiDXTCompressionType = m_uiAlpha; uiDXTCompressionType = m_ucDXTCompressionType; } else if (m_uiPlatformId == 5) // XBOX, Android { uiDXTCompressionType = m_uiAlpha; } if (uiDXTCompressionType == DXT1) { unsigned long uiWidth = m_usImageSize[0]; unsigned long uiHeight = m_usImageSize[1]; if (m_uiRasterFormat == FORMAT_1555) { unsigned long uiPixelKey = 0, uiTexelSeek = 0; for (unsigned long y = 0; y < uiHeight; y += 4) { for (unsigned long x = 0; x < uiWidth; x += 4) { string strTexel = m_strImageData.substr(uiTexelSeek, 8); unsigned char *pPixels = new unsigned char[16 * 4]; unsigned char *pBlock = new unsigned char[8]; memcpy(pBlock, strTexel.c_str(), 8); decompress_DXT1_1555(pPixels, pBlock); for (unsigned long yOffset = 0; yOffset < 4; yOffset++) { for (unsigned long xOffset = 0; xOffset < 4; xOffset++) { unsigned long uiPixelKey = (y * uiWidth) + x + (yOffset * uiWidth) + xOffset; //CDebugger::log("uiPixelKey: " + CStringUtility::toString(uiPixelKey) + ", x: " + CStringUtility::toString(x) + ", y: " + CStringUtility::toString(y) + ", xOffset: " + CStringUtility::toString(xOffset) + ", yOffset: " + CStringUtility::toString(yOffset)); uiPixelKey *= 4; if (uiPixelKey < strPixels.size()) // this checks if the height has a remainder when dividing by 4 (as the iteration does 4x4 block of pixels) { strPixels[uiPixelKey + 0] = pPixels[(((yOffset * 4) + xOffset) * 4) + 2] & 0xFF; strPixels[uiPixelKey + 1] = pPixels[(((yOffset * 4) + xOffset) * 4) + 1] & 0xFF; strPixels[uiPixelKey + 2] = pPixels[(((yOffset * 4) + xOffset) * 4) + 0] & 0xFF; strPixels[uiPixelKey + 3] = 255;// pPixels[(((yOffset * 4) + xOffset) * 4) + 3] & 0xFF; } } } delete[] pPixels; delete[] pBlock; uiTexelSeek += 8; } } } } } void CTexture::decompress_DXT1_1555(unsigned char *pixels, unsigned char *block) { string strArea = string((char*)block, 8); string strPaletteStr = strArea.substr(0, 4); unsigned long uiIndexes = CStringUtility::unpackULong(strArea.substr(4, 4), false); unsigned char ucPalette[4][4]; double fPalette[4][4]; unsigned short usPaletteInt[2]; usPaletteInt[0] = CStringUtility::unpackUShort(strPaletteStr.substr(0, 2), false); // 1555 usPaletteInt[1] = CStringUtility::unpackUShort(strPaletteStr.substr(2, 2), false); // 1555 // based on: http://www.glassechidna.com.au/2009/devblogs/s3tc-dxt1dxt5-texture-decompression/ float red, green, blue, alpha; alpha = (usPaletteInt[0] >> 15) & 1; red = ((float)((usPaletteInt[0] >> 10) & 0x1F) * 255.0 + 16.0); red = ((red / 32.0) + red) / 32.0; green = ((float)((usPaletteInt[0] >> 5) & 0x1F) * 255.0 + 16.0); green = ((green / 32.0) + green) / 32.0; blue = ((float)(usPaletteInt[0] & 0x1F)) * 255.0 + 16.0; blue = ((blue / 32.0) + blue) / 32.0; fPalette[0][0] = red; fPalette[0][1] = green; fPalette[0][2] = blue; fPalette[0][3] = alpha; alpha = (usPaletteInt[1] >> 15) & 1; red = ((float)((usPaletteInt[1] >> 10) & 0x1F) * 255.0 + 16.0); red = ((red / 32.0) + red) / 32.0; green = ((float)((usPaletteInt[1] >> 5) & 0x1F) * 255.0 + 16.0); green = ((green / 32.0) + green) / 32.0; blue = ((float)(usPaletteInt[1] & 0x1F)) * 255.0 + 16.0; blue = ((blue / 32.0) + blue) / 32.0; fPalette[1][0] = red; fPalette[1][1] = green; fPalette[1][2] = blue; fPalette[1][3] = alpha; // fetch other 2 colours in palette, interpolated between min/max colours if (usPaletteInt[0] > usPaletteInt[1]) { fPalette[2][0] = (2.0 * fPalette[0][0] + fPalette[1][0]) / 3.0; fPalette[2][1] = (2.0 * fPalette[0][1] + fPalette[1][1]) / 3.0; fPalette[2][2] = (2.0 * fPalette[0][2] + fPalette[1][2]) / 3.0; fPalette[2][3] = 255; fPalette[3][0] = (fPalette[0][0] + 2.0 * fPalette[1][0]) / 3.0; fPalette[3][1] = (fPalette[0][1] + 2.0 * fPalette[1][1]) / 3.0; fPalette[3][2] = (fPalette[0][2] + 2.0 * fPalette[1][2]) / 3.0; fPalette[3][3] = 255; } else { fPalette[2][0] = (fPalette[0][0] + fPalette[1][0]) / 2.0; fPalette[2][1] = (fPalette[0][1] + fPalette[1][1]) / 2.0; fPalette[2][2] = (fPalette[0][2] + fPalette[1][2]) / 2.0; fPalette[2][3] = 255; fPalette[3][0] = 0; fPalette[3][1] = 0; fPalette[3][2] = 0; fPalette[3][3] = 255; // transparent black } for (unsigned long i5 = 0; i5 < 4; i5++) { ucPalette[i5][0] = fPalette[i5][0]; ucPalette[i5][1] = fPalette[i5][1]; ucPalette[i5][2] = fPalette[i5][2]; ucPalette[i5][3] = fPalette[i5][3]; } for (unsigned long i2 = 0; i2<16; i2++) { unsigned char index = (uiIndexes >> (i2 * 2)) & 3; unsigned char colour[4]; colour[0] = ((unsigned char)ucPalette[index][0]) & 0xFF; colour[1] = ((unsigned char)ucPalette[index][1]) & 0xFF; colour[2] = ((unsigned char)ucPalette[index][2]) & 0xFF; colour[3] = ((unsigned char)ucPalette[index][3]) & 0xFF; // store colour pixels[(i2 * 4) + 0] = colour[0] & 0xFF; pixels[(i2 * 4) + 1] = colour[1] & 0xFF; pixels[(i2 * 4) + 2] = colour[2] & 0xFF; pixels[(i2 * 4) + 3] = colour[3] & 0xFF; } }
I think you're misunderstanding how DXT1 works a bit. There isn't any alpha in the 2 base colors. They're both in 5:6:5. The "alpha" is only coming from the case where c0 <= c1. If the block fits this condition, then any pixel with the index 3 will be fully transparent (the 1 bit of alpha is inferred from that). So... read 5:6:5 (and set alpha=255 for those) instead of 1:5:5:5 in the base colors, and change your alpha on the "transparent black" case from 0,0,0,255 to 0,0,0,0 (actually transparent black instead of opaque black), and you should get better results.
RGB to HSI and HSI to RGB conversion
I am trying to covert RGB to HSI and revert it. (The task is required to have it from scratch.) In RGB to HSI convertion, Saturation and Intensity outputs are fine. But I don't seem to get the problem in the formulation of Hue. example output: Red = 255, Green = 255, Blue = 255 Hue = -2147483648, Saturation = 0, Intensity = 255 Red = 252, Green = 255, Blue = 255 Hue = 3, Saturation = 0.00787402, Intensity = 254 I use this calculator to check my outputs. Please let me know what's wrong. Thanks. #include <iostream> #include <cv.h> #include <highgui.h> #include "rgb.h" #include <cmath> #include <algorithm> #include <fstream> using namespace std; int main() { char infname[256]; ofstream outputFile, outputFile2; outputFile.open("RGB_HSI.txt"); outputFile2.open("HSI_RGB.txt"); cout << "Enter input image : "; cin >> infname; IplImage *img = cvLoadImage(infname, 1); RgbImage pic(img); int H = img->height; int W = img->width; for (int j=0;j<H;j++) for (int i=0;i<W;i++) { double temp = 0; double R =(double) pic[j][i].r; double G =(double) pic[j][i].g; double B =(double) pic[j][i].b; double intensity = 0; double hue = 0; double saturation = 0; int resultHue = 0; double resultSaturation = 0; int resultIntensity = 0; intensity = (R + G + B) / 3; if ((R + G + B) == 765) { saturation = 0; hue = 0; } double minimum = min(R, min(G, B)); if (intensity > 0) { saturation = 1 - minimum / intensity; } else if (intensity == 0) { saturation = 0; } temp = (R - (G/2) - (B/2)) / (sqrt((R*R) + (G*G) + (B*B) - (R*G) - (R*B) - (G*B))); if (G >= B) { hue = acos(temp); outputFile<<"1. temp = "<<temp<<", H = "<<hue<<endl; } else if (B > G) { hue = 360 - acos(temp); outputFile<<"2. temp = "<<temp<<", H = "<<hue<<endl; } resultHue = (int) hue; resultSaturation = saturation; resultIntensity = (int) intensity; //outputFile2<<"image = "<<pic[j][i]<<endl; outputFile<<"Red = "<<R<<", Green = "<<G<<", Blue = "<<B<<endl; outputFile<<"Hue = "<<resultHue<<", Saturation = "<<resultSaturation<<", Intensity = "<<resultIntensity<<endl; //converting HSI to RGB int backR = 0, backG = 0, backB = 0; if (resultHue == 0){ backR = (int) (resultIntensity + (2 * resultIntensity * resultSaturation)); backG = (int) (resultIntensity - (resultIntensity * resultSaturation)); backB = (int) (resultIntensity - (resultIntensity * resultSaturation)); } else if ((0 < resultHue) && (resultHue < 120)) { backR = (int) (resultIntensity + (resultIntensity * resultSaturation) * cos(resultHue) / cos(60-resultHue)); backG = (int) (resultIntensity + (resultIntensity * resultSaturation) * (1 - cos(resultHue) / cos(60-resultHue))); backB = (int) (resultIntensity - (resultIntensity * resultSaturation)); } else if ( resultHue == 120 ){ backR = (int) (resultIntensity - (resultIntensity * resultSaturation)); backG = (int) (resultIntensity + (2 * resultIntensity * resultSaturation)); backB = (int) (resultIntensity - (resultIntensity * resultSaturation)); } else if ((120 < resultHue) && (resultHue < 240)) { backR = (int) (resultIntensity - (resultIntensity * resultSaturation)); backG = (int) (resultIntensity + (resultIntensity * resultSaturation) * cos(resultHue-120) / cos(180-resultHue)); backB = (int) (resultIntensity + (resultIntensity * resultSaturation) * (1 - cos(resultHue-120) / cos(180-resultHue))); } else if (resultHue == 240) { backR = (int) (resultIntensity - (resultIntensity * resultSaturation)); backG = (int) (resultIntensity - (resultIntensity * resultSaturation)); backB = (int) (resultIntensity + (2 * resultIntensity * resultSaturation)); } else if ((240 < resultHue) && (resultHue < 360)) { backR = (int) (resultIntensity + (resultIntensity * resultSaturation) * (1 - cos(resultHue-240) / cos(300-resultHue))); backG = (int) (resultIntensity - (resultIntensity * resultSaturation)); backB = (int) (resultIntensity + (resultIntensity * resultSaturation) * cos(resultHue-240) / cos(300-resultHue)); } //outputpic[j][i] = (int) (R + G + B); //outputFile2<<"output = "<<outputpic[j][i]<<endl; outputFile2<<"Hue = "<<resultHue<<", Saturation = "<<resultSaturation<<", Intensity = "<<resultIntensity<<endl; outputFile2<<"Red = "<<backR<<", Green = "<<backG<<", Blue = "<<backB<<endl; } outputFile.close(); cout << "\nRGB_HSI values printed as text file: RGB_HSI.text\n"; outputFile2.close(); cout << "\nHSI_RGB values printed as text file: HSI_RGB.text\n"; return 0; }
The problem is in this line: temp = (R - (G/2) - (B/2)) / (sqrt((R*R) + (G*G) + (B*B) - (R*G) - (R*B) - (G*B))); When R = G = B, then you have a division by zero: R² - G² - B² - RG - RB - GB = R² + R² + R² - R² - R² - R² = 0 I'm actually surprised it didn't crashed... In that case, just assign 0 to the hue. From your link: Neutral colors--white, gray, and black--are set to 0° for convenience.
From others answer it looks like there is a divide by zero issue when R = G = B when you calculate temp but also from what I can tell you are using degrees with the trigonometric function but they are expecting radians i.e.: #include <cmath> #include <iostream> int main() { double pi = atan(1)*4 ; std::cout << cos(180) << std::endl ; std::cout << cos(360) << std::endl ; std::cout << cos(pi) << std::endl ; std::cout << cos(2*pi) << std::endl ; }
Algorithm to convert RGB to HSV and HSV to RGB in range 0-255 for both
I am looking for color space converter from RGB to HSV, specifically for the range 0 to 255 for both color spaces.
I've used these for a long time - no idea where they came from at this point... Note that the inputs and outputs, except for the angle in degrees, are in the range of 0 to 1.0. NOTE: this code does no real sanity checking on inputs. Proceed with caution! typedef struct { double r; // a fraction between 0 and 1 double g; // a fraction between 0 and 1 double b; // a fraction between 0 and 1 } rgb; typedef struct { double h; // angle in degrees double s; // a fraction between 0 and 1 double v; // a fraction between 0 and 1 } hsv; static hsv rgb2hsv(rgb in); static rgb hsv2rgb(hsv in); hsv rgb2hsv(rgb in) { hsv out; double 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.00001) { 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; } rgb hsv2rgb(hsv in) { double hh, p, q, t, ff; long i; rgb out; if(in.s <= 0.0) { // < is bogus, just shuts up warnings out.r = in.v; out.g = in.v; out.b = in.v; return out; } hh = in.h; if(hh >= 360.0) hh = 0.0; hh /= 60.0; i = (long)hh; ff = hh - i; p = in.v * (1.0 - in.s); q = in.v * (1.0 - (in.s * ff)); t = in.v * (1.0 - (in.s * (1.0 - ff))); switch(i) { case 0: out.r = in.v; out.g = t; out.b = p; break; case 1: out.r = q; out.g = in.v; out.b = p; break; case 2: out.r = p; out.g = in.v; out.b = t; break; case 3: out.r = p; out.g = q; out.b = in.v; break; case 4: out.r = t; out.g = p; out.b = in.v; break; case 5: default: out.r = in.v; out.g = p; out.b = q; break; } return out; }
You can also try this code without floats (faster but less accurate): typedef struct RgbColor { unsigned char r; unsigned char g; unsigned char b; } RgbColor; typedef struct HsvColor { unsigned char h; unsigned char s; unsigned char v; } HsvColor; RgbColor HsvToRgb(HsvColor hsv) { RgbColor rgb; unsigned char region, remainder, p, q, t; if (hsv.s == 0) { rgb.r = hsv.v; rgb.g = hsv.v; rgb.b = hsv.v; return rgb; } region = hsv.h / 43; remainder = (hsv.h - (region * 43)) * 6; p = (hsv.v * (255 - hsv.s)) >> 8; q = (hsv.v * (255 - ((hsv.s * remainder) >> 8))) >> 8; t = (hsv.v * (255 - ((hsv.s * (255 - remainder)) >> 8))) >> 8; switch (region) { case 0: rgb.r = hsv.v; rgb.g = t; rgb.b = p; break; case 1: rgb.r = q; rgb.g = hsv.v; rgb.b = p; break; case 2: rgb.r = p; rgb.g = hsv.v; rgb.b = t; break; case 3: rgb.r = p; rgb.g = q; rgb.b = hsv.v; break; case 4: rgb.r = t; rgb.g = p; rgb.b = hsv.v; break; default: rgb.r = hsv.v; rgb.g = p; rgb.b = q; break; } return rgb; } HsvColor RgbToHsv(RgbColor rgb) { HsvColor hsv; unsigned char rgbMin, rgbMax; rgbMin = rgb.r < rgb.g ? (rgb.r < rgb.b ? rgb.r : rgb.b) : (rgb.g < rgb.b ? rgb.g : rgb.b); rgbMax = rgb.r > rgb.g ? (rgb.r > rgb.b ? rgb.r : rgb.b) : (rgb.g > rgb.b ? rgb.g : rgb.b); hsv.v = rgbMax; if (hsv.v == 0) { hsv.h = 0; hsv.s = 0; return hsv; } hsv.s = 255 * long(rgbMax - rgbMin) / hsv.v; if (hsv.s == 0) { hsv.h = 0; return hsv; } if (rgbMax == rgb.r) hsv.h = 0 + 43 * (rgb.g - rgb.b) / (rgbMax - rgbMin); else if (rgbMax == rgb.g) hsv.h = 85 + 43 * (rgb.b - rgb.r) / (rgbMax - rgbMin); else hsv.h = 171 + 43 * (rgb.r - rgb.g) / (rgbMax - rgbMin); return hsv; } Note that this algorithm uses 0-255 as its range (not 0-360) as that was requested by the author of this question.
I wrote this in HLSL for our rendering engine, it has no conditions in it: float3 HSV2RGB( float3 _HSV ) { _HSV.x = fmod( 100.0 + _HSV.x, 1.0 ); // Ensure [0,1[ float HueSlice = 6.0 * _HSV.x; // In [0,6[ float HueSliceInteger = floor( HueSlice ); float HueSliceInterpolant = HueSlice - HueSliceInteger; // In [0,1[ for each hue slice float3 TempRGB = float3( _HSV.z * (1.0 - _HSV.y), _HSV.z * (1.0 - _HSV.y * HueSliceInterpolant), _HSV.z * (1.0 - _HSV.y * (1.0 - HueSliceInterpolant)) ); // The idea here to avoid conditions is to notice that the conversion code can be rewritten: // if ( var_i == 0 ) { R = V ; G = TempRGB.z ; B = TempRGB.x } // else if ( var_i == 2 ) { R = TempRGB.x ; G = V ; B = TempRGB.z } // else if ( var_i == 4 ) { R = TempRGB.z ; G = TempRGB.x ; B = V } // // else if ( var_i == 1 ) { R = TempRGB.y ; G = V ; B = TempRGB.x } // else if ( var_i == 3 ) { R = TempRGB.x ; G = TempRGB.y ; B = V } // else if ( var_i == 5 ) { R = V ; G = TempRGB.x ; B = TempRGB.y } // // This shows several things: // . A separation between even and odd slices // . If slices (0,2,4) and (1,3,5) can be rewritten as basically being slices (0,1,2) then // the operation simply amounts to performing a "rotate right" on the RGB components // . The base value to rotate is either (V, B, R) for even slices or (G, V, R) for odd slices // float IsOddSlice = fmod( HueSliceInteger, 2.0 ); // 0 if even (slices 0, 2, 4), 1 if odd (slices 1, 3, 5) float ThreeSliceSelector = 0.5 * (HueSliceInteger - IsOddSlice); // (0, 1, 2) corresponding to slices (0, 2, 4) and (1, 3, 5) float3 ScrollingRGBForEvenSlices = float3( _HSV.z, TempRGB.zx ); // (V, Temp Blue, Temp Red) for even slices (0, 2, 4) float3 ScrollingRGBForOddSlices = float3( TempRGB.y, _HSV.z, TempRGB.x ); // (Temp Green, V, Temp Red) for odd slices (1, 3, 5) float3 ScrollingRGB = lerp( ScrollingRGBForEvenSlices, ScrollingRGBForOddSlices, IsOddSlice ); float IsNotFirstSlice = saturate( ThreeSliceSelector ); // 1 if NOT the first slice (true for slices 1 and 2) float IsNotSecondSlice = saturate( ThreeSliceSelector-1.0 ); // 1 if NOT the first or second slice (true only for slice 2) return lerp( ScrollingRGB.xyz, lerp( ScrollingRGB.zxy, ScrollingRGB.yzx, IsNotSecondSlice ), IsNotFirstSlice ); // Make the RGB rotate right depending on final slice index }
Here's a C implementation based on Agoston's Computer Graphics and Geometric Modeling: Implementation and Algorithms p. 304, with H ∈ [0, 360] and S,V ∈ [0, 1]. #include <math.h> typedef struct { double r; // ∈ [0, 1] double g; // ∈ [0, 1] double b; // ∈ [0, 1] } rgb; typedef struct { double h; // ∈ [0, 360] double s; // ∈ [0, 1] double v; // ∈ [0, 1] } hsv; rgb hsv2rgb(hsv HSV) { rgb RGB; double H = HSV.h, S = HSV.s, V = HSV.v, P, Q, T, fract; (H == 360.)?(H = 0.):(H /= 60.); fract = H - floor(H); P = V*(1. - S); Q = V*(1. - S*fract); T = V*(1. - S*(1. - fract)); if (0. <= H && H < 1.) RGB = (rgb){.r = V, .g = T, .b = P}; else if (1. <= H && H < 2.) RGB = (rgb){.r = Q, .g = V, .b = P}; else if (2. <= H && H < 3.) RGB = (rgb){.r = P, .g = V, .b = T}; else if (3. <= H && H < 4.) RGB = (rgb){.r = P, .g = Q, .b = V}; else if (4. <= H && H < 5.) RGB = (rgb){.r = T, .g = P, .b = V}; else if (5. <= H && H < 6.) RGB = (rgb){.r = V, .g = P, .b = Q}; else RGB = (rgb){.r = 0., .g = 0., .b = 0.}; return RGB; }
#fins's answer has an overflow issue on Arduio as you turn the saturation down. Here it is with some values converted to int to prevent that. typedef struct RgbColor { unsigned char r; unsigned char g; unsigned char b; } RgbColor; typedef struct HsvColor { unsigned char h; unsigned char s; unsigned char v; } HsvColor; RgbColor HsvToRgb(HsvColor hsv) { RgbColor rgb; unsigned char region, p, q, t; unsigned int h, s, v, remainder; if (hsv.s == 0) { rgb.r = hsv.v; rgb.g = hsv.v; rgb.b = hsv.v; return rgb; } // converting to 16 bit to prevent overflow h = hsv.h; s = hsv.s; v = hsv.v; region = h / 43; remainder = (h - (region * 43)) * 6; p = (v * (255 - s)) >> 8; q = (v * (255 - ((s * remainder) >> 8))) >> 8; t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8; switch (region) { case 0: rgb.r = v; rgb.g = t; rgb.b = p; break; case 1: rgb.r = q; rgb.g = v; rgb.b = p; break; case 2: rgb.r = p; rgb.g = v; rgb.b = t; break; case 3: rgb.r = p; rgb.g = q; rgb.b = v; break; case 4: rgb.r = t; rgb.g = p; rgb.b = v; break; default: rgb.r = v; rgb.g = p; rgb.b = q; break; } return rgb; } HsvColor RgbToHsv(RgbColor rgb) { HsvColor hsv; unsigned char rgbMin, rgbMax; rgbMin = rgb.r < rgb.g ? (rgb.r < rgb.b ? rgb.r : rgb.b) : (rgb.g < rgb.b ? rgb.g : rgb.b); rgbMax = rgb.r > rgb.g ? (rgb.r > rgb.b ? rgb.r : rgb.b) : (rgb.g > rgb.b ? rgb.g : rgb.b); hsv.v = rgbMax; if (hsv.v == 0) { hsv.h = 0; hsv.s = 0; return hsv; } hsv.s = 255 * ((long)(rgbMax - rgbMin)) / hsv.v; if (hsv.s == 0) { hsv.h = 0; return hsv; } if (rgbMax == rgb.r) hsv.h = 0 + 43 * (rgb.g - rgb.b) / (rgbMax - rgbMin); else if (rgbMax == rgb.g) hsv.h = 85 + 43 * (rgb.b - rgb.r) / (rgbMax - rgbMin); else hsv.h = 171 + 43 * (rgb.r - rgb.g) / (rgbMax - rgbMin); return hsv; }
this should be on here: it works anyway. And it looks good compared to the above ones. hlsl code float3 Hue(float H) { half R = abs(H * 6 - 3) - 1; half G = 2 - abs(H * 6 - 2); half B = 2 - abs(H * 6 - 4); return saturate(half3(R,G,B)); } half4 HSVtoRGB(in half3 HSV) { return half4(((Hue(HSV.x) - 1) * HSV.y + 1) * HSV.z,1); } float3 is 16 bit precision vector3 data type, i.e. float3 hue() is returns a data type (x,y,z) e.g. (r,g,b), half is same with half precision, 8bit, a float4 is (r,g,b,a) 4 values.
This isn't C, but it's certainly does work. All the other methods I see here work by casing everything into parts of a hexagon, and approximating "angles" from that. By instead starting with a different equation using cosines, and solving for h s and v, you get a lot nicer relationship between hsv and rgb, and tweening becomes smoother (at the cost of it being way slower). Assume everything is floating point. If r g and b go from 0 to 1, h goes from 0 to 2pi, v goes from 0 to 4/3, and s goes from 0 to 2/3. The following code is written in Lua. It's easily translatable into anything else. local hsv do hsv ={} local atan2 =math.atan2 local cos =math.cos local sin =math.sin function hsv.fromrgb(r,b,g) local c=r+g+b if c<1e-4 then return 0,2/3,0 else local p=2*(b*b+g*g+r*r-g*r-b*g-b*r)^0.5 local h=atan2(b-g,(2*r-b-g)/3^0.5) local s=p/(c+p) local v=(c+p)/3 return h,s,v end end function hsv.torgb(h,s,v) local r=v*(1+s*(cos(h)-1)) local g=v*(1+s*(cos(h-2.09439)-1)) local b=v*(1+s*(cos(h+2.09439)-1)) return r,g,b end function hsv.tween(h0,s0,v0,h1,s1,v1,t) local dh=(h1-h0+3.14159)%6.28318-3.14159 local h=h0+t*dh local s=s0+t*(s1-s0) local v=v0+t*(v1-v0) return h,s,v end end
GLSL Shader version based on Patapoms answer: vec3 HSV2RGB( vec3 hsv ) { hsv.x = mod( 100.0 + hsv.x, 1.0 ); // Ensure [0,1[ float HueSlice = 6.0 * hsv.x; // In [0,6[ float HueSliceInteger = floor( HueSlice ); float HueSliceInterpolant = HueSlice - HueSliceInteger; // In [0,1[ for each hue slice vec3 TempRGB = vec3( hsv.z * (1.0 - hsv.y), hsv.z * (1.0 - hsv.y * HueSliceInterpolant), hsv.z * (1.0 - hsv.y * (1.0 - HueSliceInterpolant)) ); float IsOddSlice = mod( HueSliceInteger, 2.0 ); // 0 if even (slices 0, 2, 4), 1 if odd (slices 1, 3, 5) float ThreeSliceSelector = 0.5 * (HueSliceInteger - IsOddSlice); // (0, 1, 2) corresponding to slices (0, 2, 4) and (1, 3, 5) vec3 ScrollingRGBForEvenSlices = vec3( hsv.z, TempRGB.zx ); // (V, Temp Blue, Temp Red) for even slices (0, 2, 4) vec3 ScrollingRGBForOddSlices = vec3( TempRGB.y, hsv.z, TempRGB.x ); // (Temp Green, V, Temp Red) for odd slices (1, 3, 5) vec3 ScrollingRGB = mix( ScrollingRGBForEvenSlices, ScrollingRGBForOddSlices, IsOddSlice ); float IsNotFirstSlice = clamp( ThreeSliceSelector, 0.0,1.0 ); // 1 if NOT the first slice (true for slices 1 and 2) float IsNotSecondSlice = clamp( ThreeSliceSelector-1.0, 0.0,1. ); // 1 if NOT the first or second slice (true only for slice 2) return mix( ScrollingRGB.xyz, mix( ScrollingRGB.zxy, ScrollingRGB.yzx, IsNotSecondSlice ), IsNotFirstSlice ); // Make the RGB rotate right depending on final slice index }
I'm not C++ developer so I will not provide code. But I can provide simple hsv2rgb algorithm (rgb2hsv here) which I currently discover - I update wiki with description: HSV and HLS. Main improvement is that I carefully observe r,g,b as hue functions and introduce simpler shape function to describe them (without loosing accuracy). The Algorithm - on input we have: h (0-255), s (0-255), v(0-255) r = 255*f(5), g = 255*f(3), b = 255*f(1) We use function f described as follows f(n) = v/255 - (v/255)*(s/255)*max(min(k,4-k,1),0) where (mod can return fraction part; k is floating point number) k = (n+h*360/(255*60)) mod 6; Here are snippets/PoV in SO in JS: HSV and HSL
Here is an online converter with an article after explaining all the algorithms for color conversion. You probably would prefer a ready-made C version but it should not be long to apply and it could help other people trying to do the same in another language or with another color space.
Here's one which i just wrote this morning based on pretty much the same math as above: /* math adapted from: http://www.rapidtables.com/convert/color/rgb-to-hsl.htm * reasonably optimized for speed, without going crazy */ void rgb_to_hsv (int r, int g, int b, float *r_h, float *r_s, float *r_v) { float rp, gp, bp, cmax, cmin, delta, l; int cmaxwhich, cminwhich; rp = ((float) r) / 255; gp = ((float) g) / 255; bp = ((float) b) / 255; //debug ("rgb=%d,%d,%d rgbprime=%f,%f,%f", r, g, b, rp, gp, bp); cmax = rp; cmaxwhich = 0; /* faster comparison afterwards */ if (gp > cmax) { cmax = gp; cmaxwhich = 1; } if (bp > cmax) { cmax = bp; cmaxwhich = 2; } cmin = rp; cminwhich = 0; if (gp < cmin) { cmin = gp; cminwhich = 1; } if (bp < cmin) { cmin = bp; cminwhich = 2; } //debug ("cmin=%f,cmax=%f", cmin, cmax); delta = cmax - cmin; /* HUE */ if (delta == 0) { *r_h = 0; } else { switch (cmaxwhich) { case 0: /* cmax == rp */ *r_h = HUE_ANGLE * (fmod ((gp - bp) / delta, 6)); break; case 1: /* cmax == gp */ *r_h = HUE_ANGLE * (((bp - rp) / delta) + 2); break; case 2: /* cmax == bp */ *r_h = HUE_ANGLE * (((rp - gp) / delta) + 4); break; } if (*r_h < 0) *r_h += 360; } /* LIGHTNESS/VALUE */ //l = (cmax + cmin) / 2; *r_v = cmax; /* SATURATION */ /*if (delta == 0) { *r_s = 0; } else { *r_s = delta / (1 - fabs (1 - (2 * (l - 1)))); }*/ if (cmax == 0) { *r_s = 0; } else { *r_s = delta / cmax; } //debug ("rgb=%d,%d,%d ---> hsv=%f,%f,%f", r, g, b, *r_h, *r_s, *r_v); } void hsv_to_rgb (float h, float s, float v, int *r_r, int *r_g, int *r_b) { if (h > 360) h -= 360; if (h < 0) h += 360; h = CLAMP (h, 0, 360); s = CLAMP (s, 0, 1); v = CLAMP (v, 0, 1); float c = v * s; float x = c * (1 - fabsf (fmod ((h / HUE_ANGLE), 2) - 1)); float m = v - c; float rp, gp, bp; int a = h / 60; //debug ("h=%f, a=%d", h, a); switch (a) { case 0: rp = c; gp = x; bp = 0; break; case 1: rp = x; gp = c; bp = 0; break; case 2: rp = 0; gp = c; bp = x; break; case 3: rp = 0; gp = x; bp = c; break; case 4: rp = x; gp = 0; bp = c; break; default: // case 5: rp = c; gp = 0; bp = x; break; } *r_r = (rp + m) * 255; *r_g = (gp + m) * 255; *r_b = (bp + m) * 255; //debug ("hsv=%f,%f,%f, ---> rgb=%d,%d,%d", h, s, v, *r_r, *r_g, *r_b); }
I created a possibly faster implementation by using 0-1 range for RGBS and V and 0-6 range for Hue (avoiding the division), and grouping the cases into two categories: #include <math.h> #include <float.h> void fromRGBtoHSV(float rgb[], float hsv[]) { // for(int i=0; i<3; ++i) // rgb[i] = max(0.0f, min(1.0f, rgb[i])); hsv[0] = 0.0f; hsv[2] = max(rgb[0], max(rgb[1], rgb[2])); const float delta = hsv[2] - min(rgb[0], min(rgb[1], rgb[2])); if (delta < FLT_MIN) hsv[1] = 0.0f; else { hsv[1] = delta / hsv[2]; if (rgb[0] >= hsv[2]) { hsv[0] = (rgb[1] - rgb[2]) / delta; if (hsv[0] < 0.0f) hsv[0] += 6.0f; } else if (rgb[1] >= hsv[2]) hsv[0] = 2.0f + (rgb[2] - rgb[0]) / delta; else hsv[0] = 4.0f + (rgb[0] - rgb[1]) / delta; } } void fromHSVtoRGB(const float hsv[], float rgb[]) { if(hsv[1] < FLT_MIN) rgb[0] = rgb[1] = rgb[2] = hsv[2]; else { const float h = hsv[0]; const int i = (int)h; const float f = h - i; const float p = hsv[2] * (1.0f - hsv[1]); if (i & 1) { const float q = hsv[2] * (1.0f - (hsv[1] * f)); switch(i) { case 1: rgb[0] = q; rgb[1] = hsv[2]; rgb[2] = p; break; case 3: rgb[0] = p; rgb[1] = q; rgb[2] = hsv[2]; break; default: rgb[0] = hsv[2]; rgb[1] = p; rgb[2] = q; break; } } else { const float t = hsv[2] * (1.0f - (hsv[1] * (1.0f - f))); switch(i) { case 0: rgb[0] = hsv[2]; rgb[1] = t; rgb[2] = p; break; case 2: rgb[0] = p; rgb[1] = hsv[2]; rgb[2] = t; break; default: rgb[0] = t; rgb[1] = p; rgb[2] = hsv[2]; break; } } } } For 0-255 range just * 255.0f + 0.5f and assign it to an unsigned char (or divide by 255.0 to get the opposite).
// This pair of functions convert HSL to RGB and vice-versa. // It's pretty optimized for execution speed typedef unsigned char BYTE typedef struct _RGB { BYTE R; BYTE G; BYTE B; } RGB, *pRGB; typedef struct _HSL { float H; // color Hue (0.0 to 360.0 degrees) float S; // color Saturation (0.0 to 1.0) float L; // Luminance (0.0 to 1.0) float V; // Value (0.0 to 1.0) } HSL, *pHSL; float *fMin (float *a, float *b) { return *a <= *b? a : b; } float *fMax (float *a, float *b) { return *a >= *b? a : b; } void RGBtoHSL (pRGB rgb, pHSL hsl) { // See https://en.wikipedia.org/wiki/HSL_and_HSV // rgb->R, rgb->G, rgb->B: [0 to 255] float r = (float) rgb->R / 255; float g = (float) rgb->G / 255; float b = (float) rgb->B / 255; float *min = fMin(fMin(&r, &g), &b); float *max = fMax(fMax(&r, &g), &b); float delta = *max - *min; // L, V [0.0 to 1.0] hsl->L = (*max + *min)/2; hsl->V = *max; // Special case for H and S if (delta == 0) { hsl->H = 0.0f; hsl->S = 0.0f; } else { // Special case for S if((*max == 0) || (*min == 1)) hsl->S = 0; else // S [0.0 to 1.0] hsl->S = (2 * *max - 2*hsl->L)/(1 - fabsf(2*hsl->L - 1)); // H [0.0 to 360.0] if (max == &r) hsl->H = fmod((g - b)/delta, 6); // max is R else if (max == &g) hsl->H = (b - r)/delta + 2; // max is G else hsl->H = (r - g)/delta + 4; // max is B hsl->H *= 60; } } void HSLtoRGB (pHSL hsl, pRGB rgb) { // See https://en.wikipedia.org/wiki/HSL_and_HSV float a, k, fm1, fp1, f1, f2, *f3; // L, V, S: [0.0 to 1.0] // rgb->R, rgb->G, rgb->B: [0 to 255] fm1 = -1; fp1 = 1; f1 = 1-hsl->L; a = hsl->S * *fMin(&hsl->L, &f1); k = fmod(0 + hsl->H/30, 12); f1 = k - 3; f2 = 9 - k; f3 = fMin(fMin(&f1, &f2), &fp1) ; rgb->R = (BYTE) (255 * (hsl->L - a * *fMax(f3, &fm1))); k = fmod(8 + hsl->H/30, 12); f1 = k - 3; f2 = 9 - k; f3 = fMin(fMin(&f1, &f2), &fp1) ; rgb->G = (BYTE) (255 * (hsl->L - a * *fMax(f3, &fm1))); k = fmod(4 + hsl->H/30, 12); f1 = k - 3; f2 = 9 - k; f3 = fMin(fMin(&f1, &f2), &fp1) ; rgb->B = (BYTE) (255 * (hsl->L - a * *fMax(f3, &fm1))); }
This link has formulas for what you want. Then it's a matter of performance (numerical techniques) if you want it fast.