@@ -20,37 +20,133 @@ impl PreviewPlugin for TexPreview {
2020 filename : & str ,
2121 data : & [ u8 ] ,
2222 ctx : & egui:: Context ,
23- _fetch : & super :: SiblingFetcher < ' _ > ,
23+ fetch : & super :: SiblingFetcher < ' _ > ,
2424 ) -> PreviewOutput {
25- let mut out = PreviewOutput :: new ( ) ;
25+ // Try the file as-is first. paylow/paymed are mip-stream variants we
26+ // can't decode yet, so on failure we fall back to previewing the
27+ // matching payload/ twin. Export Raw still writes the original bytes.
28+ if let Some ( ( rgba, w, h, fmt) ) = crate :: tex_preview:: decode_tex ( data, filename) {
29+ return build_output ( ctx, rgba, w, h, fmt, filename. to_owned ( ) , None , None ) ;
30+ }
31+
32+ if let Some ( ( kind, twin_path) ) = payload_twin ( filename) {
33+ if let Some ( twin_bytes) = ( fetch. by_name ) ( & twin_path) {
34+ if let Some ( ( rgba, w, h, fmt) ) =
35+ crate :: tex_preview:: decode_tex ( & twin_bytes, & twin_path)
36+ {
37+ let note = format ! (
38+ "{kind}/ mipmap stream isn't decoded yet — preview is from \
39+ the matching payload/ variant. Export Raw still writes \
40+ the original {kind} bytes."
41+ ) ;
42+ return build_output (
43+ ctx,
44+ rgba,
45+ w,
46+ h,
47+ fmt,
48+ twin_path,
49+ Some ( twin_bytes) ,
50+ Some ( note) ,
51+ ) ;
52+ }
53+ }
54+ }
2655
27- let Some ( ( rgba, w, h, fmt) ) = crate :: tex_preview:: decode_tex ( data, filename) else {
28- out. text = Some (
29- ".tex header could not be decoded — unknown dimensions or unsupported BC format."
30- . into ( ) ,
31- ) ;
32- return out;
33- } ;
34-
35- let color_image = egui:: ColorImage :: from_rgba_unmultiplied ( [ w as usize , h as usize ] , & rgba) ;
36- out. texture =
37- Some ( ctx. load_texture ( "tex_preview" , color_image, egui:: TextureOptions :: default ( ) ) ) ;
38- out. texture_pixels = Some ( ( rgba, w, h) ) ;
39- out. text = Some ( format ! (
40- "D4 .tex texture\n {w}×{h} {fmt}\n \n Decoded from raw block-compressed data."
41- ) ) ;
42-
43- let filename = filename. to_owned ( ) ;
44- out. extra_exports . push ( ExportAction {
45- label : "Export As PNG" ,
46- default_extension : "png" ,
47- filter_name : "PNG image" ,
48- build : Arc :: new ( move |data, _path| {
49- let ( rgba, w, h, _fmt) = crate :: tex_preview:: decode_tex ( data, & filename)
50- . ok_or_else ( || "tex decode failed" . to_string ( ) ) ?;
51- crate :: preview:: encode_png ( & rgba, w, h)
52- } ) ,
53- } ) ;
56+ let mut out = PreviewOutput :: new ( ) ;
57+ out. text = Some (
58+ ".tex header could not be decoded — unknown dimensions or unsupported BC format."
59+ . into ( ) ,
60+ ) ;
5461 out
5562 }
5663}
64+
65+ /// If `filename` lives under `base/paylow/` or `base/paymed/`, return
66+ /// `(tier_label, twin_path_under_base/payload/)` so the plugin can fetch
67+ /// the full-resolution variant for preview.
68+ fn payload_twin ( filename : & str ) -> Option < ( & ' static str , String ) > {
69+ let lower = filename. to_ascii_lowercase ( ) ;
70+ if let Some ( rest) = lower. strip_prefix ( "base/paylow/" ) {
71+ Some ( ( "paylow" , format ! ( "base/payload/{rest}" ) ) )
72+ } else if let Some ( rest) = lower. strip_prefix ( "base/paymed/" ) {
73+ Some ( ( "paymed" , format ! ( "base/payload/{rest}" ) ) )
74+ } else {
75+ None
76+ }
77+ }
78+
79+ #[ allow( clippy:: too_many_arguments) ]
80+ fn build_output (
81+ ctx : & egui:: Context ,
82+ rgba : Vec < u8 > ,
83+ w : u32 ,
84+ h : u32 ,
85+ fmt : & ' static str ,
86+ decode_filename : String ,
87+ decoded_bytes_for_export : Option < Vec < u8 > > ,
88+ note : Option < String > ,
89+ ) -> PreviewOutput {
90+ let mut out = PreviewOutput :: new ( ) ;
91+ let color_image = egui:: ColorImage :: from_rgba_unmultiplied ( [ w as usize , h as usize ] , & rgba) ;
92+ out. texture =
93+ Some ( ctx. load_texture ( "tex_preview" , color_image, egui:: TextureOptions :: default ( ) ) ) ;
94+ out. texture_pixels = Some ( ( rgba, w, h) ) ;
95+
96+ let mut text =
97+ format ! ( "D4 .tex texture\n {w}×{h} {fmt}\n \n Decoded from raw block-compressed data." ) ;
98+ if let Some ( n) = note {
99+ text. push_str ( "\n \n " ) ;
100+ text. push_str ( & n) ;
101+ }
102+ out. text = Some ( text) ;
103+
104+ out. extra_exports . push ( ExportAction {
105+ label : "Export As PNG" ,
106+ default_extension : "png" ,
107+ filter_name : "PNG image" ,
108+ build : Arc :: new ( move |data, _path| {
109+ // PNG export mirrors what the user *sees*. When we previewed a
110+ // payload twin, encode that; otherwise fall back to the file
111+ // bytes the framework hands us.
112+ let bytes: & [ u8 ] = decoded_bytes_for_export. as_deref ( ) . unwrap_or ( data) ;
113+ let ( rgba, w, h, _fmt) = crate :: tex_preview:: decode_tex ( bytes, & decode_filename)
114+ . ok_or_else ( || "tex decode failed" . to_string ( ) ) ?;
115+ crate :: preview:: encode_png ( & rgba, w, h)
116+ } ) ,
117+ } ) ;
118+ out
119+ }
120+
121+ #[ cfg( test) ]
122+ mod tests {
123+ use super :: payload_twin;
124+
125+ #[ test]
126+ fn paylow_maps_to_payload_twin ( ) {
127+ let ( tier, twin) =
128+ payload_twin ( "base/paylow/Texture/warlock_sigilOfSummons_Color.tex" ) . unwrap ( ) ;
129+ assert_eq ! ( tier, "paylow" ) ;
130+ assert_eq ! (
131+ twin,
132+ "base/payload/texture/warlock_sigilofsummons_color.tex"
133+ ) ;
134+ }
135+
136+ #[ test]
137+ fn paymed_maps_to_payload_twin ( ) {
138+ let ( tier, twin) = payload_twin ( "base/paymed/Texture/some_file.tex" ) . unwrap ( ) ;
139+ assert_eq ! ( tier, "paymed" ) ;
140+ assert_eq ! ( twin, "base/payload/texture/some_file.tex" ) ;
141+ }
142+
143+ #[ test]
144+ fn payload_returns_none ( ) {
145+ assert ! ( payload_twin( "base/payload/Texture/foo.tex" ) . is_none( ) ) ;
146+ }
147+
148+ #[ test]
149+ fn unrelated_path_returns_none ( ) {
150+ assert ! ( payload_twin( "World/Maps/Azeroth/foo.adt" ) . is_none( ) ) ;
151+ }
152+ }
0 commit comments