Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ColorTable struct #246

Merged
merged 2 commits into from May 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.md
Expand Up @@ -27,6 +27,10 @@

- <https://github.com/georust/gdal/pull/257>

- Add a `ColorTable` struct and `RasterBand::color_table` method

- <https://github.com/georust/gdal/pull/246>

## 0.12

- Bump Rust edition to 2021
Expand Down
Binary file added fixtures/test_color_table.tif
Binary file not shown.
5 changes: 4 additions & 1 deletion src/raster/mod.rs
Expand Up @@ -5,7 +5,10 @@ mod rasterize;
mod types;
mod warp;

pub use rasterband::{Buffer, ByteBuffer, ColorInterpretation, RasterBand, ResampleAlg};
pub use rasterband::{
Buffer, ByteBuffer, CmykEntry, ColorEntry, ColorInterpretation, ColorTable, GrayEntry,
HlsEntry, PaletteInterpretation, RasterBand, ResampleAlg, RgbaEntry,
};
pub use rasterize::{rasterize, BurnSource, MergeAlgorithm, OptimizeMode, RasterizeOptions};
pub use types::{GDALDataType, GdalType};
pub use warp::reproject;
Expand Down
155 changes: 153 additions & 2 deletions src/raster/rasterband.rs
Expand Up @@ -4,11 +4,12 @@ use crate::metadata::Metadata;
use crate::raster::{GDALDataType, GdalType};
use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string};
use gdal_sys::{
self, CPLErr, GDALColorInterp, GDALMajorObjectH, GDALRWFlag, GDALRasterBandH,
GDALRasterIOExtraArg,
self, CPLErr, GDALColorEntry, GDALColorInterp, GDALColorTableH, GDALMajorObjectH,
GDALPaletteInterp, GDALRWFlag, GDALRasterBandH, GDALRasterIOExtraArg,
};
use libc::c_int;
use std::ffi::CString;
use std::marker::PhantomData;

#[cfg(feature = "ndarray")]
use ndarray::Array2;
Expand Down Expand Up @@ -421,6 +422,15 @@ impl<'a> RasterBand<'a> {
Ok(())
}

/// Get the color table for this band if it has one.
pub fn color_table(&self) -> Option<ColorTable> {
let c_color_table = unsafe { gdal_sys::GDALGetRasterColorTable(self.c_rasterband) };
if c_color_table.is_null() {
return None;
}
Some(ColorTable::from_c_color_table(c_color_table))
}

