Лучший способ оптимизировать холст для чертежей высокой сложности с помощью React? - PullRequest
1 голос
/ 24 апреля 2020

Я и друг играем с фракталами и хотели создать интерактивный веб-сайт, на котором вы можете изменять значения, которые генерируют фрактал, и вы можете видеть, как он влияет. На тестах с небольшим разрешением сайт работает довольно быстро, но все еще медленно.

drawFractal = () => {
  for (let x = 0; x < this.canvas.width; x++) {
    for (let y = 0; y < this.canvas.height; y++) {
      const belongsToSet = this.checkIfBelongsToMandelbrotSet(x / this.state.magnificationFactor - this.state.panX, y / this.state.magnificationFactor - this.state.panY);
      if (belongsToSet === 0) {
        this.ctx.clearRect(x,y, 1,1);
      } else {
        this.ctx.fillStyle = `hsl(80, 100%, ${belongsToSet}%)`;
        // Draw a colorful pixel
        this.ctx.fillRect(x,y, 1,1);
        }
      }
    }
}

checkIfBelongsToMandelbrotSet = (x,y) => {
  let realComponentOfResult = x;
  let imaginaryComponentOfResult = y;
  // Set max number of iterations
  for (let i = 0; i < this.state.maxIterations; i++) {
    const tempRealComponent = realComponentOfResult * realComponentOfResult - imaginaryComponentOfResult * imaginaryComponentOfResult + x;
    const tempImaginaryComponent = this.state.imaginaryConstant * realComponentOfResult * imaginaryComponentOfResult + y;
    realComponentOfResult = tempRealComponent;
    imaginaryComponentOfResult = tempImaginaryComponent;
    // Return a number as a percentage
    if (realComponentOfResult * imaginaryComponentOfResult > 5) {
      return (i / this.state.maxIterations * 100);
    }
  }
  // Return zero if in set
  return 0;
}

Это алгоритм, который обрабатывает генерацию фрактала. Однако мы перебираем каждый пиксель холста, что довольно неэффективно. В результате весь сайт работает очень медленно. Я хотел спросить, хорошо ли использовать html canvas или есть более эффективные альтернативы? Или я могу оптимизировать функцию drawFractal (), чтобы быть более эффективной? Я понятия не имею, как продолжить с этого момента, так как я неопытный и был бы признателен за любые отзывы!

Ответы [ 2 ]

1 голос
/ 27 апреля 2020

Избегайте операций рисования настолько, насколько можете.

Когда вы делаете fillRect(x, y, 1, 1), браузер должен go переходить с CPU на GPU один раз на пиксель, и это очень неэффективно.

В вашем случае, поскольку вы рисуете каждый пиксель самостоятельно, вы можете просто установить все эти пиксели на ImageBitmap и поместить полное изображение один раз за кадр.

Чтобы улучшить немного изменив настройки цвета, я сгенерировал массив из сотни значений перед этим, вы можете сделать его более детализированным, если вы будете sh.

В вашем Мандельброте могут быть улучшения, я не проверял это, но это будет более подходящим для CodeReview , чем StackOverflow.

Вот простая демонстрация с использованием холста 800x600px:

const state = {
  magnificationFactor: 5000,
  imaginaryConstant: 1,
  maxIterations: 20,
  panX: 1,
  panY: 1
};

const canvas = document.getElementById('canvas');
const width = canvas.width = 800;
const height = canvas.height = 600;
const ctx = canvas.getContext('2d');
// the ImageData on which we will draw
const img = new ImageData( width, height );
// create an Uint32 view so that we can set one pixel in one op
const img_data = new Uint32Array( img.data.buffer );

const drawFractal = () => {
  for (let x = 0; x < width; x++) {
    for (let y = 0; y < height; y++) {
      const belongsToSet = checkIfBelongsToMandelbrotSet(x / state.magnificationFactor - state.panX, y / state.magnificationFactor - state.panY);
      // setthe value in our ImageData's data
      img_data[ y * width + x] = getColor( belongsToSet );
      }
    }
  // only now we paint
  ctx.putImageData( img, 0, 0 );
};

