diff --git a/packages/camera/src/RoomCameraWidgetManager.ts b/packages/camera/src/RoomCameraWidgetManager.ts index 15a14dd..98572d7 100644 --- a/packages/camera/src/RoomCameraWidgetManager.ts +++ b/packages/camera/src/RoomCameraWidgetManager.ts @@ -6,6 +6,31 @@ import { TextureUtils } from '@nitrots/utils'; import { BLEND_MODES, ColorMatrix, ColorMatrixFilter, Container, Filter, Sprite, Texture } from 'pixi.js'; import { RoomCameraWidgetEffect } from './RoomCameraWidgetEffect'; +const COLOR_MATRIX_OFFSET_INDICES = [4, 9, 14, 19] as const; + +export const normalizeCameraColorMatrix = (matrix: ColorMatrix): ColorMatrix => +{ + const normalized = [ ...matrix ] as ColorMatrix; + + for(const index of COLOR_MATRIX_OFFSET_INDICES) + { + if(Math.abs(normalized[index]) > 1) normalized[index] /= 255; + } + + for(const [ rowStart, offsetIndex ] of [[0, 4], [5, 9], [10, 14]] as const) + { + const rowHasOnlyNegativeWeights = + (normalized[rowStart] <= 0) && + (normalized[rowStart + 1] <= 0) && + (normalized[rowStart + 2] <= 0) && + ((normalized[rowStart] !== 0) || (normalized[rowStart + 1] !== 0) || (normalized[rowStart + 2] !== 0)); + + if((normalized[offsetIndex] === 0) && rowHasOnlyNegativeWeights) normalized[offsetIndex] = 1; + } + + return normalized; +}; + export class RoomCameraWidgetManager implements IRoomCameraWidgetManager { private _effects: Map = new Map(); @@ -26,16 +51,9 @@ export class RoomCameraWidgetManager implements IRoomCameraWidgetManager const cameraEffect = new RoomCameraWidgetEffect(effect.name, effect.minLevel); - if(effect.colorMatrix.length) + if(effect.colorMatrix?.length) { - // Config offsets (indices 4,9,14,19) follow Flash's 0-255 convention. - // PixiJS v8 expects the full matrix in 0-1 space, so normalise them. - const m = [ ...effect.colorMatrix ] as ColorMatrix; - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - cameraEffect.colorMatrix = m; + cameraEffect.colorMatrix = normalizeCameraColorMatrix(effect.colorMatrix); } else { diff --git a/packages/camera/src/__tests__/RoomCameraWidgetManager.test.ts b/packages/camera/src/__tests__/RoomCameraWidgetManager.test.ts new file mode 100644 index 0000000..a8866da --- /dev/null +++ b/packages/camera/src/__tests__/RoomCameraWidgetManager.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from 'vitest'; +import { ColorMatrix } from 'pixi.js'; +import { normalizeCameraColorMatrix } from '../RoomCameraWidgetManager'; + +describe('normalizeCameraColorMatrix', () => +{ + it('keeps explicit negative grayscale bias untouched', () => + { + const matrix: ColorMatrix = [ + -0.5, -0.5, -0.5, 0, 1, + -0.5, -0.5, -0.5, 0, 1, + -0.5, -0.5, -0.5, 0, 1, + 0, 0, 0, 1, 0 + ]; + + expect(normalizeCameraColorMatrix(matrix)).toEqual(matrix); + }); + + it('adds missing white bias for negative grayscale matrices', () => + { + const matrix: ColorMatrix = [ + -0.5, -0.5, -0.5, 0, 0, + -0.5, -0.5, -0.5, 0, 0, + -0.5, -0.5, -0.5, 0, 0, + 0, 0, 0, 1, 0 + ]; + + expect(normalizeCameraColorMatrix(matrix)).toEqual([ + -0.5, -0.5, -0.5, 0, 1, + -0.5, -0.5, -0.5, 0, 1, + -0.5, -0.5, -0.5, 0, 1, + 0, 0, 0, 1, 0 + ]); + }); + + it('normalizes legacy 255-based offsets to pixi range', () => + { + const matrix: ColorMatrix = [ + 1, 0, 0, 0, 255, + 0, 1, 0, 0, 128, + 0, 0, 1, 0, 64, + 0, 0, 0, 1, 255 + ]; + + expect(normalizeCameraColorMatrix(matrix)).toEqual([ + 1, 0, 0, 0, 1, + 0, 1, 0, 0, 128 / 255, + 0, 0, 1, 0, 64 / 255, + 0, 0, 0, 1, 1 + ]); + }); +});