Javascript画布颜色亮度不平滑变化

时间:2018-04-09 18:54:41

标签: javascript html5-canvas complex-numbers

我正在谈论的内容的完整演示在这里: https://ggodfrey.github.io/ComplexColor/index.html

我正在尝试创建一个可以尝试使用颜色绘制复值函数的网站。使用复数答案的角度确定色调,并且通过取复数答案的大小的对数然后找到小数部分来确定亮度。从那里,我使用一个函数将HSL转换为RGB,然后将其放入我绘制到画布上的Image对象中,允许我在每个像素上绘制。

如上页所示,亮度“级别”在对数从一个整数变为另一个整数的位置之间具有“粗糙”边缘。它应该看起来更像this。这个问题与我实际计算亮度的方式或使用javascript画布有关吗?

  window.onload = function(){

    var EQUATION = '';
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext('2d');
    var x_min = -3;
    var x_max = 3;
    var y_min = -3;
    var y_max = 3;
    var image = ctx.createImageData(canvas.width, canvas.height);

    Complex = function(re, im){
      this.re = re;
      this.im = im;
    }

    Complex.prototype.add = function(other){
      return new Complex(this.re+other.re, this.im+other.im);
    }

    Complex.prototype.multiply = function(other){
      return new Complex(this.re*other.re-other.im*this.im, this.re*other.im+this.im*other.re);
    }

    Complex.prototype.power = function(num){
      var r = this.magnitude();
      var theta = this.angle();
      var a = Math.pow(r, num)*Math.cos(num*theta);
      var b = Math.pow(r, num)*Math.sin(num*theta);
      return new Complex(a, b);
    }

    Complex.prototype.magnitude = function(){
      return Math.pow(Math.pow(this.re, 2) + Math.pow(this.im, 2), 0.5);
    }

    Complex.prototype.angle = function(){
      return Math.atan2(this.im, this.re);
    }

    Complex.prototype.divide = function(other){
      x = new Complex(this.re, this.im);
      y = new Complex(other.re, other.im);
      x = x.multiply(new Complex(other.re, -other.im));
      y = y.multiply(new Complex(other.re, -other.im));
      x = new Complex(x.re/y.re, x.im/y.re);
      return x;
    }

    function hslToRgb(h, s, l){ //NOT MY CODE
      var r, g, b;

      if(s == 0){
        r = g = b = l; // achromatic
      } else {
        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.0);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1/3.0);
      }
      return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
    }

    function evaluate(i, j){
      var z = new Complex(x_min+j*(x_max-x_min)/canvas.width, y_min+i*(y_max-y_min)/canvas.height);
      var num = z.power(2).add(new Complex(-1, 0)).multiply(z.add(new Complex(-2, -1)).power(2));
      var den = z.power(2).add(new Complex(2, 4));
      var end = num.divide(den);
      var color = end.angle()/(2*Math.PI);
      var brightness = end.magnitude();
      brightness = Math.log(brightness)/Math.log(2) % 1;
      return [color, brightness];
    }

    function main(){
      var data = image.data;
      if(EQUATION !== null){
        var count = 0;
        for(var i=0;i<canvas.height;i++){
          for(var j=0;j<canvas.width;j++){

            var c = evaluate(i, j);
            rgb = hslToRgb(c[0], 1, 0.4+c[1]/5);
            var r = rgb[0];
            var g = rgb[1];
            var b = rgb[2];
            var c = count*4;
            data[c] = r;
            data[c+1] = g;
            data[c+2] = b;
            data[c+3] = 255;
            count++;
          }
        }
        image.data = data;
        ctx.putImageData(image, 0, 0);
    }
  }

  main();

  function getMousePos(canvas, evt){
    var rect = canvas.getBoundingClientRect();
    return {x: (evt.clientX-rect.left)/(rect.right-rect.left)*canvas.width,
      y: (evt.clientY-rect.top)/(rect.bottom-rect.top)*canvas.height};
  }

  document.getElementById("submit").addEventListener("mousedown", function(event){
    EQUATION = document.getElementById("equation").innerHTML;
    var x = main();
  })

  document.getElementById("canvas").addEventListener("mousemove", function(event){
    var loc = getMousePos(canvas, event);
    document.getElementById('x').innerHTML = Math.round(loc.x*100)/100;
    document.getElementById('y').innerHTML = Math.round(loc.y*100)/100;
    document.getElementById('brightness').innerHTML = evaluate(loc.y, loc.x)[1];
  })

}
<head>
  <title>Complex Color</title>
  <meta charset="utf-8"></head>
