global.THREE = require("three"); const canvasSketch = require('canvas-sketch'); const Random = require('canvas-sketch-util/random'); const gradientHeight = 512; const settings = { dimensions: [ 2048, gradientHeight * 2 ] }; const sketch = (props) => { return ({ context, width, height }) => { context.fillStyle = 'white'; context.fillRect(0, 0, width, height); // your stepped color data, to be filled in const colorsAsHexList = [ /* '#ff0000' */ ]; const colorsAsLabList = [ /* [ 100, 0, 0 ] */ ]; const grd = context.createLinearGradient(0, 0, width, 0); colorsAsHexList.forEach((hex, i, list) => { const t = i / (list.length - 1); grd.addColorStop(t, hex); }); context.fillStyle = grd; context.fillRect(0, 0, width, gradientHeight); img = context.createImageData(width, gradientHeight); // draw curve const labPositions = colorsAsLabList.map(lab => { return new THREE.Vector3().fromArray(lab); }); const curve = new THREE.CatmullRomCurve3(labPositions); curve.curveType = 'catmullrom'; // can play with tension to make sharper or softer gradients curve.tension = 0.5; // uncomment to make a seamless gradient that wraps around // curve.closed = true; const samples = getCurveDivisions(curve, img.width, false) .map(p => p.toArray()) for (let y = 0; y < img.height; y++) { for (let x = 0; x < img.width; x++) { const i = x + y * img.width; let Lab = labJitter(samples[x], 0.5); // lab2rgb not implemented here const [ R, G, B ] = YourColorConverter.lab2rgb(Lab); img.data[i * 4 + 0] = R; img.data[i * 4 + 1] = G; img.data[i * 4 + 2] = B; img.data[i * 4 + 3] = 255; } } context.putImageData(img, 0, gradientHeight); }; // Entirely optional, but as we are doing this per-pixel we may as well // add subtle noise to reduce banding. function labJitter (lab, s = 1) { // fudged jittering in L*a*b* space const lw = 100 / 200; const K = 2.624; const [ x, y, z ] = Random.insideSphere(Random.gaussian(0, s * K)) const [ L, a, b ] = lab; return [ L + x * lw, a + y, b + z ]; } function getCurveDivisions (curve, n, spaced = false) { const points = []; for (let i = 0; i < n; i++) { const t = curve.closed ? (i / n) : (i / (n - 1)); let p = spaced ? curve.getPointAt(t) : curve.getPoint(t); points.push(p); } return points; } }; canvasSketch(sketch, settings);