admin管理员组文章数量:1304188
I'm trying to draw the following gradient image in canvas, but there's a problem in the right bottom.
Desired effect:
Current output:
I'm probably missing something really simple here.
function color(r, g, b) {
var args = Array.prototype.slice.call(arguments);
if (args.length == 1) {
args.push(args[0]);
args.push(args[0]);
} else if (args.length != 3 && args.length != 4) {
return;
}
return "rgb(" + args.join() + ")";
}
function drawPixel(x, y, fill) {
var fill = fill || "black";
context.beginPath();
context.rect(x, y, 1, 1);
context.fillStyle = fill;
context.fill();
context.closePath();
}
var canvas = document.getElementById("primary");
var context = canvas.getContext("2d");
canvas.width = 256;
canvas.height = 256;
for (var x = 0; x < canvas.width; x++) {
for (var y = 0; y < canvas.height; y++) {
var r = 255 - y;
var g = 255 - x - y;
var b = 255 - x - y;
drawPixel(x, y, color(r, g, b));
}
}
#primary {
display: block;
border: 1px solid gray;
}
<canvas id="primary"></canvas>
I'm trying to draw the following gradient image in canvas, but there's a problem in the right bottom.
Desired effect:
Current output:
I'm probably missing something really simple here.
function color(r, g, b) {
var args = Array.prototype.slice.call(arguments);
if (args.length == 1) {
args.push(args[0]);
args.push(args[0]);
} else if (args.length != 3 && args.length != 4) {
return;
}
return "rgb(" + args.join() + ")";
}
function drawPixel(x, y, fill) {
var fill = fill || "black";
context.beginPath();
context.rect(x, y, 1, 1);
context.fillStyle = fill;
context.fill();
context.closePath();
}
var canvas = document.getElementById("primary");
var context = canvas.getContext("2d");
canvas.width = 256;
canvas.height = 256;
for (var x = 0; x < canvas.width; x++) {
for (var y = 0; y < canvas.height; y++) {
var r = 255 - y;
var g = 255 - x - y;
var b = 255 - x - y;
drawPixel(x, y, color(r, g, b));
}
}
#primary {
display: block;
border: 1px solid gray;
}
<canvas id="primary"></canvas>
JSFiddle
Share Improve this question edited Jan 8, 2017 at 10:57 akinuri asked Jan 7, 2017 at 17:57 akinuriakinuri 12k10 gold badges74 silver badges107 bronze badges3 Answers
Reset to default 7Using gradients.
You can get the GPU to do most of the processing for you.The 2D posite operation multiply
effectively multiplies two colours for each pixel. So for each channel and each pixel colChanDest = Math.floor(colChanDest * (colChanSrc / 255))
is done via the massively parallel processing power of the GPU, rather than a lowly shared thread running on a single core (JavaScript execution context).
The two gradients
One is the background White to black from top to bottom
var gradB = ctx.createLinearGradient(0,0,0,255); gradB.addColorStop(0,"white"); gradB.addColorStop(1,"black");
The other is the Hue that fades from transparent to opaque from left to right
var swatchHue var col = "rgba(0,0,0,0)" var gradC = ctx.createLinearGradient(0,0,255,0); gradC.addColorStop(0,``hsla(${hueValue},100%,50%,0)``); gradC.addColorStop(1,``hsla(${hueValue},100%,50%,1)``);
Note the above strings quote are not rendering correctly on SO so I just doubled them to show, use a single quote as done in the demo snippet.
Rendering
Then layer the two, background (gray scale) first, then with posite operation "multiply"
ctx.fillStyle = gradB;
ctx.fillRect(0,0,255,255);
ctx.fillStyle = gradC;
ctx.globalCompositeOperation = "multiply";
ctx.fillRect(0,0,255,255);
ctx.globalCompositeOperation = "source-over";
Only works for Hue
It is important that the color (hue) is a pure colour value, you can not use a random rgb value. If you have a selected rgb value you need to extract the hue value from the rgb.
The following function will convert a RGB value to a HSL colour
function rgbToLSH(red, green, blue, result = {}){
value hue, sat, lum, min, max, dif, r, g, b;
r = red/255;
g = green/255;
b = blue/255;
min = Math.min(r,g,b);
max = Math.max(r,g,b);
lum = (min+max)/2;
if(min === max){
hue = 0;
sat = 0;
}else{
dif = max - min;
sat = lum > 0.5 ? dif / (2 - max - min) : dif / (max + min);
switch (max) {
case r:
hue = (g - b) / dif;
break;
case g:
hue = 2 + ((b - r) / dif);
break;
case b:
hue = 4 + ((r - g) / dif);
break;
}
hue *= 60;
if (hue < 0) {
hue += 360;
}
}
result.lum = lum * 255;
result.sat = sat * 255;
result.hue = hue;
return result;
}
Put it all together
The example renders a swatch for a random red, green, blue value every 3 second.
Note that this example uses Balel so that it will work on IE
var canvas = document.createElement("canvas");
canvas.width = canvas.height = 255;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
function drawSwatch(r, g, b) {
var col = rgbToLSH(r, g, b);
var gradB = ctx.createLinearGradient(0, 0, 0, 255);
gradB.addColorStop(0, "white");
gradB.addColorStop(1, "black");
var gradC = ctx.createLinearGradient(0, 0, 255, 0);
gradC.addColorStop(0, `hsla(${Math.floor(col.hue)},100%,50%,0)`);
gradC.addColorStop(1, `hsla(${Math.floor(col.hue)},100%,50%,1)`);
ctx.fillStyle = gradB;
ctx.fillRect(0, 0, 255, 255);
ctx.fillStyle = gradC;
ctx.globalCompositeOperation = "multiply";
ctx.fillRect(0, 0, 255, 255);
ctx.globalCompositeOperation = "source-over";
}
function rgbToLSH(red, green, blue, result = {}) {
var hue, sat, lum, min, max, dif, r, g, b;
r = red / 255;
g = green / 255;
b = blue / 255;
min = Math.min(r, g, b);
max = Math.max(r, g, b);
lum = (min + max) / 2;
if (min === max) {
hue = 0;
sat = 0;
} else {
dif = max - min;
sat = lum > 0.5 ? dif / (2 - max - min) : dif / (max + min);
switch (max) {
case r:
hue = (g - b) / dif;
break;
case g:
hue = 2 + ((b - r) / dif);
break;
case b:
hue = 4 + ((r - g) / dif);
break;
}
hue *= 60;
if (hue < 0) {
hue += 360;
}
}
result.lum = lum * 255;
result.sat = sat * 255;
result.hue = hue;
return result;
}
function drawRandomSwatch() {
drawSwatch(Math.random() * 255, Math.random() * 255, Math.random() * 255);
setTimeout(drawRandomSwatch, 3000);
}
drawRandomSwatch();
To calculate the colour from the x and y coordinates you need the calculated Hue then the saturation and value to get the hsv colour (NOTE hsl and hsv are different colour models)
// saturation and value are clamped to prevent rounding errors creating wrong colour
var rgbArray = hsv_to_rgb(
hue, // as used to create the swatch
Math.max(0, Math.min(1, x / 255)),
Math.max(0, Math.min(1, 1 - y / 255))
);
Function to get r,g,b values for h,s,v colour.
/* Function taken from datGUI.js
Web site https://workshop.chromeexperiments./examples/gui/#1--Basic-Usage
// h 0-360, s 0-1, and v 0-1
*/
function hsv_to_rgb(h, s, v) {
var hi = Math.floor(h / 60) % 6;
var f = h / 60 - Math.floor(h / 60);
var p = v * (1.0 - s);
var q = v * (1.0 - f * s);
var t = v * (1.0 - (1.0 - f) * s);
var c = [
[v, t, p],
[q, v, p],
[p, v, t],
[p, q, v],
[t, p, v],
[v, p, q]
][hi];
return {
r: c[0] * 255,
g: c[1] * 255,
b: c[2] * 255
};
}
I had to do this with OpenGL, and Blindman67's answer was the only resource I found. In the end, I did it by drawing 3 rectangles on top of each other.
- All white
- Transparent red to opaque red, horizontally
- Transparent black to opaque black, vertically
Update: In the previous example, I've only created the gradient for red. I can also use the same method to create green and blue gradients after a little modification, but I can't use it to create gradients for random hues. Red, Green, and Blue are easy because while the one channel is 255
, other two have the same value. For a random hue, e.g. 140°, that is not the case. H=140
translates to rgb(0,255,85)
. Red and Blue can't have equal values. This requires a different and a more plicated calculation.
Blindman67's answer solves this problem. Using built-in gradients, you can easily create gradients for any random hue: jsfiddle. But being a very curious person, I wanted to do it the hard way anyway, and this is it:
(Compared to Blindman67's, it's very slow...)
JSFiddle
function drawPixel(x, y, fillArray) {
fill = "rgb(" + fillArray.join() + ")" || "black";
context.beginPath();
context.rect(x, y, 1, 1);
context.fillStyle = fill;
context.fill();
}
var canvas = document.getElementById("primary");
var context = canvas.getContext("2d");
var grad1 = [ [255, 255, 255], [0, 0, 0] ]; // brightness
fillPrimary([255, 0, 0]); // initial hue = 0 (red)
$("#secondary").on("input", function() {
var hue = parseInt(this.value, 10);
var clr = hsl2rgb(hue, 100, 50);
fillPrimary(clr);
});
function fillPrimary(rgb) {
var grad2 = [ [255, 255, 255], rgb ]; // saturation
for (var x = 0; x < canvas.width; x++) {
for (var y = 0; y < canvas.height; y++) {
var grad1Change = [
grad1[0][0] - grad1[1][0],
grad1[0][1] - grad1[1][1],
grad1[0][2] - grad1[1][2],
];
var currentGrad1Color = [
grad1[0][0] - (grad1Change[0] * y / 255),
grad1[0][1] - (grad1Change[1] * y / 255),
grad1[0][2] - (grad1Change[2] * y / 255)
];
var grad2Change = [
grad2[0][0] - grad2[1][0],
grad2[0][1] - grad2[1][1],
grad2[0][2] - grad2[1][2],
];
var currentGrad2Color = [
grad2[0][0] - (grad2Change[0] * x / 255),
grad2[0][1] - (grad2Change[1] * x / 255),
grad2[0][2] - (grad2Change[2] * x / 255)
];
var multiplied = [
Math.floor(currentGrad1Color[0] * currentGrad2Color[0] / 255),
Math.floor(currentGrad1Color[1] * currentGrad2Color[1] / 255),
Math.floor(currentGrad1Color[2] * currentGrad2Color[2] / 255),
];
drawPixel(x, y, multiplied);
}
}
}
function hsl2rgb(h, s, l) {
h /= 360;
s /= 100;
l /= 100;
var r, g, b;
if (s == 0) {
r = g = b = l;
} else {
var hue2rgb = function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [
Math.round(r * 255),
Math.round(g * 255),
Math.round(b * 255),
];
}
#primary {
display: block;
border: 1px solid gray;
}
#secondary {
width: 256px;
height: 15px;
margin-top: 15px;
outline: 0;
display: block;
border: 1px solid gray;
box-sizing: border-box;
-webkit-appearance: none;
background-image: linear-gradient(to right, red 0%, yellow 16.66%, lime 33.33%, cyan 50%, blue 66.66%, violet 83.33%, red 100%);
}
#secondary::-webkit-slider-thumb {
-webkit-appearance: none;
height: 25px;
width: 10px;
border-radius: 10px;
background-color: rgb(230, 230, 230);
border: 1px solid gray;
box-shadow: inset 0 0 2px rgba(255, 255, 255, 1), 0 0 2px rgba(255, 255, 255, 1);
}
<script src="https://ajax.googleapis./ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="primary" width="256" height="256"></canvas>
<input type="range" min="0" max="360" step="1" value="0" id="secondary" />
Okay, so I've figured out what the problem is. While the vertical range is always between [0,255]
, horizontal range is between [0,r]
. So g
and b
can't be greater than r
(Duh!).
function color(r, g, b) {
var args = Array.prototype.slice.call(arguments);
if (args.length == 1) {
args.push(args[0]);
args.push(args[0]);
} else if (args.length != 3 && args.length != 4) {
return;
}
return "rgb(" + args.join() + ")";
}
function drawPixel(x, y, fill) {
var fill = fill || "black";
context.beginPath();
context.rect(x, y, 1, 1);
context.fillStyle = fill;
context.fill();
context.closePath();
}
var canvas = document.getElementById("primary");
var context = canvas.getContext("2d");
canvas.width = 256;
canvas.height = 256;
for (var x = 0; x < canvas.width; x++) {
for (var y = 0; y < canvas.height; y++) {
var r = 255 - y;
var g = b = r - Math.floor((x / 255) * r); // tada!
drawPixel(x, y, color(r, g, b));
}
}
#primary {
display: block;
border: 1px solid gray;
}
<canvas id="primary"></canvas>
本文标签: javascriptDraw saturationbrightness gradientStack Overflow
版权声明:本文标题:javascript - Draw saturationbrightness gradient - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741754645a2396040.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论