I'm trying to cycle through the RGB spectrum smoothly, but so far I've only been able to make code that either goes through 768 colors (256*3) smoothly or goes through 16777216 colors (256^3) with discrete jumps.
Here's the code that runs smoothly:
void loop() {
setColor(255,0,0);
setColor(255,255,0);
setColor(0,255,0);
setColor(0,255,255);
setColor(0,0,255);
setColor(255,0,255);
}
void setColor(int red, int green, int blue) {
while ( r != red ) {
if ( r < red ) r += 1;
if ( r > red ) r -= 1;
_setColor();
delay(10);
}
while ( g != green){
if ( g < green ) g += 1;
if ( g > green ) g -= 1;
_setColor();
delay(10);
}
while ( b != blue){
if ( b < blue ) b += 1;
if ( b > blue ) b -= 1;
_setColor();
delay(10);
}
}
void _setColor() {
analogWrite(redPin, r);
analogWrite(greenPin, g);
analogWrite(bluePin, b);
}
Here's the code that runs through every RGB value:
void loop() {
for (r = 0; r <= 255; r++) {
for (g = 0; g <= 255; g++) {
for (b = 0; b <= 255; ++) {
analogWrite(redPin, r);
analogWrite(greenPin, g);
analogWrite(bluePin, b);
}
}
}
The smooth code will never combine values in between the max/min of the colors, i.e. I never get anything like [128,64,72], only outputs like [255,64,0] or [0,0,72].
The discrete code runs through every blue value, then increases the green value, and starts back at 0 for blue, i.e. [0,0,254] -> [0,0,255] -> [0,1,0] -> [0,1,1] which creates discrete jumps.
I'm trying to get a smooth cycle which goes through every possible RGB value, is that even possible?
Sure you can visit every point in a 256^3 space continuously.
The easiest way is to start with a line, then turn a line into a plane, then turn a plane into a cube.
struct simple_generator {
int current = 0;
int min = 0;
int max = 255;
int direction = 1;
bool advance() // returns false iff we hit the end
{
if (current + direction > max || current+direction < min) {
direction = -direction;
return false;
}
current += direction;
return true;
}
};
now let's make a generator from this.
template<std::size_t N>
struct shape_generator {
simple_generator state[N];
int operator[](std::size_t i) const { return state[i].current; }
bool advance() {
for (std::size_t i = 0; i < N; ++i) {
if (state[i].advance())
return true;
}
return false;
}
};
now what this does it advances the first simple generator until it overflows (which causes the generator stand still and reverse directions). If it overflows, it "recursively" advances the next one.
If every generator overflows, it returns false. Otherwise it returns true.
This will generate a pretty boring curve, as it mostly looks like "blue goes to top, then back down, back to top, and back down". Only after many cycles does any green show up. And only after many cycles of green does any red show up.
A fancier one would use an approximation of a real space-filling curve, like the Hilbert curve. But that should work
Live example with a max of 5, because running it for 256^3 elements seems rude.
Related
The idea is to use grabcut (OpenCV) to detect the image inside a rectangle and create a geometry with Direct2D.
My test image is this:
After performing the grab cut, resulting in this image:
the idea is to outline it. I can use an opacity brush to exclude it from the background but I want to use a geometric brush in order to be able to append/widen/combine geometries on it like all other selections in my editor (polygon, lasso, rectangle, etc).
If I apply the convex hull algorithm to the points, I get this:
Which of course is not desired for my case. How do I outline the image?
After getting the image from the grabcut, I keep the points based on luminance:
DWORD* pixels = ...
for (UINT y = 0; y < he; y++)
{
for (UINT x = 0; x < wi; x++)
{
DWORD& col = pixels[y * wi + x];
auto lumthis = lum(col);
if (lumthis > Lum_Threshold)
{
points.push_back({x,y});
}
}
}
Then I sort the points on Y and X:
std::sort(points.begin(), points.end(), [](D2D1_POINT_2F p1, D2D1_POINT_2F p2) -> bool
{
if (p1.y < p2.y)
return true;
if ((int)p1.y == (int)p2.y && p1.x < p2.x)
return true;
return false;
});
Then, for each line (traversing the above point array from top Y to bototm Y) I create "groups" for each line:
struct SECTION
{
float left = 0, right = 0;
};
auto findgaps = [](D2D1_POINT_2F* p,size_t n) -> std::vector<SECTION>
{
std::vector<SECTION> j;
SECTION* jj = 0;
for (size_t i = 0; i < n; i++)
{
if (i == 0)
{
SECTION jp;
jp.left = p[i].x;
jp.right = p[i].x;
j.push_back(jp);
jj = &j[j.size() - 1];
continue;
}
if ((p[i].x - jj->right) < 1.5f)
{
jj->right = p[i].x;
}
else
{
SECTION jp;
jp.left = p[i].x;
jp.right = p[i].x;
j.push_back(jp);
jj = &j[j.size() - 1];
}
}
return j;
};
I'm stuck at this point. I know that from an arbitrary set of points many polygons are possible, but in my case the points have defined what's "left" and what's "right". How would I proceed from here?
For anyone interested, the solution is OpenCV contours. Working example here.
I'm trying to render the Mandelbrot set in color and make it look good. I'm want to replicate the image from the Wikipedia page. The Wikipedia image also included an Ultra Fractal 3 parameter file.
mandelZoom00MandelbrotSet {
fractal:
title="mandel zoom 00 mandelbrot set" width=2560 height=1920 layers=1
credits="WolfgangBeyer;8/21/2005"
layer:
method=multipass caption="Background" opacity=100
mapping:
center=-0.7/0 magn=1.3
formula:
maxiter=50000 filename="Standard.ufm" entry="Mandelbrot" p_start=0/0
p_power=2/0 p_bailout=10000
inside:
transfer=none
outside:
density=0.42 transfer=log filename="Standard.ucl" entry="Smooth"
p_power=2/0 p_bailout=128.0
gradient:
smooth=yes rotation=29 index=28 color=6555392 index=92 color=13331232
index=196 color=16777197 index=285 color=43775 index=371 color=3146289
opacity:
smooth=no index=0 opacity=255
}
I've been trying to decipher this file and write a program to reproduce the Wikipedia image. This is my best attempt:
This is part of the renderer. It's kind of messy because I was fiddling around and rewriting large chunks trying to figure this thing out.
using real = double;
using integer = long long;
struct complex {
real r, i;
};
using grey = unsigned char;
struct color {
grey r, g, b;
};
struct real_color {
real r, g, b;
};
grey real_to_grey(real r) {
// converting to srgb didn't help much
return std::min(std::max(std::round(r), real(0.0)), real(255.0));
}
color real_to_grey(real_color c) {
return {real_to_grey(c.r), real_to_grey(c.g), real_to_grey(c.b)};
}
real lerp(real a, real b, real t) {
return std::min(std::max(t * (b - a) + a, real(0.0)), real(255.0));
}
real_color lerp(real_color a, real_color b, real t) {
return {lerp(a.r, b.r, t), lerp(a.g, b.g, t), lerp(a.b, b.b, t)};
}
complex plus(complex a, complex b) {
return {a.r + b.r, a.i + b.i};
}
complex square(complex n) {
return {n.r*n.r - n.i*n.i, real{2.0} * n.r * n.i};
}
complex next(complex z, complex c) {
return plus(square(z), c);
}
real magnitude2(complex n) {
return n.r*n.r + n.i*n.i;
}
real magnitude(complex n) {
return std::sqrt(magnitude2(n));
}
color lerp(real_color a, real_color b, real t) {
return real_to_grey(lerp(a, b, t));
}
struct result {
complex zn;
integer n;
};
result mandelbrot(complex c, integer iterations, real bailout) {
complex z = {real{0.0}, real{0.0}};
integer n = 0;
real bailout2 = bailout * bailout;
for (; n < iterations && magnitude2(z) <= bailout2; ++n) {
z = next(z, c);
}
return {z, n};
}
struct table_row {
real index;
real_color color;
};
real invlerp(real value, real min, real max) {
return (value - min) / (max - min);
}
color lerp(table_row a, table_row b, real index) {
return lerp(a.color, b.color, invlerp(index, a.index, b.index));
}
color mandelbrot_color(complex c, integer iterations, real bailout) {
const result res = mandelbrot(c, iterations, bailout);
if (res.n == iterations) {
// in the set
return {0, 0, 0};
} else {
table_row table[] = {
// colors and indicies from gradient section
{28.0*0.1, {0x00, 0x07, 0x64}},
{92.0*0.1, {0x20, 0x6B, 0xCB}},
{196.0*0.1, {0xED, 0xFF, 0xFF}},
{285.0*0.1, {0xFF, 0xAA, 0x00}},
{371.0*0.1, {0x31, 0x02, 0x30}},
// interpolate towards black as we approach points that are in the set
{real(iterations), {0, 0, 0}}
};
// it should be smooth, but it's not
const real smooth = res.n + real{1.0} - std::log(std::log2(magnitude(res.zn)));
// I know what a for-loop is, I promise
if (smooth < table[1].index) {
return lerp(table[0], table[1], smooth);
} else if (table[1].index <= smooth && smooth < table[2].index) {
return lerp(table[1], table[2], smooth);
} else if (table[2].index <= smooth && smooth < table[3].index) {
return lerp(table[2], table[3], smooth);
} else if (table[3].index <= smooth && smooth < table[4].index) {
return lerp(table[3], table[4], smooth);
} else {
return lerp(table[4], table[5], smooth);
}
}
}
The colors from the gradient section are in a table in mandelbrot_color. The indices from the gradient section are also in the table but I multiplied them by 0.1. The colors look completely off if I don't multiply by 0.1.
The formula section has maxiter=50000 and p_bailout=10000. These are iterations and bailout in the code. I don't know what p_start=0/0 p_power=2/0 means. I don't know why a different bailout is mentioned in the outside section and I don't know what density=0.42, transfer=none, transfer=log means. The gradient section also mentions rotation=29 but I don't understand how a gradient could be rotated.
The reason I am asking this question is that I don't like the white bands around my image (I'd prefer a smooth while glow like in the Wikipedia image). I also don't like the dark purple skin caused by interpolating towards black (the last row in the table in mandelbrot_color). If we remove that row, we end up with a deep blue skin.
I suspect that there is some kind of mapping from the indices in the gradient section to iteration counts. Maybe * 0.1 is an approximation of that mapping that works some of the time. Might have something to do with transfer, density or rotation. Leave a comment if you would like me to post the whole program. It depends on stb_image_write (a single header image writing library).
As a side note, I've I clean up this code and chuck it into a fragment shader, will it likely be faster (generally speaking) than running multithreaded on the CPU?
Currently I am working on paralizing an image processing algorithm to extract edges from a given image. I recently started with code parallelizing.
Anyway a part of the program requires me to compute the histogram of the image and count the number of occurding pixels from 1 to its maximum gradient Intensity.
I have implemented it as the following:
tbb::concurrent_vector<double> histogram(32768);
tbb::parallel_for(tbb::blocked_range<size_t>(1, width - 1),
[&](const tbb::blocked_range<size_t>& r)
{
unsigned int idx;
for (size_t w = r.begin(); w != r.end(); ++w) //1 to (width -1)
{
for (size_t h = 1; h < height - 1; ++h)
{
idx = h * width + w;
//DO SOME STUFF BEFORE
//Get max gradient intensity
if (pgImg[idx] > maxGradIntensity)
{
maxGradIntensity = pgImg[idx];
}
//Get histogram information
if (pgImg[idx] > 0)
{
tbb::mutex::scoped_lock sync(locked);
++histogram[(int)pgImg[idx]];
++totalGradPixels;
}
}
}
});
histogram.resize(maxGradIntensity);
So the part where it becomes tricky for me is the following:
if (pgImg[idx] > 0)
{
tbb::mutex::scoped_lock sync(locked);
++histogram[(int)pgImg[idx]];
++totalGradPixels;
}
How can I avoid using tbb::mutex? I had no luck with setting the vector to tbb::atomic. Maybe I did something wrong there. Any help on this topic would be appreciated.
I currently have this thats fading between 2 set colours:
for(int i=0;i<nLEDs;i++){
a = (255 / 100) * (incomingByte * sensitivity);
r = (r * 7 + a + 7) / 8;
g = (g * 7 + (255 - a) + 7) / 8;
b = 0;
FTLEDColour col = { r , g , b };
led.setLED(i, col);
}
But now im trying to allow users to enter their own colours:
// > Colour fade, Start colour
int colFade1Red = 0;
int colFade1Green = 255;
int colFade1Blue = 0;
// > Colour fade, End colour
int colFade2Red = 255;
int colFade2Green = 0;
int colFade2Blue = 0;
int fadeAm = 7; // Fade speed
with the fading code:
void ColourFade(){
for(int i=0;i<nLEDs;i++){
r = ctest(colFade1Red, colFade2Red, r);
g = ctest(colFade1Green, colFade2Green, g);
b = ctest(colFade1Blue, colFade2Blue, b);
FTLEDColour col = { r , g , b };
led.setLED(i, col);
}
}
int ctest(int col1, int col2, int cur){
int temp = col1 - col2;
if(temp < 0) { temp = -temp; }
int alp = (temp / 100) * (incomingByte * sensitivity);
if(col1 < col2){
return (cur * fadeAm + (col2 - alp) + fadeAm) / (fadeAm +1 );
} else {
return (cur * fadeAm + alp + fadeAm) / (fadeAm +1 );
}
}
But this starts with the Second user colour and fades into pink. How would I fade colours properly?
Also "incomingByte" is a value between 0 and 100, and the code is in a update loop.
Smooth transitions between colours is best done in a different colour space (IMHO).
As an example, to transition from bright red to bright green, do you want to go via bright yellow (around the edge of the colour wheel) or via #808000 (murky yellow) - which is what a straight line interpolation would give you in the RGB domain.
Having done this for my Moodlamp app, I used the HSL colour space. I specified a start colour and end colour, along with a number of steps for the transition to take. That enabled me to calculate how much to adjust H, S and L by at each point in the transition.
Only at the point of using the colour did I convert back to RGB.
You can see the javascript code here (please bear in mind it's the first Javascript I ever wrote, so if it seems non-idiomatic, that's probably why!):
https://github.com/martinjthompson/MoodLamp/blob/master/app/assistants/lamp-assistant.js
It's impossible to fade to pink beacuse you are starting from red and ending with green.
To avoid this kind of mistake I suggest you to write an object oriented code.
If you don't want to write the classes to handle a 3D vectonr you can use the Arduino Tinker Library
I wrote this example for you:
#include <Vect3d.h>
#include <SerialLog.h>
Tinker::Vect3d<float> red(255,0,0);
Tinker::Vect3d<float> green(0,255,0);
Tinker::SerialLog serialLog;
void setup(){
Serial.begin(9600);
serialLog.display("Fade color example");
serialLog.endline();
}
void loop(){
//fade factor computation
const uint32_t t = millis()%10000;
const float cosArg = t/10000.*3.1415*2;
const float fade = abs(cos(cosArg));
//Here's the color computation... as you can see is very easy to do!! :)
Tinker::Vect3d<uint8_t> finalColor(red*fade + green*(1-fade));
//We print the vect3d on the arduino serial port
Tinker::LOG::displayVect3d(finalColor,&serialLog);
serialLog.endline();
delay(500);
}
Which prints the following output on the serial port
Fade color example
V[255;0;0]
V[242;12;0]
V[206;48;0]
V[149;105;0]
V[78;176;0]
V[0;254;0]
V[79;175;0]
V[150;104;0]
V[206;48;0]
V[242;12;0]
V[254;0;0]
V[242;12;0]
V[205;49;0]
V[148;106;0]
V[77;177;0]
V[1;253;0]
V[80;174;0]
V[151;103;0]
hope that this helps :)
uint8_t clrR = abs(255 * cos(<some var that changes in time>));
same for clrB & clrG
I've been working on this for several weeks but have been unable to get my algorithm working properly and i'm at my wits end. Here's an illustration of what i have achieved:
If everything was working i would expect a perfect circle/oval at the end.
My sample points (in white) are recalculated every time a new control point (in yellow) is added. At 4 control points everything looks perfect, again as i add a 5th on top of the 1st things look alright, but then on the 6th it starts to go off too the side and on the 7th it jumps up to the origin!
Below I'll post my code, where calculateWeightForPointI contains the actual algorithm. And for reference- here is the information i'm trying to follow. I'd be so greatful if someone could take a look for me.
void updateCurve(const std::vector<glm::vec3>& controls, std::vector<glm::vec3>& samples)
{
int subCurveOrder = 4; // = k = I want to break my curve into to cubics
// De boor 1st attempt
if(controls.size() >= subCurveOrder)
{
createKnotVector(subCurveOrder, controls.size());
samples.clear();
for(int steps=0; steps<=20; steps++)
{
// use steps to get a 0-1 range value for progression along the curve
// then get that value into the range [k-1, n+1]
// k-1 = subCurveOrder-1
// n+1 = always the number of total control points
float t = ( steps / 20.0f ) * ( controls.size() - (subCurveOrder-1) ) + subCurveOrder-1;
glm::vec3 newPoint(0,0,0);
for(int i=1; i <= controls.size(); i++)
{
float weightForControl = calculateWeightForPointI(i, subCurveOrder, controls.size(), t);
newPoint += weightForControl * controls.at(i-1);
}
samples.push_back(newPoint);
}
}
}
//i = the weight we're looking for, i should go from 1 to n+1, where n+1 is equal to the total number of control points.
//k = curve order = power/degree +1. eg, to break whole curve into cubics use a curve order of 4
//cps = number of total control points
//t = current step/interp value
float calculateWeightForPointI( int i, int k, int cps, float t )
{
//test if we've reached the bottom of the recursive call
if( k == 1 )
{
if( t >= knot(i) && t < knot(i+1) )
return 1;
else
return 0;
}
float numeratorA = ( t - knot(i) );
float denominatorA = ( knot(i + k-1) - knot(i) );
float numeratorB = ( knot(i + k) - t );
float denominatorB = ( knot(i + k) - knot(i + 1) );
float subweightA = 0;
float subweightB = 0;
if( denominatorA != 0 )
subweightA = numeratorA / denominatorA * calculateWeightForPointI(i, k-1, cps, t);
if( denominatorB != 0 )
subweightB = numeratorB / denominatorB * calculateWeightForPointI(i+1, k-1, cps, t);
return subweightA + subweightB;
}
//returns the knot value at the passed in index
//if i = 1 and we want Xi then we have to remember to index with i-1
float knot(int indexForKnot)
{
// When getting the index for the knot function i remember to subtract 1 from i because of the difference caused by us counting from i=1 to n+1 and indexing a vector from 0
return knotVector.at(indexForKnot-1);
}
//calculate the whole knot vector
void createKnotVector(int curveOrderK, int numControlPoints)
{
int knotSize = curveOrderK + numControlPoints;
for(int count = 0; count < knotSize; count++)
{
knotVector.push_back(count);
}
}
Your algorithm seems to work for any inputs I tried it on. Your problem might be a that a control point is not where it is supposed to be, or that they haven't been initialized properly. It looks like there are two control-points, half the height below the bottom left corner.