I tried with the oklab code below and it didn’t seem to help much. Still get the banding with the occasional grays – it just seems to move them around a bit. Let me know if you can improve it.
Use o
to toggle oklab vs rgb.
PImage inputs;
PGraphics grad;
PShader gradShader;
int N = 4;
float strength = 1.;
boolean bShowPoints = true;
boolean bSaveFrame = false;
boolean bOklab = true;
void setup() {
size( 800, 600, P3D );
//fullScreen( P3D, SPAN );
colorMode( HSB, 1, 1, 1, 1 );
grad = createGraphics( width, height, P3D );
gradShader = new PShader( this, gradVert, gradFrag );
initControlPoints();
gradShader.set( "strength", strength );
gradShader.set( "bOklab", bOklab );
noLoop();
}
void initControlPoints() {
inputs = createImage( N, 2, ARGB );
inputs.loadPixels();
for( int i=0; i<N; i++ ) {
int x = (int)random(65536);
int y = (int)random(65536);
color c = color( random(1), random(0.25, 1), random(0.25, 1) );
inputs.pixels[ i ] = (x << 16) | y;
inputs.pixels[ i+N ] = c;
}
inputs.updatePixels();
gradShader.set( "inputs", inputs );
gradShader.set( "N", N );
}
void draw() {
gradShader.set( "bShowPoints", bShowPoints );
grad.beginDraw();
grad.noStroke();
grad.shader( gradShader );
grad.rect( 0, 0, grad.width, grad.height );
grad.resetShader();
grad.endDraw();
image( grad, 0, 0 );
if( bShowPoints ) {
text( str(N)+" "+str(strength), 8, 16 );
text( bOklab ? "oklab" : "rgb", 8, 32 );
}
if( bSaveFrame ) {
String fileName = getClass().getSimpleName() + "-" +
nf(year(),4) + "-" + nf(month(),2) + "-" + nf(day(),2) + "-" +
nf(hour(),2) + "-" + nf(minute(),2) + "-" + nf(second(),2) + ".png";
save( fileName );
println( "saved " + fileName );
bSaveFrame = false;
}
}
void keyPressed() {
if( keyCode == ENTER ) initControlPoints();
else if( keyCode == DOWN ) {
strength /= 1.1;
gradShader.set( "strength", strength );
}
else if( keyCode == UP ) {
strength *= 1.1;
gradShader.set( "strength", strength );
}
else if( keyCode == LEFT ) { N = max(N-1, 1); initControlPoints(); }
else if( keyCode == RIGHT ) { N++; initControlPoints(); }
else if( key == ' ' ) bShowPoints = !bShowPoints;
else if( key == '+' ) bSaveFrame = true;
else if( key == 'o' ) {
bOklab = !bOklab;
gradShader.set( "bOklab", bOklab );
}
redraw();
}
String[] gradVert = {"""
#version 330
uniform mat4 transformMatrix;
in vec4 position;
void main() {
gl_Position = transformMatrix * position;
}
"""};
String[] gradFrag = {"""
#version 330
precision highp float;
uniform vec2 resolution;
uniform sampler2D inputs;
uniform int N;
uniform bool bShowPoints;
uniform bool bOklab;
uniform float strength;
out vec4 fragColor;
// oklab code from https://www.shadertoy.com/view/ttcyRS by IQ
// https://bottosson.github.io/posts/oklab
const mat3 kCONEtoLMS = mat3(
0.4121656120, 0.2118591070, 0.0883097947,
0.5362752080, 0.6807189584, 0.2818474174,
0.0514575653, 0.1074065790, 0.6302613616);
const mat3 kLMStoCONE = mat3(
4.0767245293, -1.2681437731, -0.0041119885,
-3.3072168827, 2.6093323231, -0.7034763098,
0.2307590544, -0.3411344290, 1.7068625689);
void main() {
float aspect = resolution.x/resolution.y;
vec2 uv = gl_FragCoord.xy / resolution.y;
vec3 col = vec3(0.);
float totw = 0.;
for( int i=0; i<N; i++ ) {
vec4 posData = texelFetch( inputs, ivec2( i, 0 ), 0 ) * 255.0;
float x = (posData.a * 256 + posData.r)*aspect;
float y = posData.g * 256 + posData.b;
vec2 pos = vec2( x, y ) / 65536.0;
vec3 c = texelFetch( inputs, ivec2( i, 1 ), 0 ).rgb;
c = pow( c, vec3(2.2) ); // un-gamma correct
if( bOklab ) c = pow( kCONEtoLMS*c, vec3( 1./3. ) );
float d = length( uv - pos );
float w = exp( -d*d*N*strength );
if( w > 0 ) {
col += c * w;
totw += w;
}
}
if( totw > 0. ) col /= totw;
if( bOklab ) col = kLMStoCONE*(col*col*col);
col = pow( col, vec3( 1./2.2 ) ); // gamma correct
if( bShowPoints ) {
for( int i=0; i<N; i++ ) {
vec4 posData = texelFetch( inputs, ivec2( i, 0 ), 0 ) * 255.0;
float x = (posData.a * 256 + posData.r)*aspect;
float y = posData.g * 256 + posData.b;
vec2 pos = vec2( x, y ) / 65536.0;
vec3 c = texelFetch( inputs, ivec2( i, 1 ), 0 ).rgb;
float d = length( uv - pos );
if( d < 0.01 ) col = vec3(1.);
if( d < 0.007 ) col = c;
}
}
fragColor = vec4( col, 1. );
}
"""};
The worst gray-banding seems to happen from red to blue. Maybe we just can’t interpolate nicely across the horseshoe.