From 5053ed93eec4e809667dd2687f7decb4d60d827e Mon Sep 17 00:00:00 2001 From: zarzet <42882290+zarzet@users.noreply.github.com> Date: Sun, 20 Sep 2026 17:20:52 +0700 Subject: [PATCH] perf(mornye): reuse exact luminance contributions for contrast sampling Use 6 KiB of lookup tables to avoid repeated Color allocations and exponentiation during frame sampling. Exhaustively verify exact Flutter luminance parity for every 8-bit RGB value. --- lib/widgets/mornye_artwork_contrast.dart | 31 +++++++++++++++++++++--- test/mornye_artwork_contrast_test.dart | 19 +++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/lib/widgets/mornye_artwork_contrast.dart b/lib/widgets/mornye_artwork_contrast.dart index 53743edf..af453239 100644 --- a/lib/widgets/mornye_artwork_contrast.dart +++ b/lib/widgets/mornye_artwork_contrast.dart @@ -1,9 +1,35 @@ import 'dart:async'; +import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +// Readback uses 8-bit sRGB. Cache each channel's exact Flutter luminance +// contribution (6 KiB total), avoiding three powers and a Color per pixel. +final _redLuminance = Float64List.fromList( + List.generate( + 256, + (value) => Color.fromARGB(255, value, 0, 0).computeLuminance(), + ), +); +final _greenLuminance = Float64List.fromList( + List.generate( + 256, + (value) => Color.fromARGB(255, 0, value, 0).computeLuminance(), + ), +); +final _blueLuminance = Float64List.fromList( + List.generate( + 256, + (value) => Color.fromARGB(255, 0, 0, value).computeLuminance(), + ), +); + +/// Matches [Color.computeLuminance] for an RGB pixel from an 8-bit readback. +double artworkPixelLuminance(int red, int green, int blue) => + _redLuminance[red] + _greenLuminance[green] + _blueLuminance[blue]; + /// Samples rendered video and its fade, rather than a static album palette. /// Read back only 48 pixels across, at most three times a second when visible. class MornyeArtworkContrast extends StatefulWidget { @@ -95,12 +121,11 @@ class _MornyeArtworkContrastState extends State { for (var y = top; y < bottom; y++) { for (var x = left; x < right; x++) { final index = (y * image.width + x) * 4; - luminance += Color.fromARGB( - 255, + luminance += artworkPixelLuminance( pixels.getUint8(index), pixels.getUint8(index + 1), pixels.getUint8(index + 2), - ).computeLuminance(); + ); count++; } } diff --git a/test/mornye_artwork_contrast_test.dart b/test/mornye_artwork_contrast_test.dart index 303206fd..9806993b 100644 --- a/test/mornye_artwork_contrast_test.dart +++ b/test/mornye_artwork_contrast_test.dart @@ -3,6 +3,25 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:spotiflac_android/widgets/mornye_artwork_contrast.dart'; void main() { + test('readback luminance matches Flutter exactly for every RGB pixel', () { + for (var red = 0; red < 256; red++) { + for (var green = 0; green < 256; green++) { + for (var blue = 0; blue < 256; blue++) { + final expected = Color.fromARGB( + 255, + red, + green, + blue, + ).computeLuminance(); + final actual = artworkPixelLuminance(red, green, blue); + if (actual != expected) { + fail('RGB($red, $green, $blue): $actual != $expected'); + } + } + } + } + }); + testWidgets('foreground follows changing frames separately for each region', ( tester, ) async {