Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ image_extras = { version = "0.1", features = ["pcx"], default-features = false }
| Feature | Format
| ------- | ------
| `ora` | OpenRaster [\[spec\]](https://www.openraster.org/)
| `otb` | OTA Bitmap (Over The Air Bitmap) [\[spec\]](https://www.wapforum.org/what/technical/SPEC-WAESpec-19990524.pdf)
| `otb` | OTA Bitmap (Over The Air Bitmap) [\[desc\]](https://en.wikipedia.org/wiki/OTA_bitmap)
| `pcx` | PCX (ZSoft Paintbrush bitmap/PiCture eXchange) [\[desc\]](https://en.wikipedia.org/wiki/PCX#PCX_file_format)
| `sgi` | SGI (Silicon Graphics Image) [\[spec\]](https://web.archive.org/web/20010413154909/https://reality.sgi.com/grafica/sgiimage.html)
| `wbmp` | Wireless Bitmap [\[spec\]](https://www.wapforum.org/what/technical/SPEC-WAESpec-19990524.pdf)
Expand Down
26 changes: 12 additions & 14 deletions src/otb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
//! OTB (Over The air Bitmap) Format is an image format from Nokia's Smart Messaging specification.
//!
//! # Related Links
//! * <https://en.wikipedia.org/wiki/Wireless_Application_Protocol_Bitmap_Format> - The WBMP format on Wikipedia
//! * <https://www.wapforum.org/what/technical/SPEC-WAESpec-19990524.pdf> - The WAP Specification
//! * <https://en.wikipedia.org/wiki/OTA_bitmap> - OTA bitmap on Wikipedia

use std::error;
use std::fmt::{self, Display};
Expand Down Expand Up @@ -54,28 +53,27 @@ impl error::Error for EncoderError {
}

/// Encoder for Otb images.
pub struct OtbEncoder<'a, W> {
writer: &'a mut W,
pub struct OtbEncoder<W> {
writer: W,
threshold: u8,
}

impl<'a, W: Write> OtbEncoder<'a, W> {
pub fn new(writer: &'a mut W) -> Result<OtbEncoder<'a, W>, ImageError> {
Ok(OtbEncoder {
impl<W: Write> OtbEncoder<W> {
pub fn new(writer: W) -> Self {
Self {
writer,
threshold: 127_u8,
})
}
}

pub fn with_threshold(mut self, threshold: u8) -> OtbEncoder<'a, W> {
pub fn with_threshold(mut self, threshold: u8) -> Self {
self.threshold = threshold;
self
}
}

impl<'a, W: Write> ImageEncoder for OtbEncoder<'a, W> {
impl<W: Write> ImageEncoder for OtbEncoder<W> {
fn write_image(
self,
mut self,
buf: &[u8],
width: u32,
height: u32,
Expand Down Expand Up @@ -451,7 +449,7 @@ mod test {
0b00011000, // row8
];
let mut encoded_data = Vec::<u8>::with_capacity(expected_data.len());
let encoder = crate::otb::OtbEncoder::new(&mut encoded_data).unwrap();
let encoder = crate::otb::OtbEncoder::new(&mut encoded_data);
encoder
.write_image(&img_data, 8, 8, image::ExtendedColorType::L8)
.unwrap();
Expand Down Expand Up @@ -489,7 +487,7 @@ mod test {
0b00000000, 0b00000000, // row9
];
let mut encoded_data = Vec::<u8>::with_capacity(expected_data.len());
let encoder = crate::otb::OtbEncoder::new(&mut encoded_data).unwrap();
let encoder = crate::otb::OtbEncoder::new(&mut encoded_data);
encoder
.write_image(&img_data, 10, 10, image::ExtendedColorType::L8)
.unwrap();
Expand Down
38 changes: 14 additions & 24 deletions src/wbmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,25 @@ use image::error::{
use image::{ColorType, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageResult};

/// Encoder for Wbmp images.
pub struct WbmpEncoder<'a, W> {
writer: Option<W>,
inner: Option<wbmp::Encoder<'a, W>>,
pub struct WbmpEncoder<W> {
writer: W,
threshold: u8,
}

impl<'a, W: Write> WbmpEncoder<'a, W> {
pub fn new(writer: W) -> Result<WbmpEncoder<'a, W>, ImageError> {
let threshold = 127_u8;

Ok(WbmpEncoder {
writer: Some(writer),
inner: None,
threshold,
})
impl<W: Write> WbmpEncoder<W> {
pub fn new(writer: W) -> Self {
Self {
writer,
threshold: 127,
}
}

pub fn with_threshold(mut self, threshold: u8) -> WbmpEncoder<'a, W> {
pub fn with_threshold(mut self, threshold: u8) -> Self {
self.threshold = threshold;
self
}
}

impl<'a, W: Write> ImageEncoder for WbmpEncoder<'a, W> {
impl<W: Write> ImageEncoder for WbmpEncoder<W> {
fn write_image(
mut self,
buf: &[u8],
Expand All @@ -55,15 +50,10 @@ impl<'a, W: Write> ImageEncoder for WbmpEncoder<'a, W> {
}
};

if let Some(mut inner) = self.inner {
inner.encode(buf, width, height, color)
} else {
let mut writer = self.writer.take().unwrap();
let mut encoder = wbmp::Encoder::new(&mut writer);
encoder.encode(buf, width, height, color)
}
.map_err(convert_wbmp_error)?;
Ok(())
wbmp::Encoder::new(&mut self.writer)
.with_threshold(self.threshold)
.encode(buf, width, height, color)
.map_err(convert_wbmp_error)
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/xbm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ mod tests {
#[test]
fn image_without_hotspot() {
let decoder = XbmDecoder::new(BufReader::new(
File::open("tests/images/xbm/1x1.xbm").unwrap(),
File::open("tests/decode/xbm/1x1.xbm").unwrap(),
))
.expect("Unable to read XBM file");

Expand All @@ -665,7 +665,7 @@ mod tests {
#[test]
fn image_with_hotspot() {
let decoder = XbmDecoder::new(BufReader::new(
File::open("tests/images/xbm/hotspot.xbm").unwrap(),
File::open("tests/decode/xbm/hotspot.xbm").unwrap(),
))
.expect("Unable to read XBM file");

Expand Down
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
1 change: 1 addition & 0 deletions tests/encode/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.png
Binary file added tests/encode/otb threshold/test-image_L8.otb
Binary file not shown.
Binary file added tests/encode/otb/test-image_L8.otb
Binary file not shown.
Binary file added tests/encode/wbmp threshold/test-image_L8.wbmp
Binary file not shown.
Binary file not shown.
Binary file added tests/encode/wbmp/test-image_L8.wbmp
Binary file not shown.
Binary file added tests/encode/wbmp/test-image_Rgba8.wbmp
Binary file not shown.
134 changes: 130 additions & 4 deletions tests/reference.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use image::ColorType;
use std::path::{Path, PathBuf};

use image::{ColorType, DynamicImage, ImageResult};
use walkdir::WalkDir;

/// Test decoding of all test images in `tests/images/` against reference images
fn is_bless() -> bool {
std::env::var("BLESS").is_ok()
}

/// Test decoding of all test images in `tests/decode/` against reference images
/// (either PNG or TIFF).
///
/// ## Bless mode
Expand All @@ -20,10 +26,10 @@ use walkdir::WalkDir;
fn test_decoding() {
image_extras::register();

let bless = std::env::var("BLESS").is_ok();
let bless = is_bless();
let mut errors = vec![];

for entry in WalkDir::new("tests/images") {
for entry in WalkDir::new("tests/decode") {
let entry = entry.unwrap();
if !entry.file_type().is_file()
|| entry.path().extension().unwrap() == "png"
Expand Down Expand Up @@ -82,3 +88,123 @@ fn test_decoding() {
panic!("Decoding errors:\n{}", errors.join("\n"));
}
}

#[test]
fn test_encoding() {
image_extras::register();

let test_image = &NamedImage::from_samples("test-image.png");

encode(
"otb",
"otb",
|w, img| img.write_with_encoder(image_extras::otb::OtbEncoder::new(w)),
&[&test_image.with_color_type(ColorType::L8)],
);
encode(
"otb threshold",
"otb",
|w, img| img.write_with_encoder(image_extras::otb::OtbEncoder::new(w).with_threshold(255)),
&[&test_image.with_color_type(ColorType::L8)],
);
encode(
"wbmp",
"wbmp",
|w, img| img.write_with_encoder(image_extras::wbmp::WbmpEncoder::new(w)),
&[
&test_image.with_color_type(ColorType::L8),
&test_image.with_color_type(ColorType::Rgba8),
],
);
encode(
"wbmp threshold",
"wbmp",
|w, img| {
img.write_with_encoder(image_extras::wbmp::WbmpEncoder::new(w).with_threshold(255))
},
&[
&test_image.with_color_type(ColorType::L8),
&test_image.with_color_type(ColorType::Rgba8),
],
);

fn encode(
dir: &str,
ext: &str,
write: impl Fn(&mut Vec<u8>, &DynamicImage) -> ImageResult<()>,
images: &[&NamedImage],
) {
let bless = is_bless();
let out_dir = PathBuf::from("tests/encode").join(dir);
std::fs::create_dir_all(&out_dir).unwrap();

for &image in images {
let mut buffer = Vec::new();
let result = write(&mut buffer, &image.image);
if let Err(e) = result {
panic!("Failed to encode image {} for {dir}: {e}", image.name);
}

let out_path = out_dir.join(format!("{}.{}", image.name, ext));
if bless || !out_path.exists() {
std::fs::write(&out_path, &buffer).unwrap();
} else {
let reference = std::fs::read(&out_path).unwrap();
assert_eq!(
buffer,
reference,
"Encoded image {} does not match reference at {}",
image.name,
out_path.display()
);
}

let reopen = match image::open(&out_path) {
Ok(i) => i,
Err(e) => {
panic!(
"Cannot reopen encoded image {} at {}: {e}",
image.name,
out_path.display()
);
}
};
reopen.save(out_path.with_added_extension("png")).unwrap();
}
}

#[derive(Clone)]
struct NamedImage {
name: String,
image: DynamicImage,
}
impl NamedImage {
fn from_samples(name: &str) -> Self {
Self::from_file(&PathBuf::from("tests/samples").join(name))
}
fn from_file(path: &Path) -> Self {
let name = path.file_stem().unwrap().to_string_lossy().to_string();
let image = image::open(path).unwrap();
Self { name, image }
}

fn with_color_type(&self, color_type: ColorType) -> Self {
let image: DynamicImage = match color_type {
ColorType::L8 => self.image.to_luma8().into(),
ColorType::La8 => self.image.to_luma_alpha8().into(),
ColorType::Rgb8 => self.image.to_rgb8().into(),
ColorType::Rgba8 => self.image.to_rgba8().into(),
ColorType::L16 => self.image.to_luma16().into(),
ColorType::La16 => self.image.to_luma_alpha16().into(),
ColorType::Rgb16 => self.image.to_rgb16().into(),
ColorType::Rgba16 => self.image.to_rgba16().into(),
_ => panic!("Unsupported color type {:?}", color_type),
};

Self {
name: format!("{}_{:?}", self.name, color_type),
image,
}
}
}
}
Binary file added tests/samples/test-image.png
Loading