Commit 916bbf8a authored by Steven vanZyl's avatar Steven vanZyl
Browse files

Feature Flags and Viewer

Added a number of feature flags for different
parts of the code and dependencies,
as well as the tools that go with them.

Also merged in the Viewer I created a while ago in a different
project, since with feature flags it now can be disabled.
parent baaef9ef
......@@ -2,13 +2,48 @@
name = "ssif"
version = "0.1.0"
authors = ["Steven vanZyl <rushsteve1@rushsteve1.us>"]
repository = "https://git.rushsteve1.us/rushsteve1/ssif"
readme = "./README.md"
license = "MIT"
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
autobins = false
[dependencies]
itertools = "0.8.0"
serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0"
png = "^0.15.0"
\ No newline at end of file
serde = { version = "^1.0", features = ["derive"], optional = true }
serde_json = { version = "^1.0", optional = true }
png = { version = "^0.15.0", optional = true }
minifb = { version = "0.13.0", optional = true }
[features]
default = ["png_convert"]
png_convert = ["png"]
json = ["serde", "serde_json"]
viewer = ["minifb"]
[[bin]]
name = "to_json"
path = "src/bin/to_json.rs"
required-features = ["json"]
[[bin]]
name = "from_json"
path = "src/bin/from_json.rs"
required-features = ["json"]
[[bin]]
name = "to_png"
path = "src/bin/to_png.rs"
required-features = ["png_convert"]
[[bin]]
name = "from_png"
path = "src/bin/from_png.rs"
required-features = ["png_convert"]
[[bin]]
name = "viewer"
path = "src/bin/viewer.rs"
required-features = ["viewer"]
Copyright 2019 Steven vanZyl
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in th
e Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so, subj
ect to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all c
opies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
ED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYR
IGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WIT
H THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#![cfg(feature = "viewer")]
use std::env::args;
use std::path::PathBuf;
use std::process::exit;
use minifb::{Key, Window, WindowOptions};
use ssif::Image;
fn main() {
// Load the image if it can
let image: Image = if let Some(path) = args().skip(1).next() {
let path = PathBuf::from(path);
if path.exists() {
Image::from_file(path.to_str().unwrap()).unwrap()
} else {
eprintln!("file does not exist");
exit(1);
}
} else {
eprintln!("Please provide a path to an SSIF file as the first argument");
exit(1);
};
// Create the window
let mut window = Window::new(
"SSIF Viewer",
image.width() as usize,
image.height() as usize,
WindowOptions::default(),
)
.unwrap();
// Load into a buffer
let buffer: Vec<u32> = image.pixels().map(|pix| pix.as_u32()).collect();
// Constantly render to the window
while window.is_open() && !window.is_key_down(Key::Escape) {
window.update_with_buffer(&buffer).unwrap();
}
}
......@@ -87,6 +87,7 @@ impl Image {
}
/// Decodes an SSIF file from a JSON string
#[cfg(feature = "json")]
pub fn from_json<T>(json_string: T) -> serde_json::Result<Image>
where
T: AsRef<str>,
......@@ -94,6 +95,7 @@ impl Image {
serde_json::from_str(json_string.as_ref())
}
#[cfg(feature = "png_convert")]
pub fn from_png_file<T>(path: T, compress: bool) -> Result<Self, Error>
where
T: AsRef<str>,
......
......@@ -23,8 +23,7 @@ impl Image {
let data = self
.data
.iter()
.cloned()
.into_iter()
.coalesce(|p, c| {
if p.color == c.color && compress {
Ok(Run::counted(p.color, p.count + c.count))
......@@ -34,7 +33,7 @@ impl Image {
})
.map(|p| Vec::from(&p.bytes() as &[u8]))
.flatten();
Ok(buf.iter().cloned().chain(data).collect())
Ok(buf.into_iter().chain(data).collect())
}
/// Encodes an SSIF image and writes it to a file at the given path.
......@@ -47,11 +46,13 @@ impl Image {
}
/// Encodes the image as a JSON string
#[cfg(feature = "json")]
pub fn to_json(self) -> serde_json::Result<String> {
serde_json::to_string(&self)
}
/// Converts an SSIF image to PNG and writes it to a file
#[cfg(feature = "png_convert")]
pub fn to_png_file<T>(self, path: T) -> Result<(), Error>
where
T: AsRef<str>,
......@@ -66,14 +67,12 @@ impl Image {
let mut writer = encoder.write_header().unwrap();
let data = self
.pixels()
.pixels_count(size)
.map(|p| {
let (r, g, b, a) = p.scale_up();
vec![r, g, b, a].into_iter()
})
.flatten()
.take(size * 4)
.pad_using(size * 4, |_| 0)
.collect_vec();
writer.write_image_data(data.as_slice()).unwrap();
......
use itertools::Itertools;
#[cfg(feature = "json")]
use serde::{Deserialize, Serialize};
/// The current version of SSIF this decoder is for.
......@@ -25,6 +27,7 @@ pub enum Error {
/// An I/O error occured.
IOError(std::io::Error),
/// An error happened in Serde
#[cfg(feature = "json")]
JsonError(serde_json::Error),
}
......@@ -34,6 +37,7 @@ impl From<std::io::Error> for Error {
}
}
#[cfg(feature = "json")]
impl From<serde_json::Error> for Error {
fn from(f: serde_json::Error) -> Self {
Self::JsonError(f)
......@@ -41,19 +45,20 @@ impl From<serde_json::Error> for Error {
}
/// A single pixel's color represented by SSIF as RGBA that was decoded from the file.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct Color {
/// Red component of the color
#[serde(alias = "r")]
#[cfg_attr(feature = "json", serde(alias = "r"))]
red: u8,
/// Green component of the color
#[serde(alias = "g")]
#[cfg_attr(feature = "json", serde(alias = "g"))]
green: u8,
/// Blue component of the color
#[serde(alias = "b")]
#[cfg_attr(feature = "json", serde(alias = "b"))]
blue: u8,
/// Alpha (transparency) component of the color
#[serde(alias = "a")]
#[cfg_attr(feature = "json", serde(alias = "a"))]
alpha: u8,
}
......@@ -86,16 +91,15 @@ impl Color {
/// Converts the 4 bytes of the color into one 32-bit number
pub fn as_u32(&self) -> u32 {
((self.red as u32) << 24) +
((self.green as u32) << 16) +
((self.blue as u32) << 8) +
((self.alpha as u32))
let (r, g, b, a) = self.scale_up();
((r as u32) << 24) + ((g as u32) << 16) + ((b as u32) << 8) + (a as u32)
}
}
/// A run is a series of pixels with the same color and is used for
/// compression within SSIF
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct Run {
/// The number of pixels that have this color
count: u8,
......@@ -126,7 +130,8 @@ impl Run {
}
/// Represents an image that has be fully decoded and is now ready to be used
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct Image {
/// Version specified in the file.
version: u8,
......@@ -187,19 +192,27 @@ impl Image {
None
}
/// Returns an iterator over all the pixels in the image
pub fn pixels(self) -> Pixels {
Pixels::new(self)
}
pub fn pixels_count(self, count: usize) -> impl Iterator<Item=Color> {
self.pixels()
.take(count)
.pad_using(count, |_| { Color::new() }).into_iter()
}
}
/// An iterator over all the pixels in an image.
/// This is a fused iterator that will stop after the first `None`.
#[derive(Debug)]
pub struct Pixels {
image: Image,
run: Run,
index: usize,
run_ind: u8,
fused: bool
fused: bool,
}
impl Pixels {
......@@ -209,7 +222,7 @@ impl Pixels {
run: Run::new(Color::new()), // Temporary
index: 0,
run_ind: 1,
fused: false
fused: false,
}
}
}
......@@ -218,13 +231,11 @@ impl Iterator for Pixels {
type Item = Color;
fn next(&mut self) -> Option<Self::Item> {
// Slow version alternate version
/*
// Slow version alternate version that always works
self.index += 1;
self.image.get_pixel_at(self.index - 1)
*/
/*
if !self.fused && self.index < self.image.data.len() {
// By only changing at the end of each run, and stepping
// Through runs only when needed it is MUCH faster
......@@ -240,6 +251,7 @@ impl Iterator for Pixels {
self.fused = true;
None
}
*/
}
}
......
......@@ -99,6 +99,7 @@ fn encode_rr_file_simple_run_compressed() {
}
#[test]
#[cfg(feature = "json")]
fn encode_to_json() {
assert_eq!(
Image::one_pixel().to_json().unwrap(),
......@@ -107,6 +108,7 @@ fn encode_to_json() {
}
#[test]
#[cfg(feature = "json")]
fn decode_from_json() {
assert_eq!(
Image::from_json(
......@@ -117,6 +119,7 @@ fn decode_from_json() {
}
#[test]
#[cfg(feature = "json")]
fn json_rr() {
assert_eq!(
Image::one_pixel(),
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment