1
+ use regex:: Regex ;
2
+ use serde_json:: { json, Map , Value } ;
3
+ use std:: collections:: HashMap ;
4
+ use uuid:: Uuid ;
5
+
6
+ use crate :: file_management:: Preset ;
7
+
8
+ #[ derive( Copy , Clone , Debug ) ]
9
+ enum Num {
10
+ I ( i64 ) ,
11
+ F ( f64 ) ,
12
+ }
13
+
14
+ fn parse_num ( s : & str ) -> Option < Num > {
15
+ if let Ok ( i) = s. parse :: < i64 > ( ) {
16
+ Some ( Num :: I ( i) )
17
+ } else if let Ok ( f) = s. parse :: < f64 > ( ) {
18
+ Some ( Num :: F ( f) )
19
+ } else {
20
+ None
21
+ }
22
+ }
23
+
24
+ fn num_to_json ( num : Num ) -> Option < Value > {
25
+ match num {
26
+ Num :: I ( i) => Some ( Value :: Number ( i. into ( ) ) ) ,
27
+ Num :: F ( f) => serde_json:: Number :: from_f64 ( f) . map ( Value :: Number ) ,
28
+ }
29
+ }
30
+
31
+ fn get_attr_as_f64 ( attrs : & HashMap < String , String > , key : & str ) -> Option < f64 > {
32
+ attrs. get ( key)
33
+ . and_then ( |s| s. trim_start_matches ( '+' ) . parse :: < f64 > ( ) . ok ( ) )
34
+ }
35
+
36
+ fn extract_xmp_name ( xmp_content : & str ) -> Option < String > {
37
+ let re = Regex :: new ( r#"(?s)<crs:Name>.*?<rdf:Alt>.*?<rdf:li[^>]*>([^<]+)</rdf:li>.*?</crs:Name>"# )
38
+ . ok ( ) ?;
39
+ re. captures ( xmp_content)
40
+ . and_then ( |c| c. get ( 1 ) . map ( |m| m. as_str ( ) . trim ( ) . to_string ( ) ) )
41
+ }
42
+
43
+ fn extract_tone_curve_points ( xmp_str : & str , curve_name : & str ) -> Option < Vec < Value > > {
44
+ let pattern = format ! (
45
+ r"(?s)<crs:{}>\s*<rdf:Seq>(.*?)</rdf:Seq>\s*</crs:{}>" ,
46
+ curve_name, curve_name
47
+ ) ;
48
+ let re = Regex :: new ( & pattern) . ok ( ) ?;
49
+ let captures = re. captures ( xmp_str) ?;
50
+ let seq_content = captures. get ( 1 ) ?. as_str ( ) ;
51
+
52
+ let point_re = Regex :: new ( r"<rdf:li>(\d+),\s*(\d+)</rdf:li>" ) . ok ( ) ?;
53
+ let mut points = Vec :: new ( ) ;
54
+
55
+ for point_cap in point_re. captures_iter ( seq_content) {
56
+ let x: u32 = point_cap. get ( 1 ) ?. as_str ( ) . parse ( ) . ok ( ) ?;
57
+ let y: u32 = point_cap. get ( 2 ) ?. as_str ( ) . parse ( ) . ok ( ) ?;
58
+
59
+ let mut final_y = y;
60
+ if curve_name == "ToneCurvePV2012" {
61
+ const SHADOW_RANGE_END : f64 = 64.0 ;
62
+ const SHADOW_DAMPEN_START : f64 = 0.6 ;
63
+ const SHADOW_DAMPEN_END : f64 = 1.0 ;
64
+
65
+ let x_f64 = x as f64 ;
66
+ let y_f64 = y as f64 ;
67
+
68
+ if y_f64 > x_f64 && x_f64 < SHADOW_RANGE_END {
69
+ let lift_amount = y_f64 - x_f64;
70
+ let progress = x_f64 / SHADOW_RANGE_END ;
71
+ let dampening_factor = SHADOW_DAMPEN_START + ( SHADOW_DAMPEN_END - SHADOW_DAMPEN_START ) * progress;
72
+
73
+ let new_y = x_f64 + ( lift_amount * dampening_factor) ;
74
+ final_y = new_y. round ( ) . clamp ( 0.0 , 255.0 ) as u32 ;
75
+ }
76
+ }
77
+
78
+ let mut point = Map :: new ( ) ;
79
+ point. insert ( "x" . to_string ( ) , Value :: Number ( x. into ( ) ) ) ;
80
+ point. insert ( "y" . to_string ( ) , Value :: Number ( final_y. into ( ) ) ) ;
81
+ points. push ( Value :: Object ( point) ) ;
82
+ }
83
+
84
+ if points. is_empty ( ) {
85
+ None
86
+ } else {
87
+ Some ( points)
88
+ }
89
+ }
90
+
91
+ pub fn convert_xmp_to_preset ( xmp_content : & str ) -> Result < Preset , String > {
92
+ let xmp_one_line = xmp_content. split ( '\n' ) . collect :: < Vec < _ > > ( ) . join ( " " ) ;
93
+
94
+ let attr_re = Regex :: new ( r#"crs:([A-Za-z0-9]+)="([^"]*)""# )
95
+ . map_err ( |e| format ! ( "Regex compilation failed: {}" , e) ) ?;
96
+ let mut attrs: HashMap < String , String > = HashMap :: new ( ) ;
97
+ for cap in attr_re. captures_iter ( & xmp_one_line) {
98
+ attrs. insert ( cap[ 1 ] . to_string ( ) , cap[ 2 ] . to_string ( ) ) ;
99
+ }
100
+
101
+ let mut adjustments = Map :: new ( ) ;
102
+ let mut hsl_map = Map :: new ( ) ;
103
+ let mut color_grading_map = Map :: new ( ) ;
104
+ let mut curves_map = Map :: new ( ) ;
105
+
106
+ let mappings = vec ! [
107
+ ( "Exposure2012" , "exposure" ) ,
108
+ ( "Contrast2012" , "contrast" ) ,
109
+ ( "Highlights2012" , "highlights" ) ,
110
+ ( "Whites2012" , "whites" ) ,
111
+ ( "Blacks2012" , "blacks" ) ,
112
+ ( "Clarity2012" , "clarity" ) ,
113
+ ( "Dehaze" , "dehaze" ) ,
114
+ ( "Vibrance" , "vibrance" ) ,
115
+ ( "Saturation" , "saturation" ) ,
116
+ ( "Texture" , "structure" ) ,
117
+ ( "SharpenRadius" , "sharpenRadius" ) ,
118
+ ( "SharpenDetail" , "sharpenDetail" ) ,
119
+ ( "SharpenEdgeMasking" , "sharpenMasking" ) ,
120
+ ( "LuminanceSmoothing" , "lumaNoiseReduction" ) ,
121
+ ( "ColorNoiseReduction" , "colorNoiseReduction" ) ,
122
+ ( "ColorNoiseReductionDetail" , "colorNoiseDetail" ) ,
123
+ ( "ColorNoiseReductionSmoothness" , "colorNoiseSmoothness" ) ,
124
+ ( "ChromaticAberrationRedCyan" , "chromaticAberrationRedCyan" ) ,
125
+ ( "ChromaticAberrationBlueYellow" , "chromaticAberrationBlueYellow" ) ,
126
+ ( "PostCropVignetteAmount" , "vignetteAmount" ) ,
127
+ ( "PostCropVignetteMidpoint" , "vignetteMidpoint" ) ,
128
+ ( "PostCropVignetteFeather" , "vignetteFeather" ) ,
129
+ ( "PostCropVignetteRoundness" , "vignetteRoundness" ) ,
130
+ ( "GrainAmount" , "grainAmount" ) ,
131
+ ( "GrainSize" , "grainSize" ) ,
132
+ ( "GrainFrequency" , "grainRoughness" ) ,
133
+ ( "ColorGradeBlending" , "blending" ) ,
134
+ ] ;
135
+
136
+ for ( xmp_key, rr_key) in mappings {
137
+ if let Some ( raw_val) = attrs. get ( xmp_key) {
138
+ if let Some ( num) = parse_num ( raw_val. trim_start_matches ( '+' ) ) {
139
+ if let Some ( json_val) = num_to_json ( num) {
140
+ if rr_key == "blending" {
141
+ color_grading_map. insert ( rr_key. to_string ( ) , json_val) ;
142
+ } else {
143
+ adjustments. insert ( rr_key. to_string ( ) , json_val) ;
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ if let Some ( shadows_val) = get_attr_as_f64 ( & attrs, "Shadows2012" ) {
151
+ let adjusted_shadows = ( shadows_val * 1.5 ) . min ( 100.0 ) ;
152
+ adjustments. insert ( "shadows" . to_string ( ) , json ! ( adjusted_shadows) ) ;
153
+ }
154
+ adjustments. insert ( "sharpness" . to_string ( ) , json ! ( 0 ) ) ;
155
+
156
+ if let Some ( adjusted_k) = get_attr_as_f64 ( & attrs, "Temperature" ) {
157
+ const AS_SHOT_DEFAULT : f64 = 5500.0 ;
158
+ const MAX_MIRED_SHIFT : f64 = 150.0 ;
159
+ let as_shot_k = get_attr_as_f64 ( & attrs, "AsShotTemperature" ) . unwrap_or ( AS_SHOT_DEFAULT ) ;
160
+ let mired_adjusted = 1_000_000.0 / adjusted_k;
161
+ let mired_as_shot = 1_000_000.0 / as_shot_k;
162
+ let mired_delta = mired_adjusted - mired_as_shot;
163
+ let temp_value = ( -mired_delta / MAX_MIRED_SHIFT ) * 100.0 ;
164
+ adjustments. insert ( "temperature" . to_string ( ) , json ! ( temp_value. clamp( -100.0 , 100.0 ) ) ) ;
165
+ }
166
+
167
+ if let Some ( tint_val) = get_attr_as_f64 ( & attrs, "Tint" ) {
168
+ let scaled_tint = ( tint_val / 150.0 ) * 100.0 ;
169
+ adjustments. insert ( "tint" . to_string ( ) , json ! ( scaled_tint. clamp( -100.0 , 100.0 ) ) ) ;
170
+ }
171
+
172
+ let colors = [
173
+ ( "Red" , "reds" ) , ( "Orange" , "oranges" ) , ( "Yellow" , "yellows" ) ,
174
+ ( "Green" , "greens" ) , ( "Aqua" , "aquas" ) , ( "Blue" , "blues" ) ,
175
+ ( "Purple" , "purples" ) , ( "Magenta" , "magentas" ) ,
176
+ ] ;
177
+ for ( src, dst) in colors {
178
+ let mut color_map = Map :: new ( ) ;
179
+ if let Some ( raw) = attrs. get ( & format ! ( "HueAdjustment{}" , src) ) {
180
+ if let Some ( num) = parse_num ( raw. trim_start_matches ( '+' ) ) {
181
+ if let Some ( Value :: Number ( n) ) = num_to_json ( num) {
182
+ if let Some ( val_f64) = n. as_f64 ( ) {
183
+ let halved_hue = val_f64 * 0.5 ;
184
+ color_map. insert ( "hue" . to_string ( ) , json ! ( halved_hue) ) ;
185
+ }
186
+ }
187
+ }
188
+ }
189
+ if let Some ( raw) = attrs. get ( & format ! ( "SaturationAdjustment{}" , src) ) {
190
+ if let Some ( num) = parse_num ( raw. trim_start_matches ( '+' ) ) {
191
+ if let Some ( json_val) = num_to_json ( num) { color_map. insert ( "saturation" . to_string ( ) , json_val) ; }
192
+ }
193
+ }
194
+ if let Some ( raw) = attrs. get ( & format ! ( "LuminanceAdjustment{}" , src) ) {
195
+ if let Some ( num) = parse_num ( raw. trim_start_matches ( '+' ) ) {
196
+ if let Some ( json_val) = num_to_json ( num) { color_map. insert ( "luminance" . to_string ( ) , json_val) ; }
197
+ }
198
+ }
199
+ if !color_map. is_empty ( ) {
200
+ hsl_map. insert ( dst. to_string ( ) , Value :: Object ( color_map) ) ;
201
+ }
202
+ }
203
+ if !hsl_map. is_empty ( ) {
204
+ adjustments. insert ( "hsl" . to_string ( ) , Value :: Object ( hsl_map) ) ;
205
+ }
206
+
207
+ let mut shadows_map = Map :: new ( ) ;
208
+ let mut midtones_map = Map :: new ( ) ;
209
+ let mut highlights_map = Map :: new ( ) ;
210
+ if let Some ( raw) = attrs. get ( "SplitToningShadowHue" ) { if let Some ( num) = parse_num ( raw) { if let Some ( json_val) = num_to_json ( num) { shadows_map. insert ( "hue" . to_string ( ) , json_val) ; } } }
211
+ if let Some ( raw) = attrs. get ( "ColorGradeMidtoneHue" ) { if let Some ( num) = parse_num ( raw) { if let Some ( json_val) = num_to_json ( num) { midtones_map. insert ( "hue" . to_string ( ) , json_val) ; } } }
212
+ if let Some ( raw) = attrs. get ( "SplitToningHighlightHue" ) { if let Some ( num) = parse_num ( raw) { if let Some ( json_val) = num_to_json ( num) { highlights_map. insert ( "hue" . to_string ( ) , json_val) ; } } }
213
+ if let Some ( raw) = attrs. get ( "SplitToningShadowSaturation" ) { if let Some ( num) = parse_num ( raw) { if let Some ( json_val) = num_to_json ( num) { shadows_map. insert ( "saturation" . to_string ( ) , json_val) ; } } }
214
+ if let Some ( raw) = attrs. get ( "ColorGradeMidtoneSat" ) { if let Some ( num) = parse_num ( raw) { if let Some ( json_val) = num_to_json ( num) { midtones_map. insert ( "saturation" . to_string ( ) , json_val) ; } } }
215
+ if let Some ( raw) = attrs. get ( "SplitToningHighlightSaturation" ) { if let Some ( num) = parse_num ( raw) { if let Some ( json_val) = num_to_json ( num) { highlights_map. insert ( "saturation" . to_string ( ) , json_val) ; } } }
216
+ if let Some ( raw) = attrs. get ( "ColorGradeShadowLum" ) { if let Some ( num) = parse_num ( raw) { if let Some ( json_val) = num_to_json ( num) { shadows_map. insert ( "luminance" . to_string ( ) , json_val) ; } } }
217
+ if let Some ( raw) = attrs. get ( "ColorGradeMidtoneLum" ) { if let Some ( num) = parse_num ( raw) { if let Some ( json_val) = num_to_json ( num) { midtones_map. insert ( "luminance" . to_string ( ) , json_val) ; } } }
218
+ if let Some ( raw) = attrs. get ( "ColorGradeHighlightLum" ) { if let Some ( num) = parse_num ( raw) { if let Some ( json_val) = num_to_json ( num) { highlights_map. insert ( "luminance" . to_string ( ) , json_val) ; } } }
219
+ if let Some ( raw) = attrs. get ( "SplitToningBalance" ) { if let Some ( num) = parse_num ( raw) { if let Some ( json_val) = num_to_json ( num) { color_grading_map. insert ( "balance" . to_string ( ) , json_val) ; } } }
220
+ if !shadows_map. is_empty ( ) { color_grading_map. insert ( "shadows" . to_string ( ) , Value :: Object ( shadows_map) ) ; }
221
+ if !midtones_map. is_empty ( ) { color_grading_map. insert ( "midtones" . to_string ( ) , Value :: Object ( midtones_map) ) ; }
222
+ if !highlights_map. is_empty ( ) { color_grading_map. insert ( "highlights" . to_string ( ) , Value :: Object ( highlights_map) ) ; }
223
+ if !color_grading_map. is_empty ( ) { adjustments. insert ( "colorGrading" . to_string ( ) , Value :: Object ( color_grading_map) ) ; }
224
+
225
+ let curve_mappings = [
226
+ ( "ToneCurvePV2012" , "luma" ) , ( "ToneCurvePV2012Red" , "red" ) ,
227
+ ( "ToneCurvePV2012Green" , "green" ) , ( "ToneCurvePV2012Blue" , "blue" ) ,
228
+ ] ;
229
+ for ( xmp_curve, rr_curve) in curve_mappings {
230
+ if let Some ( points) = extract_tone_curve_points ( xmp_content, xmp_curve) {
231
+ curves_map. insert ( rr_curve. to_string ( ) , Value :: Array ( points) ) ;
232
+ }
233
+ }
234
+ if !curves_map. is_empty ( ) {
235
+ adjustments. insert ( "curves" . to_string ( ) , Value :: Object ( curves_map) ) ;
236
+ }
237
+
238
+ let preset_name = extract_xmp_name ( xmp_content) . unwrap_or_else ( || "Imported Preset" . to_string ( ) ) ;
239
+
240
+ Ok ( Preset {
241
+ id : Uuid :: new_v4 ( ) . to_string ( ) ,
242
+ name : preset_name,
243
+ adjustments : Value :: Object ( adjustments) ,
244
+ } )
245
+ }
0 commit comments