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.
This commit is contained in:
zarzet committed 2026-09-20 17:20:52 +07:00
1 parent 6038ce5777
commit 5053ed93ee
2 files changed
+47 -3

No files matched your search

+28 -3
View File
@@ -1,9 +1,35 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.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. /// 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. /// Read back only 48 pixels across, at most three times a second when visible.
class MornyeArtworkContrast extends StatefulWidget { class MornyeArtworkContrast extends StatefulWidget {
@@ -95,12 +121,11 @@ class _MornyeArtworkContrastState extends State<MornyeArtworkContrast> {
for (var y = top; y < bottom; y++) { for (var y = top; y < bottom; y++) {
for (var x = left; x < right; x++) { for (var x = left; x < right; x++) {
final index = (y * image.width + x) * 4; final index = (y * image.width + x) * 4;
luminance += Color.fromARGB( luminance += artworkPixelLuminance(
255,
pixels.getUint8(index), pixels.getUint8(index),
pixels.getUint8(index + 1), pixels.getUint8(index + 1),
pixels.getUint8(index + 2), pixels.getUint8(index + 2),
).computeLuminance(); );
count++; count++;
} }
} }
+19
View File
@@ -3,6 +3,25 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/widgets/mornye_artwork_contrast.dart'; import 'package:spotiflac_android/widgets/mornye_artwork_contrast.dart';
void main() { 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', ( testWidgets('foreground follows changing frames separately for each region', (
tester, tester,
) async { ) async {