spritejs/sprite-core

View on GitHub
src/utils/render.js

Summary

Maintainability
A
1 hr
Test Coverage
// export function drawRadiusBox(context, {x, y, w, h, r}) {
//   // avoid radius larger than width or height
//   r = Math.min(r, Math.floor(Math.min(w, h) / 2));
//   // avoid radius is negative
//   r = Math.max(r, 0);

//   context.beginPath();
//   context.moveTo(x + r, y);
//   context.arcTo(x + w, y, x + w, y + h, r);
//   context.arcTo(x + w, y + h, x, y + h, r);
//   context.arcTo(x, y + h, x, y, r);
//   context.arcTo(x, y, x + w, y, r);
//   context.closePath();
// }

function drawEllipseBorder(ctx, x, y, w, h, pos = 'leftTop') {
  const kappa = 0.5522848,
    ox = (w / 2) * kappa, // control point offset horizontal
    oy = (h / 2) * kappa, // control point offset vertical
    xe = x + w, // x-end
    ye = y + h, // y-end
    xm = x + w / 2, // x-middle
    ym = y + h / 2; // y-middle

  if(pos === 'leftTop') {
    ctx.moveTo(x, ym);
    ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  } else if(pos === 'rightTop') {
    ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  } else if(pos === 'rightBottom') {
    ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  } else if(pos === 'leftBottom') {
    ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  }
}

export function drawRadiusBox(context, [x, y, w, h], radius) {
  if(!radius) {
    context.beginPath();
    context.rect(x, y, w, h);
  } else {
    if(!radius) radius = [0, 0, 0, 0, 0, 0, 0, 0];
    if(typeof radius === 'number') radius = Array(8).fill(radius);

    const [tl0, tl1, tr0, tr1, br0, br1, bl0, bl1] = radius.map((r, i) => {
      if(i % 2) return Math.min(r, h / 2);
      return Math.min(r, w / 2);
    });

    context.beginPath();
    context.moveTo(x, y + tl1);
    drawEllipseBorder(context, x, y, tl0 * 2, tl1 * 2, 'leftTop');
    context.lineTo(x + w - tr0, y);
    drawEllipseBorder(context, x + w - tr0 * 2, y, tr0 * 2, tr1 * 2, 'rightTop');
    context.lineTo(x + w, y + h - br1);
    drawEllipseBorder(context, x + w - br0 * 2, y + h - br1 * 2, br0 * 2, br1 * 2, 'rightBottom');
    context.lineTo(x + bl0, y + h);
    drawEllipseBorder(context, x, y + h - bl1 * 2, bl0 * 2, bl1 * 2, 'leftBottom');
    context.closePath();
  }
}

/* istanbul ignore next  */
function gradientBox(angle, rect) {
  const [x, y, w, h] = rect;

  angle %= 360;
  if(angle < 0) {
    angle += 360;
  }

  let ret = [x, y, x + w, y + h];
  if(angle >= 0 && angle < 90) {
    const tan = Math.tan((Math.PI * angle) / 180);

    let d = tan * w;

    if(d <= h) {
      ret = [x, y, x + w, y + d];
    } else {
      d = h / tan;
      ret = [x, y, x + d, y + h];
    }
  } else if(angle >= 90 && angle < 180) {
    const tan = Math.tan((Math.PI * (angle - 90)) / 180);

    let d = tan * h;

    if(d <= w) {
      ret = [x + w, y, x + w - d, y + h];
    } else {
      d = w / tan;
      ret = [x + w, y, x, y + d];
    }
  } else if(angle >= 180 && angle < 270) {
    const tan = Math.tan((Math.PI * (angle - 180)) / 180);

    let d = tan * w;

    if(d <= h) {
      ret = [x + w, y + h, x, y + h - d];
    } else {
      d = h / tan;
      ret = [x + w, y + h, x + w - d, y];
    }
  } else if(angle >= 270 && angle < 360) {
    const tan = Math.tan((Math.PI * (angle - 270)) / 180);

    let d = tan * h;

    if(d <= w) {
      ret = [x, y + h, x + d, y];
    } else {
      d = w / tan;
      ret = [x, y + h, x + w, y + h - d];
    }
  }

  return ret;
}

export function findColor(context, sprite, prop) {
  const gradients = sprite.attr('gradients') || {};
  let color = prop === 'border' ? sprite.attr(prop).color : sprite.attr(prop),
    gradient;

  if(gradients[prop]) {
    /* istanbul ignore next */
    gradient = gradients[prop];
  } else if(typeof color !== 'string') {
    gradient = color;
  }

  if(gradient) {
    let {colors, vector, direction, rect} = gradient;

    /* istanbul ignore if  */
    if(direction != null) {
      if(prop === 'border') {
        rect = rect || [0, 0, ...sprite.outerSize];
      } else {
        const {width: borderWidth} = sprite.attr('border');
        rect = rect || [borderWidth, borderWidth, ...sprite.innerSize];
      }
      vector = gradientBox(direction, rect);
    }

    if(vector.length === 4) {
      color = context.createLinearGradient(...vector);
    } else if(vector.length === 6) {
      color = context.createRadialGradient(...vector);
    } /* istanbul ignore next  */ else if(vector.length === 3) {
      // for wxapp
      color = context.createCircularGradient(...vector);
    } /* istanbul ignore next  */ else {
      throw Error('Invalid gradient vector!');
    }

    colors.forEach((o) => {
      color.addColorStop(o.offset, o.color);
    });
  }

  return color;
}

const contextPool = [],
  contextReady = [],
  maxPollSize = 50;

export const cacheContextPool = {
  get(context) {
    if(contextReady.length > 0) {
      return contextReady.pop();
    }

    const canvas = context.canvas;
    if(!canvas || !canvas.cloneNode) {
      return;
    }
    const copied = canvas.cloneNode();
    return copied.getContext('2d');
  },
  flush() {
    contextReady.push(...contextPool);
    contextPool.length = 0;
  },
  put(...contexts) {
    let size = this.size;
    contexts.every((context) => {
      const ret = size++ < maxPollSize;
      if(ret) {
        context.canvas.width = 0;
        context.canvas.height = 0;
        contextPool.push(context);
      }
      return ret;
    });
  },
  get size() {
    return contextPool.length + contextReady.length;
  },
};