Grade through a LUT: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: table (line 27), apply (line 74).
1/// A colour grade held in a table: every colour goes in, the colour the table
2/// says comes out.
3///
4/// Quoted by `lut_grading.md` and shown whole in the Source tab.
5library;
7import 'dart:typed_data';
9import 'package:flutter3d/flutter3d.dart';
10import 'package:flutter3d_showcase/pages/post/post_stage.dart';
11import 'package:flutter3d_showcase/src/demo/demo.dart';
13final class LutGradingDemo extends ShowcaseDemo {
14 int table = 0;
15 double strength = 1.0;
17 late final List<TextureHandle> _tables;
18 late LookSettings _look;
20 static const int _size = 17;
23 void configureView(DemoContext context) => PostStage.frame(context);
26 Scene build(DemoContext context) {
27 final Uint8List identity = buildIdentityLut(size: _size);
28 final List<Uint8List> looks = <Uint8List>[
29 // Sepia: every colour becomes a brown of the same brightness.
30 _remap(identity, (double r, double g, double b) {
31 final double l = 0.3 * r + 0.59 * g + 0.11 * b;
32 return <double>[l * 1.15, l * 0.95, l * 0.7];
33 }),
34 // Red and blue swapped, which nobody would mistake for the original.
35 _remap(identity, (double r, double g, double b) => <double>[b, g, r]),
36 // Night: dark, with the red taken out.
37 _remap(identity, (double r, double g, double b) {
38 return <double>[r * 0.3, g * 0.55, b * 0.9];
39 }),
40 ];
41 _tables = <TextureHandle>[
42 for (final Uint8List pixels in looks)
43 context.device.createTextureFromPixels(
44 width: _size * _size,
45 height: _size,
46 format: TextureFormat.r8g8b8a8UNormInt,
47 pixels: ByteData.sublistView(pixels),
48 )!,
49 ];
50 return PostStage.build(context).scene;
51 }
53 /// A copy of [source] with every entry passed through [change].
54 static Uint8List _remap(
55 Uint8List source,
56 List<double> Function(double r, double g, double b) change,
57 ) {
58 final Uint8List out = Uint8List.fromList(source);
59 for (var i = 0; i < out.length; i += 4) {
60 final List<double> mapped = change(
61 out[i] / 255.0,
62 out[i + 1] / 255.0,
63 out[i + 2] / 255.0,
64 );
65 for (var c = 0; c < 3; c++) {
66 out[i + c] = (mapped[c].clamp(0.0, 1.0) * 255).round();
67 }
68 }
69 return out;
70 }
73 RenderSettings settings(DemoContext context) {
74 _look = LookSettings(lut: _tables[table], lutStrength: strength);
75 return RenderSettings(look: _look);
76 }
79 List<DemoControl> controls(DemoContext context) => <DemoControl>[
80 ChoiceControl(
81 'Table',
82 options: const <String>['Sepia', 'Red and blue swapped', 'Night'],
83 index: () => table,
84 onChanged: (int i) => table = i,
85 ),
86 SliderControl(
87 'Strength',
88 min: 0,
89 max: 1,
90 value: () => strength,
91 onChanged: (double v) => strength = v,
92 ),
93 ];
96 void verify(Scene scene, FrameResult frame) {
97 if (!_look.gradesThroughLut) {
98 throw StateError('the table would not be sampled this frame');
99 }
100 if (!passRan(frame, 'composite')) {
101 throw StateError('the composite pass, which reads the table, is absent');
102 }
103 }
104}