From 3dfddae5440dcf598fb2bb2f14b82c6a0d95ba18 Mon Sep 17 00:00:00 2001 From: sharkAndshar Date: Wed, 20 Nov 2024 12:16:13 +0800 Subject: [PATCH] wip --- martin/src/cog/errors.rs | 13 +- martin/src/cog/mod.rs | 450 +++++++++++++++++++++++++++------------ 2 files changed, 320 insertions(+), 143 deletions(-) diff --git a/martin/src/cog/errors.rs b/martin/src/cog/errors.rs index 13e06f407..be2dcffa2 100644 --- a/martin/src/cog/errors.rs +++ b/martin/src/cog/errors.rs @@ -39,7 +39,18 @@ pub enum CogError { WriteToPngFailed(PathBuf, EncodingError), #[error( - "The bit depth {} of the tiff file {1} is not accepted by png crate when encoding tif chunk data to png" + "The combination of bit depth {0} and colory typr {2:?} of the tiff file {1} is not accepted by png crate when encoding tif chunk data to png" )] PngBitDepthNotAccepted(u8, PathBuf, ColorType), + + #[error( + "The color type {0:?} and its bit depth of the tiff file {1} is not supported by png crate" + )] + NotSupportedColorTypeAndBitDepth(tiff::ColorType, PathBuf), + + #[error("Couldn't parse the {0} value in gdal metadata(tiff tag 42112) from {1}")] + ParseSTATISTICSValueFailed(String, PathBuf), + + #[error("todo")] + InvalidGdalMetaData(String, PathBuf), } diff --git a/martin/src/cog/mod.rs b/martin/src/cog/mod.rs index 6bf0be40a..ff40bdbdd 100644 --- a/martin/src/cog/mod.rs +++ b/martin/src/cog/mod.rs @@ -1,19 +1,21 @@ mod errors; +use bytemuck::NoUninit; pub use errors::CogError; -use png::BitDepth; +use png::{BitDepth, ColorType}; +use regex::Regex; use serde_yaml::with; +use tiff::encoder::colortype; use std::collections::HashMap; use std::fs::File; use std::path::Path; -use std::vec; use std::{fmt::Debug, path::PathBuf}; +use std::{u32, u8, vec}; use std::io::BufWriter; use tiff::decoder::{Decoder, DecodingResult}; use tiff::tags::Tag; -use tiff::ColorType; use async_trait::async_trait; use martin_tile_utils::{Format, TileCoord, TileInfo}; @@ -45,6 +47,8 @@ struct Meta { min_zoom: u8, max_zoom: u8, zoom_and_ifd: HashMap, + min_of_samples: Vec, + max_of_samples: Vec, zoom_and_tile_across_down: HashMap, } @@ -66,6 +70,7 @@ impl Source for CogSource { Box::new(self.clone()) } + #[allow(clippy::too_many_lines)] async fn get_tile( &self, xyz: TileCoord, @@ -114,183 +119,293 @@ impl Source for CogSource { let tile_width = decoder.chunk_dimensions().0; let tile_height = decoder.chunk_dimensions().1; let (data_width, data_height) = decoder.chunk_data_dimensions(tile_idx); - let png_bytes = match color_type { - ColorType::Gray(n_bits_per_sample) => gray_to_png( - decode_result, + + //do more research on the not u8 case, is this the right way to do it? + let png_file_bytes = match (decode_result, color_type) { + (DecodingResult::U8(vec), tiff::ColorType::Gray(_)) => to_png( + vec, + ColorType::GrayscaleAlpha, + BitDepth::Eight, + tile_width, + tile_height, + data_width, + data_height, + 1, + true, + &self.path, + ), + (DecodingResult::U8(vec), tiff::ColorType::RGB(_)) => to_png( + vec, + ColorType::Rgba, + BitDepth::Eight, tile_width, tile_height, data_width, data_height, + 3, + true, &self.path, ), - ColorType::RGB(_) => rgb_to_png( - decode_result, + (DecodingResult::U8(vec), tiff::ColorType::RGBA(_)) => to_png( + vec, + ColorType::Rgba, + BitDepth::Eight, tile_width, tile_height, data_width, data_height, + 4, false, &self.path, ), - ColorType::Palette(_) => todo!(), - ColorType::GrayA(_) => todo!(), - ColorType::RGBA(_) => rgb_to_png( - decode_result, + (DecodingResult::U16(vec), tiff::ColorType::Gray(_)) => to_png( + vec, + ColorType::GrayscaleAlpha, + BitDepth::Sixteen, + tile_width, + tile_height, + data_width, + data_height, + 1, + true, + &self.path, + ), + (DecodingResult::U16(vec), tiff::ColorType::RGB(_)) => to_png( + vec, + ColorType::Rgba, + BitDepth::Sixteen, + tile_width, + tile_height, + data_width, + data_height, + 3, + true, + &self.path, + ), + (DecodingResult::U16(vec), tiff::ColorType::RGBA(_)) => to_png( + vec, + ColorType::Rgba, + BitDepth::Sixteen, + tile_width, + tile_height, + data_width, + data_height, + 4, + false, + &self.path, + ), + (DecodingResult::U32(vec), tiff::ColorType::Gray(_)) => to_png( + scale_to_u8( + vec, + 1, + u32::MIN, + u32::MAX, + &self.meta.min_of_samples, + &self.meta.max_of_samples, + ), + ColorType::GrayscaleAlpha, + BitDepth::Eight, + tile_width, + tile_height, + data_width, + data_height, + 1, + true, + &self.path, + ), + (DecodingResult::F32(vec), tiff::ColorType::Gray(_)) => to_png( + scale_to_u8( + vec, + 1, + f32::MIN, + f32::MAX, + &self.meta.min_of_samples, + &self.meta.max_of_samples, + ), + ColorType::GrayscaleAlpha, + BitDepth::Eight, + tile_width, + tile_height, + data_width, + data_height, + 1, + true, + &self.path, + ), + (DecodingResult::F64(vec), tiff::ColorType::Gray(_)) => to_png( + scale_to_u8( + vec, + 1, + f64::MIN, + f64::MAX, + &self.meta.min_of_samples, + &self.meta.max_of_samples, + ), + ColorType::GrayscaleAlpha, + BitDepth::Eight, + tile_width, + tile_height, + data_width, + data_height, + 1, + true, + &self.path, + ), + (DecodingResult::I8(vec), tiff::ColorType::Gray(_)) => to_png( + scale_to_u8( + vec, + 1, + i8::MIN, + i8::MAX, + &self.meta.min_of_samples, + &self.meta.max_of_samples, + ), + ColorType::GrayscaleAlpha, + BitDepth::Eight, + tile_width, + tile_height, + data_width, + data_height, + 1, + true, + &self.path, + ), + (DecodingResult::I16(vec), tiff::ColorType::Gray(_)) => to_png( + scale_to_u8( + vec, + 1, + i16::MIN, + i16::MAX, + &self.meta.min_of_samples, + &self.meta.max_of_samples, + ), + ColorType::GrayscaleAlpha, + BitDepth::Eight, + tile_width, + tile_height, + data_width, + data_height, + 1, + true, + &self.path, + ), + (DecodingResult::I32(vec), tiff::ColorType::Gray(_)) => to_png( + scale_to_u8( + vec, + 1, + i32::MIN, + i32::MAX, + &self.meta.min_of_samples, + &self.meta.max_of_samples, + ), + ColorType::GrayscaleAlpha, + BitDepth::Eight, tile_width, tile_height, data_width, data_height, + 1, true, &self.path, ), - ColorType::CMYK(_) => todo!(), - ColorType::YCbCr(_) => todo!(), + (_, _) => Err(CogError::NotSupportedColorTypeAndBitDepth( + color_type, + self.path.clone(), + )), }?; - - Ok(png_bytes) + Ok(png_file_bytes) } } -fn gray_to_png( - data: DecodingResult, - tile_width: u32, - tile_height: u32, - data_width: u32, - data_height: u32, - path: &PathBuf, -) -> Result, CogError> { - let is_padded = data_width != tile_width; - - let mut buffer = Vec::new(); - let mut encoder = png::Encoder::new(BufWriter::new(&mut buffer), tile_width, tile_height); - encoder.set_color(png::ColorType::Grayscale); - match data { - DecodingResult::U8(vec) => { - encoder.set_depth(BitDepth::Eight); - - todo!() - } - DecodingResult::U16(vec) => todo!(), - DecodingResult::U32(vec) => todo!(), - DecodingResult::U64(vec) => todo!(), - DecodingResult::F32(vec) => todo!(), - DecodingResult::F64(vec) => todo!(), - DecodingResult::I8(vec) => todo!(), - DecodingResult::I16(vec) => todo!(), - DecodingResult::I32(vec) => todo!(), - DecodingResult::I64(vec) => todo!(), - } - todo!() +fn scale_to_u8( + vec: Vec, + samples_count: u8, + min_default: T, + max_default: T, + min_values: &Vec, + max_values: &Vec, +) -> Vec +where + T: Copy + NoUninit + PartialOrd + Into, +{ + vec.iter() + .enumerate() + .map(|(i, &value)| { + let sample_index = (i as u8 % samples_count) as usize; + let min = min_values + .get(sample_index) + .copied() + .unwrap_or_else(|| min_default.into()); + let max = max_values + .get(sample_index) + .copied() + .unwrap_or_else(|| max_default.into()); + let scaled_value: f64 = (value.into() - min) / (max - min) * 255.0; + let u8_value = scaled_value.round() as u8; + u8_value + }) + .collect() } -fn rgb_to_png( - data: DecodingResult, +#[allow(clippy::too_many_arguments)] +fn to_png>( + vec: Vec, + color_type: ColorType, + bit_depth: BitDepth, tile_width: u32, tile_height: u32, data_width: u32, data_height: u32, - with_alpha: bool, - path: &PathBuf, + components_count: u32, + need_extra_alpha: bool, + path: &Path, ) -> Result, CogError> { let is_padded = data_width != tile_width; - let mut buffer = Vec::new(); - let mut encoder = png::Encoder::new(BufWriter::new(&mut buffer), tile_width, tile_height); - - let components_count; - if with_alpha { - encoder.set_color(png::ColorType::Rgba); - components_count = 4; - } else { - encoder.set_color(png::ColorType::Rgb); - components_count = 3; - } - - match data { - DecodingResult::U8(vec) => { - { - encoder.set_depth(png::BitDepth::Eight); - let mut writer = encoder - .write_header() - .map_err(|e| CogError::WritePngHeaderFailed(path.clone(), e))?; - if is_padded { - //todo the no_data value should read from the tiff file, or from configuration - let arr = pad_data( - 0, - &vec, - tile_width, - tile_height, - data_width, - data_height, - components_count, - ); - writer - .write_image_data(&arr) - .map_err(|e| CogError::WriteToPngFailed(path.clone(), e))?; + { + let mut encoder = png::Encoder::new(BufWriter::new(&mut buffer), tile_width, tile_height); + encoder.set_color(color_type); + encoder.set_depth(bit_depth); + + let mut writer = encoder + .write_header() + .map_err(|e| CogError::WritePngHeaderFailed(path.to_path_buf(), e))?; + + let no_data = T::from(0); + + let data: Vec = match (is_padded, need_extra_alpha) { + (false, false) => vec, + (_, _) => { + let components_count_of_result = if need_extra_alpha { + components_count + 1 } else { - writer - .write_image_data(&vec) - .map_err(|e| CogError::WriteToPngFailed(path.clone(), e))?; + components_count + }; + let mut result = vec![ + no_data; + (tile_width * tile_height * (components_count_of_result)) + as usize + ]; + for row in 0..data_height { + for col in 0..data_width { + let idx = row * data_width * components_count + col * components_count; + let arr_idx = row * tile_width * components_count + col * components_count; + for component_idx in 0..components_count { + result[(arr_idx + component_idx) as usize] = + vec[(idx + component_idx) as usize]; + } + } } + result } - Ok(buffer) - } - DecodingResult::U16(vec) => { - encoder.set_depth(png::BitDepth::Sixteen); - - if is_padded { - //todo the no_data value should read from the tiff file - let arr = pad_data( - 0, - &vec, - tile_width, - tile_height, - data_width, - data_height, - components_count, - ); - let u8_vec: &[u8] = bytemuck::cast_slice(&arr); - let mut writer = encoder - .write_header() - .map_err(|e| CogError::WriteToPngFailed(path.clone(), e))?; - writer - .write_image_data(u8_vec) - .map_err(|e| CogError::WriteToPngFailed(path.clone(), e))?; - } else { - let u8_vec: &[u8] = bytemuck::cast_slice(&vec); - let mut writer = encoder - .write_header() - .map_err(|e| CogError::WriteToPngFailed(path.clone(), e))?; - writer - .write_image_data(u8_vec) - .map_err(|e| CogError::WriteToPngFailed(path.clone(), e))?; - } - Ok(buffer) - } - _ => todo!(), - } -} + }; -fn pad_data( - no_data: T, - vec: &[T], - tile_width: u32, - tile_height: u32, - data_width: u32, - data_height: u32, - components_count: u32, -) -> Vec { - let mut result = vec![no_data; (tile_width * tile_height * 3) as usize]; - for row in 0..data_height { - for col in 0..data_width { - let idx = row * data_width * 3 + col * 3; - let arr_idx = row * tile_width * 3 + col * 3; - for component_idx in 0..components_count { - result[(arr_idx + component_idx) as usize] = vec[(idx + component_idx) as usize]; - } - } + let u8_vec: &[u8] = bytemuck::cast_slice(&data); + writer + .write_image_data(u8_vec) + .map_err(|e| CogError::WriteToPngFailed(path.to_path_buf(), e))?; } - result + Ok(buffer) } #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] @@ -340,6 +455,8 @@ fn get_meta(path: &PathBuf) -> Result { .map_err(|e| CogError::InvalidTifFile(e, path.clone()))? .with_limits(tiff::decoder::Limits::unlimited()); + let (min_samples, max_samples) = get_minmax_of_samples(&mut decoder, path)?; + let images_ifd = get_images_ifd(&mut decoder); let mut zoom_and_ifd: HashMap = HashMap::new(); @@ -391,10 +508,59 @@ fn get_meta(path: &PathBuf) -> Result { min_zoom: *min_zoom, max_zoom: *max_zoom, zoom_and_ifd, + min_of_samples: min_samples, + max_of_samples: max_samples, zoom_and_tile_across_down, }) } +fn get_minmax_of_samples( + decoder: &mut Decoder, + path: &PathBuf, +) -> Result<(Vec, Vec), CogError> { + let gdal_metadata_tag = Tag::Unknown(42112); + let metadata = decoder.get_tag_ascii_string(gdal_metadata_tag); + + let mut min_values = Vec::new(); + let mut max_values = Vec::new(); + + if let Ok(metadata_text) = metadata { + if let Ok(re_min) = + Regex::new(r#"([\d.]+)"#) + { + for cap in re_min.captures_iter(&metadata_text) { + let value: f64 = cap[2].parse::().map_err(|_| { + CogError::ParseSTATISTICSValueFailed( + "STATISTICS_MINIMUM".to_string(), + path.clone(), + ) + })?; + min_values.push(value); + } + } else { + //todo log + } + + if let Ok(re_max) = + Regex::new(r#"([\d.]+)"#) + { + for cap in re_max.captures_iter(&metadata_text) { + let value: f64 = cap[2].parse().map_err(|_| { + CogError::ParseSTATISTICSValueFailed( + "sample of STATISTICS_MINIMUM".to_string(), + path.clone(), + ) + })?; + max_values.push(value); + } + } else { + //todo log + } + } + + Ok((min_values, max_values)) +} + fn get_across_down( decoder: &mut Decoder, path: &Path,