Created
March 20, 2019 11:30
-
-
Save ivanpopelyshev/6128a6b31f1a7f51d536b369a3812ecf to your computer and use it in GitHub Desktop.
WIP Pixi v5 Gradient
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const enum GradientInterpolationMethod { | |
RGB = 0, | |
LinearRGB = 1 | |
} | |
function toSRGB(x: number) { | |
if (x < 0.00313080) { | |
return x * 12.92; | |
} | |
return Math.pow(x, 1.0 / 2.4) * 1.055 - 0.055; | |
} | |
function toLinear(x: number) { | |
if (x <= 0.04045) { | |
return x / 12.92; | |
} | |
return Math.pow((x + 0.055) / 1.055, 2.4); | |
} | |
export class ColorStop { | |
constructor(public offset: number, public color: number, public alpha: number = 1.0) { | |
this.setColor(color); | |
} | |
setColor(color: number) { | |
this.color = color; | |
this.r = ((color >> 16) & 0xff) / 255.0; | |
this.g = ((color >> 8) & 0xff) / 255.0; | |
this.b = (color & 0xff) / 255.0; | |
this.cssColor = ''; | |
} | |
r: number; | |
g: number; | |
b: number; | |
cssColor: string; | |
sR: number; | |
sG: number; | |
sB: number; | |
} | |
export class Gradient implements core.ITextureResource { | |
constructor(public width = 256, public wrapMode = core.WRAP_MODES.CLAMP, public interpolationMethod = GradientInterpolationMethod.RGB) { | |
this.baseTexture = new core.BaseTexture(this); | |
this.texture = new core.Texture(this.baseTexture); | |
} | |
texture: core.Texture; | |
baseTexture: core.BaseTexture; | |
tempCanvas: HTMLCanvasElement = null; | |
tempContext: CanvasRenderingContext2D = null; | |
stops: Array<ColorStop> = []; | |
addColorStop(offset: number, color: number, alpha: number) { | |
const stop = new ColorStop(offset, color, alpha); | |
const stops = this.stops; | |
let ind = stops.length; | |
while (ind > 0 && stop.offset < stops[ind - 1].offset) { | |
ind--; | |
} | |
stops.push(null); | |
for (let t = stops.length - 1; t > ind; t--) { | |
stops[t] = stops[t - 1]; | |
} | |
stops[ind] = stop; | |
this.invalidate(); | |
} | |
addColorStopRatio(ratio: number, color: number, alpha: number) { | |
this.addColorStop(ratio / (this.width - 1), color, alpha); | |
} | |
_values: Float32Array = null; | |
_byteValues: Uint8Array = null; | |
_hasLinear = false; | |
invalidate() { | |
this.baseTexture._updateID++; | |
this._hasLinear = false; | |
} | |
calculateStopsLinear() { | |
this._hasLinear = true; | |
for (let i = 0; i < this.stops.length; i++) { | |
const stop = this.stops[i]; | |
stop.sR = toLinear(stop.r); | |
stop.sG = toLinear(stop.g); | |
stop.sB = toLinear(stop.b); | |
} | |
} | |
calculateLinearRGB() { | |
if (!this._hasLinear) { | |
this.calculateStopsLinear(); | |
} | |
const w = this.width; | |
const w4 = this.width * 4; | |
//lets sort | |
if (!this._values || this._values.length !== w4) { | |
this._values = new Float32Array(w4); | |
this._byteValues = new Uint8Array(w4); | |
} | |
if (this.baseTexture.width !== w) { | |
this.baseTexture.resize(w, 1); | |
} | |
const stops = this.stops; | |
const values = this._values; | |
const len = stops.length; | |
const pma = this.baseTexture.premultiplyAlpha ? 1 : 0; | |
let ind = 0; | |
let cur = stops[0]; | |
let next = stops[0]; | |
let i = 0; | |
let i4 = 0; | |
if (len > 1) { | |
for (; i < w; i++, i4 += 4) { | |
let part = i / w; | |
while (cur.offset >= part) { | |
ind++; | |
if (ind >= len) { | |
break; | |
} | |
cur = next; | |
next = stops[ind]; | |
} | |
const T = (part - cur.offset) / (next.offset - cur.offset); | |
const U = 1.0 - T; | |
const alpha = T * next.alpha + U * cur.alpha; | |
const alpha2 = 1.0 - pma * (1.0 - alpha); | |
// | |
values[i4] = toSRGB(T * next.sR + U * cur.sR) * alpha2; | |
values[i4 + 1] = toSRGB(T * next.sG + U * cur.sG) * alpha2; | |
values[i4 + 2] = toSRGB(T * next.sB + U * cur.sB) * alpha2; | |
values[i4 + 3] = alpha; | |
} | |
} | |
const A = cur.alpha; | |
const R = cur.r * (1.0 - pma * (1.0 - A)); | |
const G = cur.g * (1.0 - pma * (1.0 - A)); | |
const B = cur.b * (1.0 - pma * (1.0 - A)); | |
while (i4 < w4) { | |
values[i4] = R; | |
values[i4 + 1] = G; | |
values[i4 + 2] = B; | |
values[i4 + 3] = A; | |
} | |
const bytes = this._byteValues; | |
for (i = 0; i < w4; i++) { | |
bytes[i] = Math.round(values[i] * 255.0); | |
} | |
} | |
calculateCanvas() { | |
const w = this.width; | |
if (!this.tempCanvas) { | |
this.tempCanvas = document.createElement('canvas'); | |
this.tempCanvas.height = 1; | |
this.tempContext = this.tempCanvas.getContext('2d'); | |
} | |
if (this.baseTexture.width !== w) { | |
this.baseTexture.resize(w, 1); | |
} | |
const canvas = this.tempCanvas; | |
const context = this.tempContext; | |
if (canvas.width !== this.width) { | |
canvas.width = this.width; | |
canvas.height = 1; | |
} | |
//TODO: make a test if first and last pixel color correspond to first and last stop color | |
const grad = context.createLinearGradient(0, 0, this.width, 0); | |
const stops = this.stops; | |
for (let i = 0; i < stops.length; i++) { | |
const cur = stops[i]; | |
if (cur.cssColor.length === 0) { | |
const R = Math.round(cur.r * 255.0); | |
const G = Math.round(cur.g * 255.0); | |
const B = Math.round(cur.b * 255.0); | |
const A = Math.round(cur.alpha * 1000.0) / 1000.0; | |
cur.cssColor = `rgba(${R},${G},${B},${A})`; | |
} | |
grad.addColorStop(cur.offset, cur.cssColor); | |
} | |
context.fillStyle = grad; | |
context.fillRect(0, 0, this.width, 1); | |
} | |
onTextureDestroy(baseTexture: gobi.core.BaseTexture): boolean { | |
return false; | |
} | |
onTextureNew(baseTexture: gobi.core.BaseTexture): void { | |
baseTexture.resize(this.width, 1); | |
baseTexture.mipmap = false; | |
baseTexture.wrapMode = this.wrapMode; | |
} | |
onTextureTag(baseTexture: gobi.core.BaseTexture): void { | |
} | |
onTextureUpload(renderer: gobi.pixi.WebGLRenderer, baseTexture: gobi.core.BaseTexture, glTexture: gobi.glCore.GLTexture): boolean { | |
// do it if we make float texture | |
// glTexture.width = this.width; | |
// glTexture.height = 1; | |
if (this.interpolationMethod === GradientInterpolationMethod.LinearRGB) { | |
this.calculateLinearRGB(); | |
glTexture.uploadData(this._byteValues, this.width, 1); | |
} else { | |
this.calculateCanvas(); | |
glTexture.upload(this.tempCanvas); | |
} | |
return true; | |
} | |
destroy() { | |
this.texture.destroy(true); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment