gistools/readers/geotiff/
color.rs

1use super::{
2    Raster,
3    constants::{ExtraSamplesValues, PhotometricInterpretations},
4};
5use alloc::{vec, vec::Vec};
6use libm::{fmax, fmin, pow};
7
8/// Converts photometric interpretation to samples
9///
10/// ## Parameters
11/// - `pi`: photometric interpretation
12/// - `bitsPerSample`: bits per sample
13/// - `extraSamples`: extra samples
14///
15/// ## Returns
16/// Sample output
17pub fn build_samples(
18    pi: PhotometricInterpretations,
19    bits_per_sample: Option<Vec<u16>>,
20    extra_samples: Option<ExtraSamplesValues>,
21) -> Vec<u16> {
22    let extra_samples = extra_samples.unwrap_or(ExtraSamplesValues::Unspecified);
23    let bits_per_sample = bits_per_sample.unwrap_or(vec![0]);
24    let mut samples;
25    if pi == PhotometricInterpretations::RGB {
26        samples = vec![0, 1, 2, 3];
27        // support alpha if it exists
28        if extra_samples != ExtraSamplesValues::Unspecified {
29            samples = vec![];
30            for i in 0..bits_per_sample.len() {
31                samples.push(i as u16);
32            }
33        }
34    } else {
35        match pi {
36            PhotometricInterpretations::WhiteIsZero
37            | PhotometricInterpretations::BlackIsZero
38            | PhotometricInterpretations::Palette => {
39                samples = vec![0];
40            }
41            PhotometricInterpretations::CMYK => {
42                samples = vec![0, 1, 2, 3];
43            }
44            PhotometricInterpretations::YCbCr
45            | PhotometricInterpretations::CIELab
46            | PhotometricInterpretations::ICCLab => {
47                samples = vec![0, 1, 2];
48            }
49            _ => panic!("Invalid or unsupported photometric interpretation."),
50        }
51    }
52
53    samples
54}
55
56/// Convert color space raster to RGB
57/// TODO: ICCLAB, ITULAB
58///
59/// ## Parameters
60/// - `pi`: photometric interpretation
61/// - `raster_data`: raster data
62/// - `max`: maximum value if needed
63/// - `color_map`: color map if needed
64pub fn convert_color_space(
65    pi: PhotometricInterpretations,
66    raster: &mut Raster,
67    max: f64,
68    color_map: Option<Vec<u16>>,
69) {
70    if pi == PhotometricInterpretations::RGB {
71    } else if pi == PhotometricInterpretations::WhiteIsZero {
72        from_white_is_zero(raster, max);
73    } else if pi == PhotometricInterpretations::BlackIsZero {
74        from_black_is_zero(raster, max);
75    } else if pi == PhotometricInterpretations::Palette {
76        from_palette(raster, color_map);
77    } else if pi == PhotometricInterpretations::CMYK {
78        from_cmyk(raster);
79    } else if pi == PhotometricInterpretations::YCbCr {
80        from_ycb_cr(raster);
81    } else if pi == PhotometricInterpretations::CIELab {
82        from_cei_lab(raster);
83    } else {
84        panic!("Unsupported photometric interpretation {:?}.", pi);
85    }
86}
87
88/// Converts raster with white is zero and max is one to RGB
89///
90/// ## Parameters
91/// - `raster`: raster
92/// - `max`: maximum value
93pub fn from_white_is_zero(raster: &mut Raster, max: f64) {
94    let mut rbgdata = vec![0_f64; raster.width * raster.height * 3];
95    let data = &raster.data;
96    let mut i = 0;
97    let mut j = 0;
98    while i < data.len() {
99        let value = 256. - (data[i] / max) * 256.;
100        rbgdata[j] = value;
101        rbgdata[j + 1] = value;
102        rbgdata[j + 2] = value;
103
104        i += 1;
105        j += 3;
106    }
107    raster.data = rbgdata;
108}
109
110/// Converts raster with black is zero and max is one to RGB
111///
112/// ## Parameters
113/// - `raster`: raster
114/// - `max`: maximum value
115pub fn from_black_is_zero(raster: &mut Raster, max: f64) {
116    let mut rbgdata = vec![0_f64; raster.width * raster.height * 3];
117    let data = &raster.data;
118    let mut i = 0;
119    let mut j = 0;
120    while i < data.len() {
121        let value = (data[i] / max) * 256.;
122        rbgdata[j] = value;
123        rbgdata[j + 1] = value;
124        rbgdata[j + 2] = value;
125
126        i += 1;
127        j += 3;
128    }
129    raster.data = rbgdata;
130}
131
132/// Converts raster with a color map to RGB
133///
134/// ## Parameters
135/// - `raster`: raster
136/// - `color_map`: color map
137pub fn from_palette(raster: &mut Raster, color_map: Option<Vec<u16>>) {
138    let color_map = color_map.unwrap_or_default();
139    let mut rbgdata = vec![0_f64; raster.width * raster.height * 3];
140    let data = &raster.data;
141    let green_offset = color_map.len() / 3;
142    let blue_offset = (color_map.len() / 3) * 2;
143    let mut i = 0;
144    let mut j = 0;
145    while i < data.len() {
146        let map_index = data[i] as usize;
147        rbgdata[j] = (color_map[map_index] as f64 / 65_536.) * 256.;
148        rbgdata[j + 1] = (color_map[map_index + green_offset] as f64 / 65_536.) * 256.;
149        rbgdata[j + 2] = (color_map[map_index + blue_offset] as f64 / 65_536.) * 256.;
150
151        i += 1;
152        j += 3;
153    }
154    raster.data = rbgdata;
155}
156
157/// Converts CMYK to RGB
158///
159/// ## Parameters
160/// - `raster`: CMYK raster
161pub fn from_cmyk(raster: &mut Raster) {
162    let mut rbgdata = vec![0_f64; raster.width * raster.height * 3];
163    let data = &raster.data;
164    let mut i = 0;
165    let mut j = 0;
166    while i < data.len() {
167        let c = data[i] / 255.0;
168        let m = data[i + 1] / 255.0;
169        let y = data[i + 2] / 255.0;
170        let k = data[i + 3] / 255.0;
171
172        rbgdata[j] = 255.0 * (1.0 - c) * (1.0 - k);
173        rbgdata[j + 1] = 255.0 * (1.0 - m) * (1.0 - k);
174        rbgdata[j + 2] = 255.0 * (1.0 - y) * (1.0 - k);
175
176        i += 4;
177        j += 3;
178    }
179    raster.data = rbgdata;
180}
181
182/// Converts YCbCr to RGB
183///
184/// ## Parameters
185/// - `raster`: YCbCr raster
186pub fn from_ycb_cr(raster: &mut Raster) {
187    let mut rbgdata = vec![0_f64; raster.width * raster.height * 3];
188    let data = &raster.data;
189    let mut i = 0;
190    let mut j = 0;
191    while i < data.len() {
192        let y = data[i];
193        let cb = data[i + 1] - (0x80 as f64);
194        let cr = data[i + 2] - (0x80 as f64);
195
196        rbgdata[j] = y + 1.402 * cr;
197        rbgdata[j + 1] = y - 0.34414 * cb - 0.71414 * cr;
198        rbgdata[j + 2] = y + 1.772 * cb;
199        i += 3;
200        j += 3;
201    }
202    raster.data = rbgdata;
203}
204
205const XN: f64 = 0.95047;
206const YN: f64 = 1.0;
207const ZN: f64 = 1.08883;
208
209/// Converts CIELab to RGB
210/// <https://github.com/antimatter15/rgb-lab/blob/master/color.js>
211///
212/// ## Parameters
213/// - `raster`: CIELab raster
214pub fn from_cei_lab(raster: &mut Raster) {
215    let mut rbgdata = vec![0_f64; raster.width * raster.height * 3];
216    let data = &raster.data;
217
218    let mut l: f64;
219    let mut a_: f64;
220    let mut b_: f64;
221    let mut x: f64;
222    let mut y: f64;
223    let mut z: f64;
224    let mut r: f64;
225    let mut g: f64;
226    let mut b: f64;
227    let mut i = 0;
228    let mut j = 0;
229    while i < data.len() {
230        l = data[i];
231        a_ = (data[i + 1] as i8) as f64; // conversion from uint8 to int8
232        b_ = (data[i + 2] as i8) as f64; // same
233        y = (l + 16.) / 116.;
234        x = a_ / 500. + y;
235        z = y - b_ / 200.;
236
237        x = XN * if x * x * x > 0.008856 { x * x * x } else { (x - 16. / 116.) / 7.787 };
238        y = YN * if y * y * y > 0.008856 { y * y * y } else { (y - 16. / 116.) / 7.787 };
239        z = ZN * if z * z * z > 0.008856 { z * z * z } else { (z - 16. / 116.) / 7.787 };
240
241        r = x * 3.2406 + y * -1.5372 + z * -0.4986;
242        g = x * -0.9689 + y * 1.8758 + z * 0.0415;
243        b = x * 0.0557 + y * -0.204 + z * 1.057;
244
245        r = if r > 0.0031308 { 1.055 * pow(r, (1. / 2.4) - 0.055) } else { 12.92 * r };
246        g = if g > 0.0031308 { 1.055 * pow(g, (1. / 2.4) - 0.055) } else { 12.92 * g };
247        b = if b > 0.0031308 { 1.055 * pow(b, (1. / 2.4) - 0.055) } else { 12.92 * b };
248
249        rbgdata[j] = fmax(0., fmin(1., r)) * 255.;
250        rbgdata[j + 1] = fmax(0., fmin(1., g)) * 255.;
251        rbgdata[j + 2] = fmax(0., fmin(1., b)) * 255.;
252        i += 3;
253        j += 3;
254    }
255    raster.data = rbgdata;
256}