use log::{info, warn, debug};
use std::path::Path;
use crate::tiff::errors::{TiffResult, TiffError};
use crate::tiff::colormap::{ColorMap, ColorMapReader, RgbColor, ColorMapEntry};
use crate::utils::logger::Logger;
use crate::extractor::Region;
use crate::tiff::TiffReader;
use crate::tiff::geo_key_parser::GeoKeyParser;
use crate::utils::reference_utils::add_georeferencing_to_builder;
pub fn find_color_for_value(colormap: &ColorMap, value: u16) -> RgbColor {
if value == 255 {
return RgbColor::new(255, 255, 255); }
if colormap.entries.is_empty() {
return RgbColor::new(0, 0, 0);
}
for entry in &colormap.entries {
if entry.value == value {
return entry.color;
}
}
if colormap.map_type == "ramp" && colormap.entries.len() > 1 {
return interpolate_color(colormap, value);
}
find_nearest_color(colormap, value)
}
pub fn interpolate_color(colormap: &ColorMap, value: u16) -> RgbColor {
let (lower_entry, upper_entry) = find_bracketing_entries(colormap, value);
if value <= lower_entry.value {
return lower_entry.color;
}
if value >= upper_entry.value {
return upper_entry.color;
}
let range = upper_entry.value as f32 - lower_entry.value as f32;
let t = (value as f32 - lower_entry.value as f32) / range;
let r = (lower_entry.color.r as f32 * (1.0 - t) + upper_entry.color.r as f32 * t) as u8;
let g = (lower_entry.color.g as f32 * (1.0 - t) + upper_entry.color.g as f32 * t) as u8;
let b = (lower_entry.color.b as f32 * (1.0 - t) + upper_entry.color.b as f32 * t) as u8;
RgbColor::new(r, g, b)
}
pub fn find_bracketing_entries<'a>(colormap: &'a ColorMap, value: u16) -> (&'a ColorMapEntry, &'a ColorMapEntry) {
let mut lower_entry = &colormap.entries[0];
let mut upper_entry = &colormap.entries[colormap.entries.len()-1];
for i in 0..colormap.entries.len()-1 {
if colormap.entries[i].value <= value && colormap.entries[i+1].value > value {
lower_entry = &colormap.entries[i];
upper_entry = &colormap.entries[i+1];
break;
}
}
(lower_entry, upper_entry)
}
pub fn find_nearest_color(colormap: &ColorMap, value: u16) -> RgbColor {
let mut nearest_entry = &colormap.entries[0];
let mut min_distance = u16::MAX;
for entry in &colormap.entries {
let distance = if entry.value > value {
entry.value - value
} else {
value - entry.value
};
if distance < min_distance {
min_distance = distance;
nearest_entry = entry;
}
}
nearest_entry.color
}
pub fn apply_colormap_to_image(
grayscale: &image::GrayImage,
colormap: &ColorMap
) -> image::RgbImage {
let width = grayscale.width();
let height = grayscale.height();
let mut rgb_image = image::RgbImage::new(width, height);
for y in 0..height {
for x in 0..width {
let pixel = grayscale.get_pixel(x, y);
let value = pixel[0] as u16;
let color = find_color_for_value(colormap, value);
rgb_image.put_pixel(x, y, image::Rgb([color.r, color.g, color.b]));
}
}
rgb_image
}
pub fn extract_colormap(tiff_path: &str, output_path: &str, logger: &Logger) -> TiffResult<()> {
info!("Extracting colormap from {} to {}", tiff_path, output_path);
let colormap_reader = ColorMapReader::new(logger);
let colormap = colormap_reader.read_from_tiff(tiff_path)?;
let extension = Path::new(output_path)
.extension()
.map(|ext| ext.to_string_lossy().to_lowercase())
.unwrap_or_default();
let layer_name = Path::new(tiff_path)
.file_stem()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| "layer".to_string());
if extension != "sld" {
warn!("Unknown colormap format '{}', defaulting to SLD", extension);
}
colormap.to_sld_file(output_path, &layer_name)?;
info!("Colormap extracted and saved to {}", output_path);
colormap.print();
Ok(())
}
pub fn save_colorized_tiff(
rgb_image: image::RgbImage,
output_path: &str,
input_path: &str,
region: Option<Region>,
logger: &Logger,
shape: Option<&str>
) -> TiffResult<()> {
let path = std::path::Path::new(output_path);
let extension = path.extension()
.map(|ext| ext.to_string_lossy().to_lowercase())
.unwrap_or_default();
let dynamic_image = image::DynamicImage::ImageRgb8(rgb_image);
let final_image = if let Some(shape_str) = shape {
if shape_str.to_lowercase() == "circle" {
crate::utils::mask_utils::apply_shape_mask(&dynamic_image, shape_str)
} else {
dynamic_image
}
} else {
dynamic_image
};
if extension != "tif" && extension != "tiff" {
info!("Saving colorized image to {} format", extension);
if let Some(shape_str) = shape {
return crate::utils::mask_utils::save_shaped_image(&final_image, output_path, shape_str);
} else {
return match final_image.save(output_path) {
Ok(_) => Ok(()),
Err(e) => Err(TiffError::GenericError(format!("Failed to save image: {}", e)))
};
}
}
let width = final_image.width();
let height = final_image.height();
let rgb_data = final_image.to_rgb8().into_raw();
let mut builder = crate::tiff::TiffBuilder::new(logger, false);
let ifd_index = builder.add_ifd(crate::tiff::ifd::IFD::new(0, 0));
builder.add_basic_rgb_tags(ifd_index, width, height);
builder.setup_single_strip(ifd_index, rgb_data);
if let Some(extract_region) = region {
add_georeferencing_to_builder(&mut builder, ifd_index, &extract_region, input_path, logger)?;
}
info!("Writing RGB TIFF with applied colormap to {}", output_path);
builder.write(output_path)?;
Ok(())
}
pub fn load_colormap(colormap_path: &str, logger: &Logger) -> TiffResult<ColorMap> {
let colormap_reader = ColorMapReader::new(logger);
colormap_reader.read_file(colormap_path)
}