/// Returns the scale of this band if set.
pub fn scale(&self) -> Option<f64> {
let mut pb_success = 1;
Expand Down Expand Up @@ -612,3 +622,144 @@ impl ColorInterpretation {
_string(rv)
}
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum PaletteInterpretation {
Gray,
Rgba,
Cmyk,
Hls,
}

impl PaletteInterpretation {
fn from_c_int(palette_interpretation: GDALPaletteInterp::Type) -> Self {
match palette_interpretation {
GDALPaletteInterp::GPI_Gray => Self::Gray,
GDALPaletteInterp::GPI_RGB => Self::Rgba,
GDALPaletteInterp::GPI_CMYK => Self::Cmyk,
GDALPaletteInterp::GPI_HLS => Self::Hls,
_ => unreachable!("GDAL has implemented a new type of `GDALPaletteInterp`"),
}
}
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct GrayEntry {
pub g: i16,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the i32 / usize discussion: do you know how often these happen to be negative? I mean, if I have a 16-bit RGB image, I'll still want positive numbers here.

Anyway, this isn't blocking, I was just wondering how GDAL expects these to be used.

Copy link
Contributor Author

@Barugon Barugon May 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think they would ever be negative but I thought it might be best to leave them the same as what gdal_sys provides. I suppose elevation data could be negative but I can't imagine that ever being palettized.

}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct RgbaEntry {
pub r: i16,
pub g: i16,
pub b: i16,
pub a: i16,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct CmykEntry {
pub c: i16,
pub m: i16,
pub y: i16,
pub k: i16,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct HlsEntry {
pub h: i16,
pub l: i16,
pub s: i16,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ColorEntry {
Gray(GrayEntry),
Rgba(RgbaEntry),
Cmyk(CmykEntry),
Hls(HlsEntry),
}

/// Color table for raster bands that use the PaletteIndex color interpretation.
///
/// This object carries the lifetime of the raster band that
/// contains it. This is necessary to prevent the raster band
/// from being dropped before the color table.
pub struct ColorTable<'a> {
Barugon marked this conversation as resolved.
Show resolved Hide resolved
palette_interpretation: PaletteInterpretation,
c_color_table: GDALColorTableH,
phantom_raster_band: PhantomData<&'a RasterBand<'a>>,
}

impl<'a> ColorTable<'a> {
fn from_c_color_table(c_color_table: GDALColorTableH) -> Self {
let interp_index = unsafe { gdal_sys::GDALGetPaletteInterpretation(c_color_table) };
ColorTable {
palette_interpretation: PaletteInterpretation::from_c_int(interp_index),
c_color_table,
phantom_raster_band: PhantomData,
}
}

/// How the values of this color table are interpreted.
pub fn palette_interpretation(&self) -> PaletteInterpretation {
self.palette_interpretation
}

/// Get the number of color entries in this color table.
pub fn entry_count(&self) -> usize {
unsafe { gdal_sys::GDALGetColorEntryCount(self.c_color_table) as usize }
}

/// Get a color entry.
pub fn entry(&self, index: usize) -> Option<ColorEntry> {
let color_entry = unsafe {
let c_color_entry = gdal_sys::GDALGetColorEntry(self.c_color_table, index as i32);
if c_color_entry.is_null() {
return None;
}
*c_color_entry
};
match self.palette_interpretation {
PaletteInterpretation::Gray => Some(ColorEntry::Gray(GrayEntry { g: color_entry.c1 })),
PaletteInterpretation::Rgba => Some(ColorEntry::Rgba(RgbaEntry {
r: color_entry.c1,
g: color_entry.c2,
b: color_entry.c3,
a: color_entry.c4,
})),
PaletteInterpretation::Cmyk => Some(ColorEntry::Cmyk(CmykEntry {
c: color_entry.c1,
m: color_entry.c2,
y: color_entry.c3,
k: color_entry.c4,
})),
PaletteInterpretation::Hls => Some(ColorEntry::Hls(HlsEntry {
h: color_entry.c1,
l: color_entry.c2,
s: color_entry.c3,
})),
}
}

/// Get a color entry as RGB.
pub fn entry_as_rgb(&self, index: usize) -> Option<RgbaEntry> {
let mut color_entry = GDALColorEntry {
c1: 0,
c2: 0,
c3: 0,
c4: 0,
};
if unsafe {
gdal_sys::GDALGetColorEntryAsRGB(self.c_color_table, index as i32, &mut color_entry)
} == 0
{
return None;
}
Some(RgbaEntry {
r: color_entry.c1,
g: color_entry.c2,
b: color_entry.c3,
a: color_entry.c4,
})
}
}
40 changes: 40 additions & 0 deletions src/raster/tests.rs
Expand Up @@ -706,3 +706,43 @@ fn test_rasterband_unit() {

assert_eq!(rasterband.unit(), "m".to_string());
}

#[test]
fn test_color_table() {
use crate::raster::rasterband::{ColorEntry, PaletteInterpretation};

// Raster containing one band.
let dataset = Dataset::open(fixture!("test_color_table.tif")).expect("open failure");
assert_eq!(dataset.raster_count(), 1);

// Band is PaletteIndex.
let band = dataset.rasterband(1).expect("rasterband failure");
assert_eq!(
band.color_interpretation(),
ColorInterpretation::PaletteIndex
);

// Color table is RGB.
let color_table = band.color_table().unwrap();
assert_eq!(
color_table.palette_interpretation(),
PaletteInterpretation::Rgba
);

// Color table has 256 entries.
let entry_count = color_table.entry_count();
assert_eq!(entry_count, 256);

// Check that entry and entry_as_rgb are the same.
for index in 0..entry_count {
if let ColorEntry::Rgba(entry) = color_table.entry(index).unwrap() {
let rgb_entry = color_table.entry_as_rgb(index).unwrap();
assert_eq!(entry.r, rgb_entry.r);
assert_eq!(entry.g, rgb_entry.g);
assert_eq!(entry.b, rgb_entry.b);
assert_eq!(entry.a, rgb_entry.a);
} else {
panic!();
}
}
}