use log::info;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use image::{ImageBuffer, Rgb, DynamicImage};
use crate::extractor::array_strategy::ArrayData;
use crate::tiff::{TiffReader, TiffBuilder};
use crate::tiff::errors::{TiffError, TiffResult};
use crate::tiff::ifd::IFD;
use crate::tiff::constants::{tags, photometric};
use crate::utils::logger::Logger;
use crate::utils::tiff_extraction_utils;
use super::region::Region;
use super::tile_reader::TileReader;
use super::strip_reader::StripReader;
use super::extractor_strategy::ExtractorStrategy;
pub struct TiffExtractorStrategy<'a> {
logger: &'a Logger,
reader: TiffReader<'a>,
}
impl<'a> TiffExtractorStrategy<'a> {
pub fn new(logger: &'a Logger) -> Self {
TiffExtractorStrategy {
logger,
reader: TiffReader::new(logger),
}
}
}
impl<'a> ExtractorStrategy for TiffExtractorStrategy<'a> {
fn extract_to_file(&mut self, tiff_path: &str, output_path: &str,
region: Option<Region>, shape: Option<&str>) -> TiffResult<()> {
info!("Extracting image from {} to {}", tiff_path, output_path);
let tiff = self.reader.load(tiff_path)?;
if tiff.ifds.is_empty() {
return Err(TiffError::GenericError("No IFDs found in TIFF file".to_string()));
}
let original_ifd = &tiff.ifds[0];
let (bits_per_sample, photometric, samples_per_pixel) =
tiff_extraction_utils::get_tiff_image_properties(original_ifd);
let file_path = self.reader.get_file_path().unwrap_or(tiff_path);
let (pixel_scale, tiepoint) = tiff_extraction_utils::read_geotiff_info(
original_ifd, &self.reader, file_path);
let extracted_region = region.unwrap_or_else(|| {
if let Some((width, height)) = original_ifd.get_dimensions() {
Region::new(0, 0, width as u32, height as u32)
} else {
Region::new(0, 0, 0, 0)
}
});
info!("Extracting region: x={}, y={}, width={}, height={}",
extracted_region.x, extracted_region.y,
extracted_region.width, extracted_region.height);
let image = self.extract_image(tiff_path, Some(extracted_region))?;
let final_image = if let Some(shape_str) = shape {
if shape_str.to_lowercase() == "circle" {
crate::utils::mask_utils::apply_shape_mask(&image, shape_str)
} else {
image
}
} else {
image
};
if let Some(shape_str) = shape {
if shape_str.to_lowercase() == "circle" {
return crate::utils::mask_utils::save_shaped_image(&final_image, output_path, shape_str);
}
}
let image_for_tiff = final_image.to_rgb8();
let mut builder = TiffBuilder::new(self.logger, false);
let new_ifd = IFD::new(0, 0);
let ifd_index = builder.add_ifd(new_ifd);
tiff_extraction_utils::setup_tiff_tags(&mut builder, ifd_index, original_ifd, &final_image)?;
builder.copy_statistics_tags(ifd_index, original_ifd);
builder.copy_geotiff_tags(ifd_index, original_ifd, &mut self.reader)?;
builder.adjust_geotiff_for_region(ifd_index, &extracted_region, &pixel_scale, &tiepoint)?;
if samples_per_pixel == 1 {
tiff_extraction_utils::process_grayscale_image(&final_image, &mut builder, ifd_index, bits_per_sample)?;
} else {
tiff_extraction_utils::process_rgb_image(&final_image, &mut builder, ifd_index)?;
}
let nodata_value = tiff_extraction_utils::extract_nodata_value(original_ifd, &self.reader);
let metadata_str = tiff_extraction_utils::extract_gdal_metadata(original_ifd, &self.reader);
info!("Setting NoData value: '{}'", nodata_value);
builder.add_nodata_tag(ifd_index, &nodata_value);
builder.add_gdal_metadata_tag(ifd_index, metadata_str.as_deref(), &nodata_value);
tiff_extraction_utils::set_photometric_interpretation(
&mut builder, ifd_index, photometric::BLACK_IS_ZERO);
builder.write(output_path)?;
info!("Saved {}x{} image to {} with adjusted GeoTIFF metadata",
final_image.width(), final_image.height(), output_path);
Ok(())
}
fn extract_image(&mut self, tiff_path: &str,
region: Option<Region>) -> TiffResult<DynamicImage> {
let tiff = self.reader.load(tiff_path)?;
if tiff.ifds.is_empty() {
return Err(TiffError::GenericError("No IFDs found in TIFF file".to_string()));
}
let ifd = &tiff.ifds[0];
let region = tiff_extraction_utils::determine_extraction_region(region, ifd)?;
info!("Extracting region: ({}, {}) with size {}x{}",
region.x, region.y, region.width, region.height);
let file = File::open(tiff_path)?;
let reader = BufReader::with_capacity(1024 * 1024, file);
let mut image = ImageBuffer::<Rgb<u8>, Vec<u8>>::new(region.width, region.height);
let is_tiled = ifd.has_tag(tags::TILE_WIDTH) && ifd.has_tag(tags::TILE_LENGTH);
if is_tiled {
let mut tile_reader = TileReader::new(reader, ifd, &self.reader);
tile_reader.extract(&mut image, region)?;
} else {
let mut strip_reader = StripReader::new(reader, ifd, &self.reader);
strip_reader.extract(&mut image, region)?;
}
Ok(DynamicImage::ImageRgb8(image))
}
fn extract_to_array(&mut self, source_path: &str, output_path: &str,
format: &str, region: Option<Region>) -> TiffResult<()> {
info!("TIFF strategy: Converting image to array format {}", format);
let array_data = self.extract_array_data(source_path, region)?;
array_data.save_to_file(output_path, format)
}
fn extract_array_data(&mut self, source_path: &str,
region: Option<Region>) -> TiffResult<ArrayData> {
info!("TIFF strategy: Extracting array data to memory");
let image = self.extract_image(source_path, region)?;
Ok(ArrayData::from_image(&image))
}
fn supports_format(&self, file_path: &str) -> bool {
let extension = Path::new(file_path)
.extension()
.and_then(std::ffi::OsStr::to_str)
.unwrap_or("")
.to_lowercase();
matches!(extension.as_str(), "tif" | "tiff")
}
}