Нарисуйте насыщенность / градиент яркости
Я пытаюсь нарисовать следующее градиентное изображение на холсте, но в правом нижнем углу есть проблема.
Желаемый эффект:
Токовый выход:
Я, наверное, упускаю что-то действительно простое здесь.
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>
2 ответа
Использование градиентов.
Вы можете заставить графический процессор выполнять большую часть обработки за вас. multiply
эффективно умножает два цвета для каждого пикселя. Так что для каждого канала и каждого пикселя colChanDest = Math.floor(colChanDest * (colChanSrc / 255))
это делается с помощью массивно параллельной вычислительной мощности графического процессора, а не с низким общим потоком, работающим на одном ядре (контекст выполнения JavaScript).
Два градиента
Одним из них является фон от белого до черного сверху вниз
var gradB = ctx.createLinearGradient(0,0,0,255); gradB.addColorStop(0,"white"); gradB.addColorStop(1,"black");
Другой - это оттенок, который исчезает с прозрачного на непрозрачный слева направо
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)``);
Обратите внимание, что приведенные выше цитаты строк некорректно отображаются на SO, поэтому я просто удвоил их, чтобы показать, используйте одинарную кавычку, как в демонстрационном фрагменте.
Rendering
Затем слой два, сначала фон (серая шкала), затем с помощью составной операции "умножение"
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";
Работает только для Хюэ
Важно, что цвет (оттенок) является чистым значением цвета, вы не можете использовать случайное значение RGB. Если у вас есть выбранное значение rgb, вам нужно извлечь значение оттенка из rgb.
Следующая функция преобразует значение RGB в цвет HSL
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;
}
Положил все это вместе
Пример отображает образец случайного красного, зеленого и синего значений каждые 3 секунды.
Обратите внимание, что этот пример использует Balel, чтобы он работал на 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();
Чтобы рассчитать цвет по координатам x и y, вам нужен рассчитанный оттенок, затем насыщенность и значение для получения цвета hsv (ПРИМЕЧАНИЕ. Hsl и hsv - это разные цветовые модели)
// 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))
);
Функция для получения значений r,g,b для цвета h,s,v.
/* Function taken from datGUI.js
Web site https://workshop.chromeexperiments.com/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
};
}
Мне пришлось сделать это с помощью OpenGL, и ответ Blindman67 был единственным ресурсом, который я нашел. В конце концов, я сделал это, нарисовав 3 прямоугольника друг над другом.
- Все белое
- От прозрачного красного до непрозрачного красного по горизонтали
- От прозрачного черного до непрозрачного черного по вертикали
Обновление: в предыдущем примере я только создал градиент для красного. Я также могу использовать тот же метод для создания зеленых и синих градиентов после небольшой модификации, но я не могу использовать его для создания градиентов для случайных оттенков. Красный, Зеленый и Синий легко, потому что пока один канал 255
другие два имеют одинаковое значение. Для случайного оттенка, например, 140°, это не так. H=140
переводит на rgb(0,255,85)
, Красный и Синий не могут иметь равные значения. Это требует другого и более сложного расчета.
Ответ Blindman67 решает эту проблему. Используя встроенные градиенты, вы можете легко создавать градиенты для любого случайного оттенка: jsfiddle. Но, будучи очень любопытным человеком, я все равно хотел сделать это трудным путем, и вот оно:
(По сравнению с Blindman67 это очень медленно...)
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.com/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" />
Хорошо, я понял, в чем проблема. В то время как вертикальный диапазон всегда между [0,255]
горизонтальный диапазон между [0,r]
, Так g
а также b
не может быть больше чем 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>