<body>
  <input id="equation" type="text">Type Equation</input><button id="submit">Submit</button><br>
  <canvas id="canvas" style="width:500px;height:500px"></canvas><p> <span id="x"></span>, <span id="y"></span>, <span id="brightness"></span></p>
</body>

2 个答案:

答案 0 :(得分:2)

假设公式是正确的:

  • 增加画布的bitmap resolution 并使用较小的CSS大小来引入平滑 - 或 - 实现手动消除锯齿。这是因为您逐个像素地写入,绕过了抗锯齿。

  • 将饱和度降低至约80%:rgb = hslToRgb(c[0], 0.8, 0.4 + c[1] / 5);。 100%通常会在屏幕上产生过饱和的外观图像。打印虽然使用100%。

var EQUATION = '';
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
var x_min = -3;
var x_max = 3;
var y_min = -3;
var y_max = 3;
var image = ctx.createImageData(canvas.width, canvas.height);

Complex = function(re, im) {
  this.re = re;
  this.im = im;
}

Complex.prototype.add = function(other) {
  return new Complex(this.re + other.re, this.im + other.im);
}

Complex.prototype.multiply = function(other) {
  return new Complex(this.re * other.re - other.im * this.im, this.re * other.im + this.im * other.re);
}

Complex.prototype.power = function(num) {
  var r = this.magnitude();
  var theta = this.angle();
  var a = Math.pow(r, num) * Math.cos(num * theta);
  var b = Math.pow(r, num) * Math.sin(num * theta);
  return new Complex(a, b);
}

Complex.prototype.magnitude = function() {
  return Math.pow(Math.pow(this.re, 2) + Math.pow(this.im, 2), 0.5);
}

Complex.prototype.angle = function() {
  return Math.atan2(this.im, this.re);
}

Complex.prototype.divide = function(other) {
  x = new Complex(this.re, this.im);
  y = new Complex(other.re, other.im);
  x = x.multiply(new Complex(other.re, -other.im));
  y = y.multiply(new Complex(other.re, -other.im));
  x = new Complex(x.re / y.re, x.im / y.re);
  return x;
}

function hslToRgb(h, s, l) { //NOT MY CODE
  var r, g, b;

  if (s == 0) {
    r = g = b = l; // achromatic
  } else {
    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.0);
    g = hue2rgb(p, q, h);
    b = hue2rgb(p, q, h - 1 / 3.0);
  }
  return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}

function evaluate(i, j) {
  var z = new Complex(x_min + j * (x_max - x_min) / canvas.width, y_min + i * (y_max - y_min) / canvas.height);
  var num = z.power(2).add(new Complex(-1, 0)).multiply(z.add(new Complex(-2, -1)).power(2));
  var den = z.power(2).add(new Complex(2, 4));
  var end = num.divide(den);
  var color = end.angle() / (2 * Math.PI);
  var brightness = end.magnitude();
  brightness = Math.log(brightness) / Math.log(2) % 1;
  return [color, brightness];
}

function main() {
  var data = image.data;
  if (EQUATION !== null) {
    var count = 0;
    for (var i = 0; i < canvas.height; i++) {
      for (var j = 0; j < canvas.width; j++) {

        var c = evaluate(i, j);
        rgb = hslToRgb(c[0], 0.8, 0.4 + c[1] / 5);
        var r = rgb[0];
        var g = rgb[1];
        var b = rgb[2];
        var c = count * 4;
        data[c] = r;
        data[c + 1] = g;
        data[c + 2] = b;
        data[c + 3] = 255;
        count++;
      }
    }
    image.data = data;
    ctx.putImageData(image, 0, 0);
  }
}

main();
#canvas {width:500px;height:500px}
<canvas id="canvas" width=1000 height=1000></canvas>

答案 1 :(得分:0)

