Skip to content

Commit

Permalink
Merge georust#246
Browse files Browse the repository at this point in the history
246: Implement ColorTable struct r=ChristianBeilschmidt,lnicola a=Barugon

Implement a `ColorTable` struct and add a `RasterBand::color_table` method.

- [x] I agree to follow the project's [code of conduct](https://github.com/georust/gdal/blob/master/CODE_OF_CONDUCT.md).
- [x] I added an entry to `CHANGES.md`.
---



Co-authored-by: Barugon <[email protected]>
  • Loading branch information
bors[bot] and Barugon authored May 14, 2022
2 parents b0ebd1d + 73f5fcd commit b700dc0
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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,
}

#[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> {
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
Original file line number Diff line number Diff line change
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!();
}
}
}

0 comments on commit b700dc0

Please sign in to comment.