@@ -10,15 +10,15 @@ use plotters::coord::ranged1d::{KeyPointHint, NoDefaultFormatting, Ranged, Value
10
10
use plotters:: coord:: types:: RangedCoordusize ;
11
11
use plotters:: prelude:: {
12
12
AreaSeries , BitMapBackend , Cartesian2d , ChartBuilder , ChartContext , IntoDrawingArea ,
13
- PathElement , SeriesLabelPosition , WHITE ,
13
+ LineSeries , PathElement , SeriesLabelPosition , WHITE ,
14
14
} ;
15
15
use plotters:: style:: { BLACK , Color , IntoTextStyle , RGBColor , ShapeStyle } ;
16
16
17
17
use dolby_vision:: rpu:: utils:: parse_rpu_file;
18
18
use dolby_vision:: utils:: { nits_to_pq, pq_to_nits} ;
19
19
20
20
use super :: input_from_either;
21
- use super :: rpu_info:: RpusListSummary ;
21
+ use super :: rpu_info:: { L2Data , RpusListSummary } ;
22
22
use crate :: commands:: PlotArgs ;
23
23
24
24
#[ cfg( not( feature = "system-font" ) ) ]
@@ -56,10 +56,13 @@ impl Plotter {
56
56
title,
57
57
start : start_arg,
58
58
end : end_arg,
59
+ l2,
59
60
} = args;
60
61
61
- let output = output. unwrap_or ( PathBuf :: from ( "L1_plot.png" ) ) ;
62
- let title = title. unwrap_or ( String :: from ( "Dolby Vision L1 plot" ) ) ;
62
+ let ( level, y_desc) = if l2 { ( 2 , "" ) } else { ( 1 , "nits (cd/m²)" ) } ;
63
+
64
+ let output = output. unwrap_or ( PathBuf :: from ( format ! ( "L{level}_plot.png" ) ) ) ;
65
+ let title = title. unwrap_or ( format ! ( "Dolby Vision L{level} plot" ) ) ;
63
66
64
67
let input = input_from_either ( "info" , input, input_pos) ?;
65
68
let plotter = Plotter { input } ;
@@ -81,13 +84,17 @@ impl Plotter {
81
84
. titled ( & title, ( "sans-serif" , 40 ) ) ?;
82
85
83
86
println ! ( "Plotting..." ) ;
84
- let summary = RpusListSummary :: new ( rpus) ?;
87
+ let summary = if l2 {
88
+ RpusListSummary :: with_l2_data ( rpus) ?
89
+ } else {
90
+ RpusListSummary :: new ( rpus) ?
91
+ } ;
85
92
86
93
let mut chart = ChartBuilder :: on ( & root)
87
94
. x_label_area_size ( 60 )
88
95
. y_label_area_size ( 60 )
89
96
. margin_top ( 90 )
90
- . build_cartesian_2d ( x_spec, PqCoord { } ) ?;
97
+ . build_cartesian_2d ( x_spec, PqCoord :: for_level ( level ) ) ?;
91
98
92
99
chart
93
100
. configure_mesh ( )
@@ -98,10 +105,15 @@ impl Plotter {
98
105
. x_desc ( "frames" )
99
106
. x_max_light_lines ( 1 )
100
107
. x_labels ( 24 )
101
- . y_desc ( "nits (cd/m²)" )
108
+ . y_desc ( y_desc )
102
109
. draw ( ) ?;
103
110
104
- Self :: draw_l1_series ( & mut chart, & summary) ?;
111
+ if l2 {
112
+ Self :: draw_l2_series ( & mut chart, & summary) ?;
113
+ } else {
114
+ Self :: draw_l1_series ( & mut chart, & summary) ?;
115
+ }
116
+
105
117
chart
106
118
. configure_series_labels ( )
107
119
. border_style ( BLACK )
@@ -239,49 +251,155 @@ impl Plotter {
239
251
240
252
Ok ( ( ) )
241
253
}
254
+
255
+ fn draw_l2_series (
256
+ chart : & mut ChartContext < BitMapBackend , Cartesian2d < RangedCoordusize , PqCoord > > ,
257
+ summary : & RpusListSummary ,
258
+ ) -> Result < ( ) > {
259
+ let data = summary. l2_data . as_ref ( ) . unwrap ( ) ;
260
+ let l2_stats = summary. l2_stats . as_ref ( ) . unwrap ( ) ;
261
+
262
+ type Series = ( & ' static str , fn ( & L2Data ) -> f64 , ( f64 , f64 , f64 ) , RGBColor ) ;
263
+ let series: [ Series ; 6 ] = [
264
+ (
265
+ "slope (gain)" ,
266
+ |e| e. 0 as f64 ,
267
+ l2_stats. slope ,
268
+ RGBColor ( 96 , 158 , 232 ) , // blue
269
+ ) ,
270
+ (
271
+ "offset (lift)" ,
272
+ |e| e. 1 as f64 ,
273
+ l2_stats. offset ,
274
+ RGBColor ( 230 , 110 , 132 ) , // pink
275
+ ) ,
276
+ (
277
+ "power (gamma)" ,
278
+ |e| e. 2 as f64 ,
279
+ l2_stats. power ,
280
+ RGBColor ( 236 , 162 , 75 ) , // orange
281
+ ) ,
282
+ (
283
+ "chroma (weight)" ,
284
+ |e| e. 3 as f64 ,
285
+ l2_stats. chroma ,
286
+ RGBColor ( 115 , 187 , 190 ) , // cyan
287
+ ) ,
288
+ (
289
+ "saturation (gain)" ,
290
+ |e| e. 4 as f64 ,
291
+ l2_stats. saturation ,
292
+ RGBColor ( 144 , 106 , 252 ) , // purple
293
+ ) ,
294
+ (
295
+ "ms (weight)" ,
296
+ |e| e. 5 as f64 ,
297
+ l2_stats. ms_weight ,
298
+ RGBColor ( 243 , 205 , 95 ) , // yellow
299
+ ) ,
300
+ ] ;
301
+
302
+ for ( name, field_extractor, stats, color) in series. iter ( ) {
303
+ let label = format ! (
304
+ "{name} (min: {:.0}, max: {:.0}, avg: {:.0})" ,
305
+ stats. 0 , stats. 1 , stats. 2
306
+ ) ;
307
+ let series = LineSeries :: new (
308
+ ( 0 ..) . zip ( data. iter ( ) ) . map ( |( x, y) | ( x, field_extractor ( y) ) ) ,
309
+ color,
310
+ ) ;
311
+ let shape_style = ShapeStyle {
312
+ color : color. to_rgba ( ) ,
313
+ filled : false ,
314
+ stroke_width : 2 ,
315
+ } ;
316
+
317
+ chart
318
+ . draw_series ( series) ?
319
+ . label ( label)
320
+ . legend ( move |( x, y) | PathElement :: new ( vec ! [ ( x, y) , ( x + 20 , y) ] , shape_style) ) ;
321
+ }
322
+
323
+ Ok ( ( ) )
324
+ }
325
+ }
326
+
327
+ pub struct PqCoord {
328
+ key_points : Vec < f64 > ,
329
+ range : Range < f64 > ,
330
+ mapper : fn ( & f64 , ( i32 , i32 ) ) -> i32 ,
331
+ formatter : fn ( & f64 ) -> String ,
242
332
}
243
333
244
- pub struct PqCoord { }
334
+ impl PqCoord {
335
+ pub fn for_level ( level : u8 ) -> PqCoord {
336
+ if level == 2 {
337
+ PqCoord {
338
+ key_points : vec ! [
339
+ 0.0 , 512.0 , 1024.0 , 1536.0 , 2048.0 , 2560.0 , 3072.0 , 3584.0 , 4096.0 ,
340
+ ] ,
341
+ range : 0_f64 ..4096.0_f64 ,
342
+ mapper : |value, limit| {
343
+ let norm = value / 4096.0 ;
344
+ let size = limit. 1 - limit. 0 ;
345
+ ( norm * size as f64 ) . round ( ) as i32 + limit. 0
346
+ } ,
347
+ formatter : |value| format ! ( "{value}" ) ,
348
+ }
349
+ } else {
350
+ PqCoord {
351
+ key_points : vec ! [
352
+ nits_to_pq( 0.01 ) ,
353
+ nits_to_pq( 0.1 ) ,
354
+ nits_to_pq( 0.5 ) ,
355
+ nits_to_pq( 1.0 ) ,
356
+ nits_to_pq( 2.5 ) ,
357
+ nits_to_pq( 5.0 ) ,
358
+ nits_to_pq( 10.0 ) ,
359
+ nits_to_pq( 25.0 ) ,
360
+ nits_to_pq( 50.0 ) ,
361
+ nits_to_pq( 100.0 ) ,
362
+ nits_to_pq( 200.0 ) ,
363
+ nits_to_pq( 400.0 ) ,
364
+ nits_to_pq( 600.0 ) ,
365
+ nits_to_pq( 1000.0 ) ,
366
+ nits_to_pq( 2000.0 ) ,
367
+ nits_to_pq( 4000.0 ) ,
368
+ nits_to_pq( 10000.0 ) ,
369
+ ] ,
370
+ range : 0_f64 ..1.0_f64 ,
371
+ mapper : |value, limit| {
372
+ let size = limit. 1 - limit. 0 ;
373
+ ( * value * size as f64 ) as i32 + limit. 0
374
+ } ,
375
+ formatter : |value| {
376
+ let nits = ( pq_to_nits ( * value) * 1000.0 ) . round ( ) / 1000.0 ;
377
+ format ! ( "{nits}" )
378
+ } ,
379
+ }
380
+ }
381
+ }
382
+ }
245
383
246
384
impl Ranged for PqCoord {
247
385
type FormatOption = NoDefaultFormatting ;
248
386
type ValueType = f64 ;
249
387
250
388
fn map ( & self , value : & f64 , limit : ( i32 , i32 ) ) -> i32 {
251
- let size = limit. 1 - limit. 0 ;
252
- ( * value * size as f64 ) as i32 + limit. 0
389
+ ( self . mapper ) ( value, limit)
253
390
}
254
391
255
392
fn key_points < Hint : KeyPointHint > ( & self , _hint : Hint ) -> Vec < f64 > {
256
- vec ! [
257
- nits_to_pq( 0.01 ) ,
258
- nits_to_pq( 0.1 ) ,
259
- nits_to_pq( 0.5 ) ,
260
- nits_to_pq( 1.0 ) ,
261
- nits_to_pq( 2.5 ) ,
262
- nits_to_pq( 5.0 ) ,
263
- nits_to_pq( 10.0 ) ,
264
- nits_to_pq( 25.0 ) ,
265
- nits_to_pq( 50.0 ) ,
266
- nits_to_pq( 100.0 ) ,
267
- nits_to_pq( 200.0 ) ,
268
- nits_to_pq( 400.0 ) ,
269
- nits_to_pq( 600.0 ) ,
270
- nits_to_pq( 1000.0 ) ,
271
- nits_to_pq( 2000.0 ) ,
272
- nits_to_pq( 4000.0 ) ,
273
- nits_to_pq( 10000.0 ) ,
274
- ]
393
+ self . key_points . clone ( )
275
394
}
276
395
277
396
fn range ( & self ) -> Range < f64 > {
278
- 0_f64 .. 1.0_f64
397
+ self . range . clone ( )
279
398
}
280
399
}
281
400
282
401
impl ValueFormatter < f64 > for PqCoord {
283
402
fn format_ext ( & self , value : & f64 ) -> String {
284
- let nits = ( pq_to_nits ( * value) * 1000.0 ) . round ( ) / 1000.0 ;
285
- format ! ( "{nits}" )
403
+ ( self . formatter ) ( value)
286
404
}
287
405
}
0 commit comments