4

I'm using the Pixi.js v4 graphics library to make a game with JavaScript. I know that I can draw a black + rounded rectangle like so:

const rectangle = new pixi.Graphics();
rectangle.beginFill(0); // Color it black
rectangle.drawRoundedRect(
    0,
    0,
    100, // Make it 100x100
    100,
    5, // Make the rounded corners have a radius of 5
);
rectangle.endFill();
stage.addChild(rectangle);
  1. How do I draw a rounded rectangle with a gradient from white to black?

  2. How do I draw a rounded rectangle that has gradual opacity such that it fades in from left to right?

Neuron
  • 5,141
  • 5
  • 38
  • 59
James
  • 1,394
  • 2
  • 21
  • 31

4 Answers4

5

It looks like it's not possible to implement what you need with pixi.js without additional code, but we can do some magic to make it happen. Here's the result of what I've got: https://jsfiddle.net/exkf3zfo/21/

The bottom color is a pure red with 0.2 alpha.

I would split the whole process to the next steps:

  1. Drawing the gradient
  2. Masking the gradient with the rounded mask

Here is the code itself:

var app = new PIXI.Application(800, 600, {
  antialias: true
});
document.body.appendChild(app.view);

// Functions

// param color is a number (e.g. 255)
// return value is a string (e.g. ff)
var prepareRGBChannelColor = function(channelColor) {
  var colorText = channelColor.toString(16);
  if (colorText.length < 2) {
    while (colorText.length < 2) {
      colorText = "0" + colorText;
    }
  }

  return colorText;
}

// Getting RGB channels from a number color
// param color is a number
// return an RGB channels object {red: number, green: number, blue: number}
var getRGBChannels = function(color) {
  var colorText = color.toString(16);
  if (colorText.length < 6) {
    while (colorText.length < 6) {
      colorText = "0" + colorText;
    }
  }

  var result = {
    red: parseInt(colorText.slice(0, 2), 16),
    green: parseInt(colorText.slice(2, 4), 16),
    blue: parseInt(colorText.slice(4, 6), 16)
  };
  return result;
}

// Preparaiton of a color data object
// param color is a number [0-255]
// param alpha is a number [0-1]
// return the color data object {color: number, alpha: number, channels: {red: number, green: number, blue: number}}
var prepareColorData = function(color, alpha) {
  return {
    color: color,
    alpha: alpha,
    channels: getRGBChannels(color)
  }
}

// Getting the color of a gradient for a very specific gradient coef
// param from is a color data object
// param to is a color data object
// return value is of the same type
var getColorOfGradient = function(from, to, coef) {
  if (!from.alpha && from.alpha !== 0) {
    from.alpha = 1;
  }
  if (!from.alpha && from.alpha !== 0) {
    to.alpha = 1;
  }

  var colorRed = Math.floor(from.channels.red + coef * (to.channels.red - from.channels.red));
  colorRed = Math.min(colorRed, 255);
  var colorGreen = Math.floor(from.channels.green + coef * (to.channels.green - from.channels.green));
  colorGreen = Math.min(colorGreen, 255);
  var colorBlue = Math.floor(from.channels.blue + coef * (to.channels.blue - from.channels.blue));
  colorBlue = Math.min(colorBlue, 255);

  var rgb = prepareRGBChannelColor(colorRed) + prepareRGBChannelColor(colorGreen) + prepareRGBChannelColor(colorBlue);

  return {
    color: parseInt(rgb, 16),
    alpha: from.alpha + coef * (to.alpha - from.alpha)
  };
}

var startTime = Date.now();
console.log("start: " + startTime);

// Drawing the gradient
//
var gradient = new PIXI.Graphics();
app.stage.addChild(gradient);
//
var rect = {
  width: 200,
  height: 200
};
var round = 20;
//
var colorFromData = prepareColorData(0xFF00FF, 1);
var colorToData = prepareColorData(0xFF0000, 0.2);
//
var stepCoef;
var stepColor;
var stepAlpha;
var stepsCount = 100;
var stepHeight = rect.height / stepsCount;
for (var stepIndex = 0; stepIndex < stepsCount; stepIndex++) {
  stepCoef = stepIndex / stepsCount;
  stepColor = getColorOfGradient(colorFromData, colorToData, stepCoef);

  gradient.beginFill(stepColor.color, stepColor.alpha);
  gradient.drawRect(
    0,
    rect.height * stepCoef,
    rect.width,
    stepHeight
  );
}