checkIfBelongsToMandelbrotSet = (x,y) => {
  let realComponentOfResult = x;
  let imaginaryComponentOfResult = y;
  // Set max number of iterations
  for (let i = 0; i < state.maxIterations; i++) {
    const tempRealComponent = realComponentOfResult * realComponentOfResult - imaginaryComponentOfResult * imaginaryComponentOfResult + x;
    const tempImaginaryComponent = state.imaginaryConstant * realComponentOfResult * imaginaryComponentOfResult + y;
    realComponentOfResult = tempRealComponent;
    imaginaryComponentOfResult = tempImaginaryComponent;
    // Return a number as a percentage
    if (realComponentOfResult * imaginaryComponentOfResult > 5) {
      return (i / state.maxIterations * 100);
    }
  }
  // Return zero if in set
  return 0;
}
// we generate all the colors at init instead of generating every frame
const colors = Array.from( { length: 100 }, (_,i) => {
  if( !i ) { return 0; }
  return hslToRgb( 80/360, 100/100, i/100 );
} );

function getColor( ratio ) {
  if( ratio === 0 ) { return 0; }
  return colors[ Math.round( ratio ) ];
}

function anim() {
  state.magnificationFactor -= 10;
  drawFractal();
  requestAnimationFrame( anim );
}
requestAnimationFrame( anim );


// original by mjijackson.com
// borrowed from https://stackoverflow.com/a/9493060/3702797
function hslToRgb(h, s, l){
    var r, g, b;

    if(s == 0){
        r = g = b = l; // achromatic
    }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);
    }
    // we want 0xAABBGGRR format
    function toHex( val ) {
      return  Math.round( val * 255 ).toString(16);
    }
    return Number( '0xFF' + toHex(b) + toHex(g) + toHex(r) );
}
<canvas id="canvas"></canvas>
0 голосов
/ 27 апреля 2020

Одним небольшим улучшением производительности будет вызов clearRect только один раз для всего холста на drawFractal вместо того, чтобы индивидуально для тех пикселей, которым для ToSet установлено значение 0:

function drawFractal() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  for (let x = 0; x < canvas.width; x++) {
    for (let y = 0; y < canvas.height; y++) {
      const belongsToSet = checkIfBelongsToMandelbrotSet(x / state.magnificationFactor - state.panX, y / state.magnificationFactor - state.panY);
      if (belongsToSet !== 0) {
        ctx.fillStyle = `hsl(80, 100%, ${belongsToSet}%)`;
        // Draw a colorful pixel
        ctx.fillRect(x, y, 1, 1);
      }
    }
  }
}

function checkIfBelongsToMandelbrotSet(x, y) {
  let realComponentOfResult = x;
  let imaginaryComponentOfResult = y;
  // Set max number of iterations
  for (let i = 0; i < state.maxIterations; i++) {
    const tempRealComponent = realComponentOfResult * realComponentOfResult - imaginaryComponentOfResult * imaginaryComponentOfResult + x;
    const tempImaginaryComponent = state.imaginaryConstant * realComponentOfResult * imaginaryComponentOfResult + y;
    realComponentOfResult = tempRealComponent;
    imaginaryComponentOfResult = tempImaginaryComponent;
    // Return a number as a percentage
    if (realComponentOfResult * imaginaryComponentOfResult > 5) {
      return (i / state.maxIterations * 100);
    }
  }
  // Return zero if in set
  return 0;
}

const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const state = {
  magnificationFactor: 5000,
  imaginaryConstant: 1,
  maxIterations: 32,
  panX: 1,
  panY: 1
}
drawFractal()
<canvas id="c" width=200 height=200></canvas>

Для небольшого холста достаточно просто нарисовать один раз.
, но я проверил локально с большим холстом (1024x1024) с высокими итерациями (> 256) и да, это становится медленным.

Если нужны такие высокие значения, я думаю, что это что-то лучше подходит для бэкэнда, создайте веб-сервис, который будет обрабатывать многие из них параллельно и возвращать изображение (и Конечно, кэширование и CDN значительно улучшат скорость для последовательной загрузки), а затем отобразят изображение на холсте.

...