use super::common;
use crate::formats::RasterDriver;
use crate::{Error, RasterDataset, Result};
use codec::{Component, encode};
use std::path::Path;
use wbraster::raster::RasterData;
use wbraster::{CrsInfo, DataType, Raster};
const GEOJP2_UUID: [u8; 16] = [0xb1, 0x4b, 0xf8, 0xbd, 0x08, 0x3d, 0x4b, 0x43, 0xa5, 0xae, 0x8c, 0xd7, 0xd5, 0xa6, 0xce, 0x03];
mod codec {
use crate::{Error, Result};
use core::ffi::c_void;
use openjp2::openjpeg::*;
use wbraster::raster::RasterData;
unsafe extern "C" {
fn malloc(size: usize) -> *mut c_void;
}
struct Buffer {
data: Vec<u8>,
pos: usize,
}
pub(crate) trait ReadSeek: std::io::Read + std::io::Seek + Send + Sync {}
impl<T: std::io::Read + std::io::Seek + Send + Sync> ReadSeek for T {}
struct Source<'a> {
r: &'a mut dyn ReadSeek,
error: Option<std::io::Error>,
}
unsafe extern "C" fn read_fn(dst: *mut c_void, n: usize, user: *mut c_void) -> usize {
let s = unsafe { &mut *(user as *mut Source) };
if dst.is_null() {
return usize::MAX;
}
let buf = unsafe { std::slice::from_raw_parts_mut(dst as *mut u8, n) };
let mut k = 0;
while k < n {
match s.r.read(&mut buf[k..]) {
Ok(0) => break,
Ok(m) => k += m,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
Err(e) => {
s.error.get_or_insert(e);
break;
}
}
}
if k == 0 { usize::MAX } else { k } }
unsafe extern "C" fn read_skip_fn(n: i64, user: *mut c_void) -> i64 {
let s = unsafe { &mut *(user as *mut Source) };
match s.r.seek(std::io::SeekFrom::Current(n)) {
Ok(_) => n,
Err(e) => {
s.error.get_or_insert(e);
-1
}
}
}
unsafe extern "C" fn read_seek_fn(n: i64, user: *mut c_void) -> i32 {
let s = unsafe { &mut *(user as *mut Source) };
match s.r.seek(std::io::SeekFrom::Start(n.max(0) as u64)) {
Ok(_) => 1,
Err(e) => {
s.error.get_or_insert(e);
0
}
}
}
unsafe extern "C" fn write_fn(src: *mut c_void, n: usize, user: *mut c_void) -> usize {
let b = unsafe { &mut *(user as *mut Buffer) };
let bytes = unsafe { std::slice::from_raw_parts(src as *const u8, n) };
let end = b.pos + n;
if b.data.len() < end {
b.data.resize(end, 0);
}
b.data[b.pos..end].copy_from_slice(bytes);
b.pos = end;
n
}
unsafe extern "C" fn skip_fn(n: i64, user: *mut c_void) -> i64 {
let b = unsafe { &mut *(user as *mut Buffer) };
let new = (b.pos as i64 + n).max(0) as usize;
b.pos = new;
n
}
unsafe extern "C" fn seek_fn(n: i64, user: *mut c_void) -> i32 {
let b = unsafe { &mut *(user as *mut Buffer) };
b.pos = n.max(0) as usize;
1
}
pub(super) struct Component {
pub precision: u32,
pub signed: bool,
pub data: Vec<i32>,
}
fn collect(comps: &[&[i32]], precision: u32, signed: bool) -> RasterData {
fn gather<T>(comps: &[&[i32]], f: impl Fn(i32) -> T) -> Vec<T> {
let mut out = Vec::with_capacity(comps.iter().map(|c| c.len()).sum());
for c in comps {
out.extend(c.iter().map(|&v| f(v)));
}
out
}
match (precision, signed) {
(0..=8, false) => RasterData::U8(gather(comps, |v| v as u8)),
(0..=8, true) => RasterData::I8(gather(comps, |v| v as i8)),
(9..=16, false) => RasterData::U16(gather(comps, |v| v as u16)),
(9..=16, true) => RasterData::I16(gather(comps, |v| v as i16)),
(_, false) => RasterData::U32(gather(comps, |v| v as u32)),
(_, true) => RasterData::I32(gather(comps, |v| v)),
}
}
#[derive(Debug, Clone)]
pub(super) struct Header {
pub area: [u32; 4],
pub precisions: Vec<u32>,
pub signed: bool,
pub subsampled: bool,
pub tile: (u32, u32),
pub tile_origin: (u32, u32),
pub resolutions: u32,
}
unsafe fn with_codec<T>(
r: &mut dyn ReadSeek,
len: u64,
j2k: bool,
f: impl FnOnce(*mut opj_codec_t, *mut opj_stream_t, *mut opj_image_t) -> Result<T>,
) -> Result<T> {
r.seek(std::io::SeekFrom::Start(0))?;
let format = if j2k { OPJ_CODEC_J2K } else { OPJ_CODEC_JP2 };
let mut source = Source { r, error: None };
unsafe {
let stream = opj_stream_create(16 * 1024, 1);
opj_stream_set_read_function(stream, Some(read_fn));
opj_stream_set_skip_function(stream, Some(read_skip_fn));
opj_stream_set_seek_function(stream, Some(read_seek_fn));
opj_stream_set_user_data_length(stream, len);
opj_stream_set_user_data(stream, &mut source as *mut Source as *mut c_void, None);
let codec = opj_create_decompress(format);
let mut params = opj_dparameters_t::default();
let mut image: *mut opj_image_t = std::ptr::null_mut();
let ok = !codec.is_null() && opj_setup_decoder(codec, &mut params) != 0 && opj_read_header(stream, codec, &mut image) != 0;
let result = if ok && !image.is_null() {
f(codec, stream, image)
} else {
Err(Error::InvalidData("cannot read the JPEG 2000 codestream header".into()))
};
if !image.is_null() {
opj_image_destroy(image);
}
if !codec.is_null() {
opj_destroy_codec(codec);
}
opj_stream_destroy(stream);
match source.error.take() {
Some(e) => Err(e.into()),
None => result,
}
}
}
pub(super) fn read_header(r: &mut dyn ReadSeek, len: u64, j2k: bool) -> Result<Header> {
unsafe {
with_codec(r, len, j2k, |codec, _, image| {
let img = &*image;
let comps = std::slice::from_raw_parts(img.comps, img.numcomps as usize);
if comps.is_empty() {
return Err(Error::InvalidData("JPEG 2000 image without components".into()));
}
let mut info = opj_get_cstr_info(codec);
let (tile, tile_origin, resolutions) = if info.is_null() {
((img.x1 - img.x0, img.y1 - img.y0), (img.x0, img.y0), 1)
} else {
let i = &*info;
let tccp = i.m_default_tile_info.tccp_info;
let res = if tccp.is_null() { 1 } else { (*tccp).numresolutions };
let out = ((i.tdx, i.tdy), (i.tx0, i.ty0), res);
opj_destroy_cstr_info(&mut info);
out
};
Ok(Header {
area: [img.x0, img.y0, img.x1, img.y1],
precisions: comps.iter().map(|c| c.prec).collect(),
signed: comps[0].sgnd != 0,
subsampled: comps.iter().any(|c| c.dx != 1 || c.dy != 1),
tile,
tile_origin,
resolutions,
})
})
}
}
pub(super) fn decode_area(
r: &mut dyn ReadSeek,
len: u64,
j2k: bool,
area: Option<[u32; 4]>,
reduce: u32,
alpha: Option<usize>,
) -> Result<(usize, usize, RasterData)> {
unsafe {
with_codec(r, len, j2k, |codec, stream, image| {
if reduce > 0 && opj_set_decoded_resolution_factor(codec, reduce) == 0 {
return Err(Error::InvalidData(format!("cannot decode JPEG 2000 resolution level {reduce}")));
}
if let Some([x0, y0, x1, y1]) = area {
let img = &mut *image;
for c in std::slice::from_raw_parts_mut(img.comps, img.numcomps as usize) {
c.factor = reduce;
}
if opj_set_decode_area(codec, image, x0 as i32, y0 as i32, x1 as i32, y1 as i32) == 0 {
return Err(Error::InvalidArgument("invalid JPEG 2000 decode area".into()));
}
}
if opj_decode(codec, stream, image) == 0 || opj_end_decompress(codec, stream) == 0 {
return Err(Error::InvalidData("cannot decode the JPEG 2000 codestream".into()));
}
let img = &*image;
let comps = std::slice::from_raw_parts(img.comps, img.numcomps as usize);
let (w, h) = (comps[0].w as usize, comps[0].h as usize);
if comps.iter().any(|c| c.w as usize != w || c.h as usize != h || c.data.is_null()) {
return Err(Error::Unsupported("JPEG 2000 components with different sizes are not supported".into()));
}
let mut planes: Vec<&[i32]> = comps.iter().map(|c| std::slice::from_raw_parts(c.data, w * h)).collect();
let mut precisions: Vec<u32> = comps.iter().map(|c| c.prec).collect();
let promoted: Vec<i32>;
if let Some(a) = alpha.filter(|&a| a < comps.len()) {
let colour = if a == 0 && comps.len() > 1 { 1 } else { 0 };
if precisions[a] == 1 && precisions[colour] == 8 {
promoted = planes[a].iter().map(|v| v * 255).collect();
planes[a] = &promoted;
precisions[a] = 8;
}
}
let precision = precisions.iter().copied().max().unwrap_or(8);
let signed = comps[0].sgnd != 0;
Ok((w, h, collect(&planes, precision, signed)))
})
}
}
pub(super) fn data_type(precisions: &[u32], signed: bool, alpha: Option<usize>) -> wbraster::DataType {
let mut p = precisions.to_vec();
if let Some(a) = alpha.filter(|&a| a < p.len()) {
let colour = if a == 0 && p.len() > 1 { 1 } else { 0 };
if p[a] == 1 && p[colour] == 8 {
p[a] = 8;
}
}
match (p.iter().copied().max().unwrap_or(8), signed) {
(0..=8, false) => wbraster::DataType::U8,
(0..=8, true) => wbraster::DataType::I8,
(9..=16, false) => wbraster::DataType::U16,
(9..=16, true) => wbraster::DataType::I16,
(_, false) => wbraster::DataType::U32,
(_, true) => wbraster::DataType::I32,
}
}
pub(super) fn encode(w: usize, h: usize, comps: &[Component]) -> Result<Vec<u8>> {
let mut parms: Vec<openjp2::opj_image_comptparm> = comps
.iter()
.map(|c| openjp2::opj_image_comptparm {
dx: 1,
dy: 1,
w: w as u32,
h: h as u32,
x0: 0,
y0: 0,
prec: c.precision,
bpp: c.precision,
sgnd: c.signed as u32,
})
.collect();
let color = if comps.len() == 3 || comps.len() == 4 { OPJ_CLRSPC_SRGB } else { OPJ_CLRSPC_GRAY };
let buffer = Box::into_raw(Box::new(Buffer { data: Vec::new(), pos: 0 }));
unsafe {
let image = opj_image_create(parms.len() as u32, parms.as_mut_ptr(), color);
if image.is_null() {
drop(Box::from_raw(buffer));
return Err(Error::InvalidData("cannot create the JPEG 2000 image".into()));
}
let img = &mut *image;
img.x0 = 0;
img.y0 = 0;
img.x1 = w as u32;
img.y1 = h as u32;
for (dst, src) in std::slice::from_raw_parts_mut(img.comps, img.numcomps as usize).iter_mut().zip(comps) {
std::slice::from_raw_parts_mut(dst.data, w * h).copy_from_slice(&src.data);
}
let mut params = opj_cparameters_t::default();
opj_set_default_encoder_parameters(&mut params);
params.tcp_numlayers = 1;
params.tcp_rates[0] = 0.0;
params.cp_disto_alloc = 1;
params.irreversible = 0;
let comment = format!("Created by OpenJPEG version {OPJ_VERSION}. LOSSLESS settings used\0");
let copy = malloc(comment.len()) as *mut u8;
if !copy.is_null() {
std::ptr::copy_nonoverlapping(comment.as_ptr(), copy, comment.len());
params.cp_comment = copy as *mut core::ffi::c_char;
}
let mut levels = 6;
while levels > 1 && (w.min(h) >> (levels - 1)) == 0 {
levels -= 1;
}
params.numresolution = levels;
params.tcp_mct = (comps.len() >= 3 && comps[..3].iter().all(|c| c.precision == comps[0].precision)) as core::ffi::c_char;
if w > 1024 || h > 1024 {
params.tile_size_on = 1;
params.cp_tdx = 1024;
params.cp_tdy = 1024;
}
let codec = opj_create_compress(OPJ_CODEC_J2K);
let stream = opj_stream_default_create(0);
opj_stream_set_write_function(stream, Some(write_fn));
opj_stream_set_skip_function(stream, Some(skip_fn));
opj_stream_set_seek_function(stream, Some(seek_fn));
opj_stream_set_user_data(stream, buffer as *mut c_void, None);
let ok = !codec.is_null()
&& opj_setup_encoder(codec, &mut params, image) != 0
&& opj_start_compress(codec, image, stream) != 0
&& opj_encode(codec, stream) != 0
&& opj_end_compress(codec, stream) != 0;
if !codec.is_null() {
opj_destroy_codec(codec);
}
opj_stream_destroy(stream);
opj_image_destroy(image);
let data = Box::from_raw(buffer).data;
if ok { Ok(data) } else { Err(Error::InvalidData("JPEG 2000 encoding failed".into())) }
}
}
}
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
mod wasm_libc {
use core::ffi::c_void;
use std::alloc::{Layout, alloc, alloc_zeroed, dealloc, realloc as re_alloc};
const HEADER: usize = 16;
fn layout(size: usize) -> Option<Layout> {
Layout::from_size_align(size.checked_add(HEADER)?, HEADER).ok()
}
unsafe fn finish(base: *mut u8, size: usize) -> *mut c_void {
if base.is_null() {
return std::ptr::null_mut();
}
unsafe {
(base as *mut usize).write(size);
base.add(HEADER) as *mut c_void
}
}
unsafe fn block(ptr: *mut c_void) -> (*mut u8, usize) {
unsafe {
let base = (ptr as *mut u8).sub(HEADER);
(base, (base as *const usize).read())
}
}
#[unsafe(no_mangle)]
extern "C" fn malloc(size: usize) -> *mut c_void {
layout(size).map_or(std::ptr::null_mut(), |l| unsafe { finish(alloc(l), size) })
}
#[unsafe(no_mangle)]
extern "C" fn calloc(n: usize, size: usize) -> *mut c_void {
let Some(total) = n.checked_mul(size) else { return std::ptr::null_mut() };
layout(total).map_or(std::ptr::null_mut(), |l| unsafe { finish(alloc_zeroed(l), total) })
}
#[unsafe(no_mangle)]
unsafe extern "C" fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
if ptr.is_null() {
return malloc(size);
}
let Some(new) = layout(size) else { return std::ptr::null_mut() };
unsafe {
let (base, old) = block(ptr);
let Some(l) = layout(old) else { return std::ptr::null_mut() };
finish(re_alloc(base, l, new.size()), size)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn free(ptr: *mut c_void) {
if ptr.is_null() {
return;
}
unsafe {
let (base, size) = block(ptr);
if let Some(l) = layout(size) {
dealloc(base, l);
}
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn strcpy(dst: *mut core::ffi::c_char, src: *const core::ffi::c_char) -> *mut core::ffi::c_char {
unsafe {
let n = core::ffi::CStr::from_ptr(src).to_bytes_with_nul().len();
std::ptr::copy_nonoverlapping(src, dst, n);
}
dst
}
}
fn boxes(b: &[u8]) -> Vec<([u8; 4], &[u8])> {
let mut out = Vec::new();
let mut i = 0usize;
while i + 8 <= b.len() {
let len = u32::from_be_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) as usize;
let typ = [b[i + 4], b[i + 5], b[i + 6], b[i + 7]];
let (header, total) = match len {
0 => (8, b.len() - i),
1 if i + 16 <= b.len() => (16, u64::from_be_bytes(b[i + 8..i + 16].try_into().unwrap_or_default()) as usize),
_ => (8, len),
};
if total < header || i.checked_add(total).is_none_or(|end| end > b.len()) {
break;
}
out.push((typ, &b[i + header..i + total]));
i += total;
}
out
}
fn alpha_channel(b: &[u8]) -> Option<usize> {
let header = boxes(b).into_iter().find(|(t, _)| t == b"jp2h")?.1;
let cdef = boxes(header).into_iter().find(|(t, _)| t == b"cdef")?.1;
let n = u16::from_be_bytes([*cdef.first()?, *cdef.get(1)?]) as usize;
(0..n).find_map(|i| {
let e = cdef.get(2 + i * 6..2 + i * 6 + 6)?;
(u16::from_be_bytes([e[2], e[3]]) == 1).then_some(u16::from_be_bytes([e[0], e[1]]) as usize)
})
}
fn georeferencing(b: &[u8]) -> (Option<[f64; 6]>, Option<CrsInfo>) {
let top = boxes(b);
if let Some((_, payload)) = top.iter().find(|(t, p)| t == b"uuid" && p.starts_with(&GEOJP2_UUID))
&& let Some(tags) = crate::geotiff::read_geo_tags_from(std::io::Cursor::new(&payload[16..]))
{
let crs = tags.epsg.map(CrsInfo::from_epsg).or(tags.crs);
let gt = tags
.geo_transform
.map(|gt| if tags.pixel_is_point { [gt[0] - gt[1] / 2.0, gt[1], gt[2], gt[3] - gt[5] / 2.0, gt[4], gt[5]] } else { gt });
if gt.is_some() || crs.is_some() {
return (gt, crs);
}
}
let xml = top
.iter()
.filter(|(t, _)| t == b"asoc")
.flat_map(|(_, p)| p.windows(5).position(|w| w == b"<?xml" || w == b"<gml:").map(|i| String::from_utf8_lossy(&p[i..]).into_owned()))
.find(|x| x.contains("RectifiedGrid"));
xml.map(|x| gml_georeferencing(&x)).unwrap_or((None, None))
}
fn gml_georeferencing(xml: &str) -> (Option<[f64; 6]>, Option<CrsInfo>) {
let grid = &xml[xml.find("RectifiedGrid").unwrap_or(0)..];
let attr = |s: &str| s.split("srsName=\"").nth(1).and_then(|s| s.split('"').next()).map(str::to_string);
let origin_xml = grid.find("<gml:origin").map(|i| &grid[i..]).unwrap_or(grid);
let srs = origin_xml.find("</gml:origin>").and_then(|end| attr(&origin_xml[..end])).or_else(|| attr(grid)).or_else(|| attr(xml));
let epsg = srs.as_deref().and_then(|s| s.rsplit([':', '/']).next()).and_then(|c| c.parse::<u32>().ok());
let numbers = |tag: &str| -> Vec<Vec<f64>> {
grid.split(&format!("<gml:{tag}"))
.skip(1)
.filter_map(|s| s.split_once('>').map(|(_, rest)| rest.split('<').next().unwrap_or_default()))
.map(|t| t.split([' ', ',', '\n', '\t']).filter_map(|v| v.trim().parse().ok()).collect())
.collect()
};
let crs = epsg.map(CrsInfo::from_epsg);
let origin = numbers("pos").into_iter().next().or_else(|| numbers("coordinates").into_iter().next());
let vectors = numbers("offsetVector");
let (Some(o), [v1, v2, ..]) = (origin, vectors.as_slice()) else {
return (None, crs);
};
if o.len() < 2 || v1.len() < 2 || v2.len() < 2 {
return (None, crs);
}
let mut gt = [o[0], v1[0], v2[0], o[1], v1[1], v2[1]];
gt[0] -= gt[1] * 0.5;
gt[0] -= gt[2] * 0.5;
gt[3] -= gt[4] * 0.5;
gt[3] -= gt[5] * 0.5;
let by_urn = srs.as_deref().is_some_and(|s| {
let lower = s.to_ascii_lowercase();
(lower.starts_with("urn:") && lower.contains(":def:")) || lower.starts_with("http://www.opengis.net/def/crs/")
});
let axis_names: Vec<String> = element_texts(grid, "axisName");
let east_north = axis_names.len() >= 2
&& ["EAST", "LONG"].iter().any(|p| axis_names[0].to_ascii_uppercase().starts_with(p))
&& ["NORTH", "LAT"].iter().any(|p| axis_names[1].to_ascii_uppercase().starts_with(p));
if by_urn && epsg.is_some_and(northing_first) && !east_north {
gt.swap(0, 3);
if xml.contains("GDAL_JP2K_ALT_OFFSETVECTOR_ORDER=TRUE") {
gt.swap(1, 5);
gt.swap(2, 4);
} else {
gt.swap(1, 4);
gt.swap(2, 5);
}
}
if gt[2] != 0.0 || gt[4] != 0.0 {
return (None, crs);
}
(Some(gt), crs)
}
fn element_texts(xml: &str, tag: &str) -> Vec<String> {
xml.split(&format!("<gml:{tag}"))
.skip(1)
.filter_map(|s| s.split_once('>').map(|(_, rest)| rest.split('<').next().unwrap_or_default().trim().to_string()))
.collect()
}
fn northing_first(code: u32) -> bool {
let Some(wkt) = crate::crs::epsg_wkt(code) else { return false };
let axes: Vec<&str> = wkt.match_indices("AXIS[").map(|(i, _)| &wkt[i..]).collect();
let [.., first, _] = axes.as_slice() else { return false };
let direction = first.split(']').next().and_then(|a| a.rsplit(',').next()).unwrap_or_default().trim().to_ascii_uppercase();
direction == "NORTH" || direction == "SOUTH"
}
pub(crate) fn is_jpeg2000(b: &[u8]) -> bool {
b.starts_with(&[0, 0, 0, 0x0c, b'j', b'P', b' ', b' ', 0x0d, 0x0a, 0x87, 0x0a]) || b.starts_with(openjp2::J2K_CODESTREAM_MAGIC)
}
fn header_boxes(r: &mut dyn codec::ReadSeek) -> Result<Vec<u8>> {
use std::io::SeekFrom;
let len = r.seek(SeekFrom::End(0))?;
let mut pos = 0u64;
let mut out = Vec::new();
while pos + 8 <= len {
r.seek(SeekFrom::Start(pos))?;
let mut h = [0u8; 16];
r.read_exact(&mut h[..8])?;
let size = u32::from_be_bytes([h[0], h[1], h[2], h[3]]) as u64;
let (header, total) = match size {
0 => (8, len - pos),
1 => {
r.read_exact(&mut h[8..16])?;
(16, u64::from_be_bytes(h[8..16].try_into().unwrap_or_default()))
}
n => (8, n),
};
let Some(end) = pos.checked_add(total) else { break };
if total < header || end > len || &h[4..8] == b"jp2c" {
break;
}
if total <= 64 << 20 {
let mut b = vec![0u8; total as usize];
r.seek(SeekFrom::Start(pos))?;
r.read_exact(&mut b)?;
out.extend(b);
}
pos = end;
}
Ok(out)
}
enum StreamSource {
File(std::path::PathBuf),
Bytes(std::sync::Arc<[u8]>),
#[cfg(not(target_arch = "wasm32"))]
Http(crate::http::RangeReader),
}
impl StreamSource {
fn open(&self) -> Result<Box<dyn codec::ReadSeek>> {
Ok(match self {
Self::File(p) => Box::new(std::io::BufReader::with_capacity(1 << 16, std::fs::File::open(p)?)),
Self::Bytes(b) => Box::new(std::io::Cursor::new(b.clone())),
#[cfg(not(target_arch = "wasm32"))]
Self::Http(r) => Box::new(r.clone()),
})
}
}
struct Jp2Window {
source: StreamSource,
len: u64,
j2k: bool,
header: codec::Header,
alpha: Option<usize>,
overviews: Vec<(usize, usize)>,
block: (usize, usize),
}
impl Jp2Window {
fn open(source: StreamSource) -> Result<(Self, RasterDataset)> {
use std::io::SeekFrom;
let mut stream = source.open()?;
let len = stream.seek(SeekFrom::End(0))?;
stream.seek(SeekFrom::Start(0))?;
let mut magic = [0u8; 12];
stream.read_exact(&mut magic[..len.min(12) as usize])?;
let j2k = magic.starts_with(openjp2::J2K_CODESTREAM_MAGIC);
let boxes_bytes = if j2k { Vec::new() } else { header_boxes(stream.as_mut())? };
let (gt, crs) = georeferencing(&boxes_bytes);
let alpha = alpha_channel(&boxes_bytes);
let header = codec::read_header(stream.as_mut(), len, j2k)?;
if header.subsampled {
return Err(Error::Unsupported("JPEG 2000 components with different sizes are not supported".into()));
}
let [x0, y0, x1, y1] = header.area;
let (w, h) = ((x1 - x0) as usize, (y1 - y0) as usize);
let palette =
boxes(&boxes_bytes).into_iter().find(|(t, _)| t == b"jp2h").is_some_and(|(_, p)| boxes(p).iter().any(|(t, _)| t == b"pclr"));
let mut header = header;
let (bands, data_type) = if palette {
let mut s = source.open()?;
let (_, _, px) = codec::decode_area(s.as_mut(), len, j2k, Some([x0, y0, x0 + 1, y0 + 1]), 0, alpha)?;
let n = px.len().max(1);
header.precisions = vec![header.precisions[0]; n];
(n, px.data_type())
} else {
(header.precisions.len(), codec::data_type(&header.precisions, header.signed, alpha))
};
let (overviews, block) = levels(w, h, header.tile, if palette { 0 } else { header.resolutions });
let raster = Raster {
cols: w,
rows: h,
bands,
x_min: 0.0,
y_min: 0.0,
cell_size_x: 1.0,
cell_size_y: 1.0,
nodata: -9999.0,
data_type,
crs: crs.unwrap_or_default(),
metadata: Vec::new(),
data: RasterData::new_filled(data_type, 0, 0.0),
};
let mut ds = RasterDataset::with_driver(raster, RasterDriver::JP2, false);
ds.set_geo_transform(gt.unwrap_or([0.0, 1.0, 0.0, h as f64, 0.0, -1.0]));
Ok((Self { source, len, j2k, header, alpha, overviews, block }, ds))
}
fn decode(&self, area: [u32; 4], reduce: u32) -> Result<(usize, usize, RasterData)> {
let mut stream = self.source.open()?;
codec::decode_area(stream.as_mut(), self.len, self.j2k, Some(area), reduce, self.alpha)
}
}
fn ceil_shift(a: u32, r: u32) -> usize {
(((a as u64) + (1u64 << r) - 1) >> r) as usize
}
fn levels(w: usize, h: usize, tile: (u32, u32), resolutions: u32) -> (Vec<(usize, usize)>, (usize, usize)) {
let (mut tw, mut th) = (tile.0 as usize, tile.1 as usize);
let single = w == tw && h == th;
let mut block = (tw.min(w), th.min(h));
let decode_area = if w <= 1024 && h <= 1024 && tw < 32 && th < 32 {
block = (w, h);
true
} else if w < tw && h < th {
block = (w.min(2048), h.min(2048));
true
} else {
let area = single && (w > 1024 || h > 1024);
if area && !(th == 1 && tw < 20 * 1024 * 1024) {
block = (block.0.min(1024), block.1.min(1024));
}
area
};
let mut out = Vec::new();
let (mut ow, mut oh) = (w, h);
while out.len() + 1 < resolutions as usize && (ow > 128 || oh > 128) && (decode_area || (tw % 2 == 0 && th % 2 == 0)) {
ow = ow.div_ceil(2);
oh = oh.div_ceil(2);
if !decode_area {
tw /= 2;
th /= 2;
}
out.push((ow, oh));
}
(out, block)
}
impl crate::cog::WindowSource for Jp2Window {
fn overviews(&self) -> Vec<(usize, usize)> {
self.overviews.clone()
}
fn block_size(&self) -> Option<(usize, usize)> {
Some(self.block)
}
fn read(&mut self, level: usize, window: crate::Window, bands: &[usize]) -> Result<RasterData> {
let [x0, y0, x1, y1] = self.header.area;
let r = level as u32;
let edge =
|o: u32, p: usize, end: u32| -> u32 { o.saturating_add(u32::try_from(p).unwrap_or(u32::MAX).saturating_mul(1 << r)).min(end) };
let area =
[edge(x0, window.x, x1), edge(y0, window.y, y1), edge(x0, window.x + window.width, x1), edge(y0, window.y + window.height, y1)];
let (tw, th) = (self.header.tile.0.max(1), self.header.tile.1.max(1));
let (tx0, ty0) = self.header.tile_origin;
let span = |a0: u32, a1: u32, o: u32, t: u32| ((a0 - o.min(a0)) / t)..=((a1 - 1 - o.min(a1 - 1)) / t);
let cols: Vec<u32> = span(area[0], area[2], tx0, tw).collect();
let rows: Vec<u32> = span(area[1], area[3], ty0, th).collect();
let parts: Vec<[u32; 4]> = rows
.iter()
.flat_map(|j| {
cols.iter().map(move |i| {
[area[0].max(tx0 + i * tw), area[1].max(ty0 + j * th), area[2].min(tx0 + (i + 1) * tw), area[3].min(ty0 + (j + 1) * th)]
})
})
.collect();
let (w, h) = (window.width, window.height);
let all = self.header.precisions.len();
let decoded: Vec<Result<(usize, usize, RasterData)>> = if parts.len() == 1 {
vec![self.decode(area, r)]
} else {
let this = &*self;
crate::par::map_range(parts.len(), |k| this.decode(parts[k], r))
};
let (ox, oy) = (ceil_shift(area[0], r), ceil_shift(area[1], r));
let mut out: Option<RasterData> = None;
for (part, d) in parts.iter().zip(decoded) {
let (pw, ph, data) = d?;
let (px, py) = (ceil_shift(part[0], r) - ox, ceil_shift(part[1], r) - oy);
if px + pw > w || py + ph > h {
return Err(Error::InvalidData(format!("JPEG 2000 decoded {pw}x{ph} pixels at ({px}, {py}) for a {w}x{h} window")));
}
if parts.len() == 1 && (pw, ph) == (w, h) {
out = Some(data);
break;
}
let dst = out.get_or_insert_with(|| RasterData::new_filled(data.data_type(), w * h * all, 0.0));
macro_rules! place {
($($v:ident),*) => {
match (dst, &data) {
$((RasterData::$v(o), RasterData::$v(src)) => {
for b in 0..all {
for row in 0..ph {
let s0 = b * pw * ph + row * pw;
let d0 = b * w * h + (py + row) * w + px;
o[d0..d0 + pw].copy_from_slice(&src[s0..s0 + pw]);
}
}
})*
_ => return Err(Error::InvalidData("JPEG 2000 tiles of different sample types".into())),
}
};
}
place!(U8, I8, U16, I16, U32, I32, U64, I64, F32, F64);
}
let data = out.ok_or_else(|| Error::InvalidData("empty JPEG 2000 window".into()))?;
if data.len() != w * h * all {
return Err(Error::InvalidData(format!("JPEG 2000 decoded {} values for a {w}x{h} window of {all} bands", data.len())));
}
if bands.len() == all && bands.iter().enumerate().all(|(i, b)| *b == i + 1) {
return Ok(data);
}
let n = w * h;
macro_rules! pick {
($($v:ident),*) => {
match &data {
$(RasterData::$v(src) => {
let mut out = Vec::with_capacity(bands.len() * n);
for b in bands {
out.extend_from_slice(&src[(b - 1) * n..b * n]);
}
RasterData::$v(out)
})*
}
};
}
Ok(pick!(U8, I8, U16, I16, U32, I32, U64, I64, F32, F64))
}
}
pub(crate) fn open_lazy(path: &str) -> Option<Result<crate::LazyRaster>> {
use crate::vfs::Location;
use std::io::Read;
let source = match Location::parse(path) {
Location::Local(p) => {
let mut f = std::fs::File::open(&p).ok()?;
let mut magic = [0u8; 12];
let n = f.read(&mut magic).ok()?;
if !is_jpeg2000(&magic[..n]) {
return None;
}
StreamSource::File(p)
}
#[cfg(not(target_arch = "wasm32"))]
loc @ (Location::Url(_) | Location::S3 { .. }) => {
let lower = path.to_ascii_lowercase();
let lower = lower.split(['?', '#']).next().unwrap_or_default();
if ![".jp2", ".j2k", ".j2c", ".jpx"].iter().any(|e| lower.ends_with(e)) {
return None;
}
let remote = loc.remote()?;
let options = crate::http::HttpOptions { block_size: 16 * 1024, ..crate::http::HttpOptions::from_env() };
match crate::http::RangeReader::open_with(&remote, options) {
Ok(r) => StreamSource::Http(r),
Err(e) => return Some(Err(e)),
}
}
_ => return None,
};
Some(open_lazy_source(source, Some(path)))
}
pub(crate) fn open_lazy_bytes(bytes: Vec<u8>) -> Result<crate::LazyRaster> {
open_lazy_source(StreamSource::Bytes(bytes.into()), None)
}
fn open_lazy_source(source: StreamSource, path: Option<&str>) -> Result<crate::LazyRaster> {
let (reader, mut ds) = Jp2Window::open(source).map_err(|e| match path {
Some(p) => Error::InvalidData(format!("{p}: {e}")),
None => e,
})?;
if let Some(p) = path {
super::nodata::apply_pam(p, &mut ds);
}
Ok(crate::LazyRaster::from_source(ds, Box::new(reader)))
}
pub(crate) fn read(path: &Path) -> Result<RasterDataset> {
read_source(StreamSource::File(path.to_path_buf())).map_err(|e| Error::InvalidData(format!("{}: {e}", path.display())))
}
pub(crate) fn read_bytes(bytes: &[u8]) -> Result<RasterDataset> {
read_source(StreamSource::Bytes(bytes.into()))
}
fn read_source(source: StreamSource) -> Result<RasterDataset> {
use crate::cog::WindowSource;
let (mut reader, mut ds) = Jp2Window::open(source)?;
let (w, h, bands) = (ds.raster.cols, ds.raster.rows, ds.raster.bands);
let all: Vec<usize> = (1..=bands).collect();
ds.raster.data = reader.read(0, crate::Window::full(w, h), &all)?;
Ok(ds)
}
pub(crate) fn write(r: &Raster, nodata: Option<f64>, path: &Path) -> Result<()> {
std::fs::write(path, to_bytes(r)?)?;
common::write_pam_with(path, None, nodata, r.bands)
}
pub(crate) fn to_bytes(r: &Raster) -> Result<Vec<u8>> {
let (precision, signed) = match r.data_type {
DataType::U8 => (8, false),
DataType::I8 => (8, true),
DataType::U16 => (16, false),
DataType::I16 => (16, true),
t => {
return Err(Error::Unsupported(format!(
"JPEG 2000 output holds 8- and 16-bit integers, not {}",
crate::info::gdal_type_name(t)
)));
}
};
let n = r.cols * r.rows;
let band = |b: usize| -> Vec<i32> {
let range = b * n..(b + 1) * n;
match &r.data {
RasterData::U8(v) => v[range].iter().map(|x| *x as i32).collect(),
RasterData::I8(v) => v[range].iter().map(|x| *x as i32).collect(),
RasterData::U16(v) => v[range].iter().map(|x| *x as i32).collect(),
RasterData::I16(v) => v[range].iter().map(|x| *x as i32).collect(),
_ => unreachable!("checked above"),
}
};
let comps: Vec<Component> = (0..r.bands).map(|b| Component { precision, signed, data: band(b) }).collect();
let codestream = encode(r.cols, r.rows, &comps)?;
let mut out = Vec::new();
let mut put = |typ: &[u8; 4], payload: &[u8]| {
out.extend(((payload.len() + 8) as u32).to_be_bytes());
out.extend(typ);
out.extend(payload);
};
put(b"jP ", &[0x0d, 0x0a, 0x87, 0x0a]);
put(b"ftyp", b"jp2 \0\0\0\0jp2 ");
let mut ihdr = Vec::new();
ihdr.extend((r.rows as u32).to_be_bytes());
ihdr.extend((r.cols as u32).to_be_bytes());
ihdr.extend((r.bands as u16).to_be_bytes());
ihdr.extend([(precision - 1) as u8 | if signed { 0x80 } else { 0 }, 7, 0, 0]);
let enumcs: u32 = if r.bands == 3 || r.bands == 4 { 16 } else { 17 };
let mut colr = vec![1u8, 0, 0];
colr.extend(enumcs.to_be_bytes());
let mut jp2h = Vec::new();
jp2h.extend(((ihdr.len() + 8) as u32).to_be_bytes());
jp2h.extend(b"ihdr");
jp2h.extend(&ihdr);
jp2h.extend(((colr.len() + 8) as u32).to_be_bytes());
jp2h.extend(b"colr");
jp2h.extend(&colr);
put(b"jp2h", &jp2h);
let tiny_cfg = wbraster::RasterConfig {
cols: 1,
rows: 1,
bands: 1,
x_min: r.x_min,
y_min: r.y_max() - r.cell_size_y,
cell_size: r.cell_size_x,
cell_size_y: Some(r.cell_size_y),
data_type: DataType::U8,
crs: r.crs.clone(),
..Default::default()
};
let tiny = Raster::from_data_native(tiny_cfg, RasterData::U8(vec![0]))?;
let tiff = super::gtiff_write::encode(&tiny, &Default::default())?;
let mut uuid = GEOJP2_UUID.to_vec();
uuid.extend(tiff);
put(b"uuid", &uuid);
put(b"jp2c", &codestream);
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_signed_samples() {
let data: Vec<i16> = (0..40 * 30).map(|i: i32| ((i * 37) % 3000 - 1500) as i16).collect();
let r = common::make_raster(
40,
30,
1,
RasterData::I16(data.clone()),
[500000.0, 10.0, 0.0, 4100000.0, 0.0, -10.0],
CrsInfo::from_epsg(32611),
None,
)
.unwrap();
let dir = tempfile::tempdir().unwrap();
let p = dir.path().join("t.jp2");
write(&r, Some(-1500.0), &p).unwrap();
let ds = read(&p).unwrap();
assert_eq!(ds.raster.data_i16().unwrap(), &data[..]);
assert_eq!(ds.crs().epsg, Some(32611));
assert_eq!(ds.geo_transform(), [500000.0, 10.0, 0.0, 4100000.0, 0.0, -10.0]);
}
#[test]
fn parses_gmljp2_origins() {
let xml = r#"<gml:RectifiedGrid dimension="2"><gml:origin><gml:Point srsName="urn:ogc:def:crs:EPSG::32633"><gml:pos>300005 5000035</gml:pos></gml:Point></gml:origin><gml:offsetVector srsName="urn:ogc:def:crs:EPSG::32633">10 0</gml:offsetVector><gml:offsetVector srsName="urn:ogc:def:crs:EPSG::32633">0 -10</gml:offsetVector></gml:RectifiedGrid>"#;
let (gt, crs) = gml_georeferencing(xml);
assert_eq!(gt.unwrap(), [300000.0, 10.0, 0.0, 5000040.0, 0.0, -10.0]);
assert_eq!(crs.unwrap().epsg, Some(32633));
}
}