// Applying a mask with round corners to the gradient
var roundMask = new PIXI.Graphics();
roundMask.beginFill(0x000000);
roundMask.drawRoundedRect(0, 0, rect.width, rect.height, round);
app.stage.addChild(roundMask);
gradient.mask = roundMask;

var endTime = Date.now();
console.log("end: " + endTime);
console.log("total: " + (endTime - startTime));

The interesting thing is that it takes only about 2-5 ms for the whole process!

If you wan't to change colors of the gradient to white>black (as described in the question), just change the next params:

var colorFromData = prepareColorData(0xFF00FF, 1);
var colorToData = prepareColorData(0xFF0000, 0.2);

To:

var colorFromData = prepareColorData(0xFFFFFF, 1);
var colorToData = prepareColorData(0x000000, 0.2);
Neuron
  • 5,141
  • 5
  • 38
  • 59
Mark Dolbyrev
  • 1,887
  • 1
  • 17
  • 24
  • 1
    I did your code and it works very well on my computer but some of my players see lines(background) between fills. https://prnt.sc/py6kr9 https://prnt.sc/pyrvs8 Why is this happening on some devices and how to fix it? You can check out the game here: https://colonist.io/ – demiculus Nov 22 '19 at 02:47
4

Not full answer but some extra information

  1. As far I know, you can't use gradient for PIXI.Graphics even for sprites you need extra canvas

    Just draw the gradient you want to a canvas: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createLinearGradient

    Then use that canvas as a texture: Texture.fromCanvas(canvas);

    Look at this article.

  2. For gradual opacity, Alpha Mask can help

    http://pixijs.io/examples/#/demos/alpha-mask.js

P.S Maybe phaser.js can do more

Neuron
  • 5,141
  • 5
  • 38
  • 59
2

Did you ever figure this out? I couldn't find a solution online either, so I implemented it myself using a filter. Have a look: https://codepen.io/Lancer611/pen/KodabK.

Some of the pixi code:

function newGradientPoly(poly, fill, fillSize){
  var container = new PIXI.Sprite();
  app.stage.addChild(container);
  var shape = new PIXI.Graphics();
  shape.beginFill(0xffffff)
  .lineStyle(1, 0x333333)
  .drawPolygon(poly);
  var mask = new PIXI.Graphics();
  mask.beginFill(0xffffff, 1)
    .drawPolygon(poly);
  container.mask = mask;
  container.addChild(shape);
  var fshaderCode = document.getElementById("fragShader").innerHTML;
  fogShader = new PIXI.Filter(null,  fshaderCode);
  fogShader.uniforms.resolution = [width, height];
  fogShader.uniforms.segments = poly.slice();
  fogShader.uniforms.count = poly.length/2;
  fogShader.uniforms.gSize = fillSize;
  fogShader.uniforms.fill = fill;
  shape.filters=[fogShader];
}
lancew
  • 780
  • 4
  • 22
0

I've created a pixi plugin for displaying vector drawings in Pixi. The main limitation is that you need to draw your rectangle in the vector art program Omber first, so you need to know the size of your rectangle beforehand (since everything is vector-based, you could theoretically scale things later, but then the rounded corners would end up being a little uneven). The workflow is similar to using sprites: 1. draw your rectangles in Omber 2. export them to gltf 3. load the gltf files in your Pixi program 4. position the rectangles where you want them.

Another possibility is that you could create the gradient as a separate object, and then you can mask it out with a polygon. Here's an example. In that example, I'm using a vector drawing for the gradient, but since gradients don't become blurry when resized, you could probably use a sprite for that as well. I'm not sure if masks have good performance, but if you just need a few of them, then it's probably fine.

Ming-Yee Iu
  • 844
  • 6
  • 5