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.