CSS'widthheight与同名的canvas元素的属性不同 - 它们只是将画布拉伸到给定的大小(另请参阅this answer) 。因此,canvas.width === 300canvas.height === 150是默认值。这种低分辨率会立即产生问题。这是你的相同代码,只是画布'属性设置正确,而不是使用不正确的CSS:

window.onload = function(){

    var EQUATION = '';
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext('2d');
    var x_min = -3;
    var x_max = 3;
    var y_min = -3;
    var y_max = 3;
    var image = ctx.createImageData(canvas.width, canvas.height);

    Complex = function(re, im){
      this.re = re;
      this.im = im;
    }

    Complex.prototype.add = function(other){
      return new Complex(this.re+other.re, this.im+other.im);
    }

    Complex.prototype.multiply = function(other){
      return new Complex(this.re*other.re-other.im*this.im, this.re*other.im+this.im*other.re);
    }

    Complex.prototype.power = function(num){
      var r = this.magnitude();
      var theta = this.angle();
      var a = Math.pow(r, num)*Math.cos(num*theta);
      var b = Math.pow(r, num)*Math.sin(num*theta);
      return new Complex(a, b);
    }

    Complex.prototype.magnitude = function(){
      return Math.pow(Math.pow(this.re, 2) + Math.pow(this.im, 2), 0.5);
    }

    Complex.prototype.angle = function(){
      return Math.atan2(this.im, this.re);
    }

    Complex.prototype.divide = function(other){
      x = new Complex(this.re, this.im);
      y = new Complex(other.re, other.im);
      x = x.multiply(new Complex(other.re, -other.im));
      y = y.multiply(new Complex(other.re, -other.im));
      x = new Complex(x.re/y.re, x.im/y.re);
      return x;
    }

    function hslToRgb(h, s, l){ //NOT MY CODE
      var r, g, b;

      if(s == 0){
        r = g = b = l; // achromatic
      } else {
        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.0);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1/3.0);
      }
      return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
    }

    function evaluate(i, j){
      var z = new Complex(x_min+j*(x_max-x_min)/canvas.width, y_min+i*(y_max-y_min)/canvas.height);
      var num = z.power(2).add(new Complex(-1, 0)).multiply(z.add(new Complex(-2, -1)).power(2));
      var den = z.power(2).add(new Complex(2, 4));
      var end = num.divide(den);
      var color = end.angle()/(2*Math.PI);
      var brightness = end.magnitude();
      brightness = Math.log(brightness)/Math.log(2) % 1;
      return [color, brightness];
    }

    function main(){
      var data = image.data;
      if(EQUATION !== null){
        var count = 0;
        for(var i=0;i<canvas.height;i++){
          for(var j=0;j<canvas.width;j++){

            var c = evaluate(i, j);
            rgb = hslToRgb(c[0], 1, 0.4+c[1]/5);
            var r = rgb[0];
            var g = rgb[1];
            var b = rgb[2];
            var c = count*4;
            data[c] = r;
            data[c+1] = g;
            data[c+2] = b;
            data[c+3] = 255;
            count++;
          }
        }
        image.data = data;
        ctx.putImageData(image, 0, 0);
    }
  }

  main();

  function getMousePos(canvas, evt){
    var rect = canvas.getBoundingClientRect();
    return {x: (evt.clientX-rect.left)/(rect.right-rect.left)*canvas.width,
      y: (evt.clientY-rect.top)/(rect.bottom-rect.top)*canvas.height};
  }

  document.getElementById("submit").addEventListener("mousedown", function(event){
    EQUATION = document.getElementById("equation").innerHTML;
    var x = main();
  })

  document.getElementById("canvas").addEventListener("mousemove", function(event){
    var loc = getMousePos(canvas, event);
    document.getElementById('x').innerHTML = Math.round(loc.x*100)/100;
    document.getElementById('y').innerHTML = Math.round(loc.y*100)/100;
    document.getElementById('brightness').innerHTML = evaluate(loc.y, loc.x)[1];
  })

}
<input id="equation" type="text">Type Equation</input>
<canvas id="canvas" width="500" height="500"></canvas>
<button id="submit">Submit</button><br><span id="x"></span>, <span id="y"></span>, <span id="brightness"></span></p>

之后,如另一个答案所述,提高分辨率和“拉伸更小”(一种超级采样)有助于进一步,但不是核心问题。