Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions base/src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,20 @@ impl SRgb {
// It is checked that the components of the color are valid, so we can safely implement Eq.
impl Eq for SRgb {}

impl Lerp for SRgb {
fn lerp(self, other: Self, t: f32) -> Self {
debug_assert!(
t >= 0.0 && t <= 1.0,
"t must be in the range [0.0, 1.0] (got {})",
t
);
let r = self.0 * (1.0 - t) + other.0 * t;
let g = self.1 * (1.0 - t) + other.1 * t;
let b = self.2 * (1.0 - t) + other.2 * t;
Self(r, g, b)
}
}

/// A linear RGB color with f32 components in the range [0.0, 1.0].
/// This is a linear colorspace where the components are proportional to the actual light intensity.
#[derive(Debug, Clone, Copy, PartialEq)]
Expand Down
2 changes: 2 additions & 0 deletions base/src/color/css4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ pub const YELLOWGREEN: Rgba8 = Rgba8::from_hex(b"#9acd32");
/// Return Some(Rgba8) if the name is known
pub(super) fn lookup_name(name: &str) -> Option<Rgba8> {
match name.trim().to_ascii_lowercase().as_str() {
"transparent" => Some(TRANSPARENT),

"black" => Some(BLACK),
"silver" => Some(SILVER),
"gray" | "grey" => Some(GRAY),
Expand Down
2 changes: 1 addition & 1 deletion examples/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::sync::Arc;

use plotive::{Prepare, Style, data, des, fontdb};
use plotive_iced::Show;
use plotive_pxl::SavePng;
use plotive_pxl::PxlRender;
use plotive_svg::SaveSvg;
use rand::SeedableRng;

Expand Down
8 changes: 6 additions & 2 deletions examples/stars.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{fs, path};

use plotive::data::Source;
use plotive::des::cmap;
use plotive::des::{cmap, colorbar};
use plotive::{data, des, style};

mod common;
Expand Down Expand Up @@ -83,7 +83,11 @@ fn main() {
.with_marker(style::series::Marker::default().with_fill_opacity(0.85))
.into(),
])
.with_colorbar(des::ColorBar::default().with_title("Surface Temperature [K]".into()))
.with_colorbar(
des::ColorBar::default()
.with_title("Surface Temperature [K]".into())
.with_ticks_locator(colorbar::stellar_ticks_locator()),
)
.into(),
)
.with_title("45 bright stars".into());
Expand Down
4 changes: 2 additions & 2 deletions iced/src/show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ where
self.fig_scale = scale;
}
Message::ExportPng => {
use plotive_pxl::SavePng;
use plotive_pxl::PxlRender;
let filename = rfd::FileDialog::new()
.set_title("Save figure as PNG")
.add_filter("PNG Image", &["png"])
Expand Down Expand Up @@ -576,7 +576,7 @@ where
Message::ExportClipboard => {
use std::borrow::Cow;

use plotive_pxl::ToPixmap;
use plotive_pxl::PxlRender;

let style = if let Some(style) = &self.style {
style.clone()
Expand Down
1 change: 1 addition & 0 deletions pxl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ rustybuzz.workspace = true
tiny-skia.workspace = true
tiny-skia-path.workspace = true
ttf-parser.workspace = true
png = "0.17.16"
106 changes: 55 additions & 51 deletions pxl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub enum Error {
Io(io::Error),
Drawing(drawing::Error),
InvalidSurfaceSize(u32, u32),
PngEncoding(png::EncodingError),
}

impl From<io::Error> for Error {
Expand All @@ -23,22 +24,34 @@ impl From<drawing::Error> for Error {
}
}

impl From<png::EncodingError> for Error {
fn from(err: png::EncodingError) -> Self {
Error::PngEncoding(err)
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Io(err) => write!(f, "IO error: {}", err),
Error::Drawing(err) => write!(f, "Drawing error: {}", err),
Error::InvalidSurfaceSize(w, h) => write!(f, "Invalid surface size: {}x{}", w, h),
Error::PngEncoding(err) => write!(f, "PNG encoding error: {}", err),
}
}
}

impl std::error::Error for Error {}

/// Parameters needed for saving a figure as PNG
/// Parameters needed for rendering a figure on a pixel surface
#[derive(Debug, Clone)]
pub struct Params<'a> {
/// Styling palette
pub style: Style,
/// Scale factor between figure point size and pixel surface dimensions
///
/// e.g. a scale of 2.0 and figure size of 800x600 pts will result in an image of 1600x1200
/// pixels, thus doubling the resolution
pub scale: f32,
/// Optional font database to use for text rendering
/// This parameter is ignored when saving a prepared figure,
Expand All @@ -57,20 +70,24 @@ impl Default for Params<'_> {
}
}

/// Trait for saving a figure as PNG file
pub trait SavePng {
/// Save the figure as a PNG file at the given path.
pub trait PxlRender {
/// Rasterizes the figure on a `tiny_skia::Pixmap`
///
/// The data source parameter is ignored when saving a prepared figure,
/// as the data has already been resolved.
/// Therefore, this parameter can be left to `&()` when saving a prepared figure.
fn to_pixmap<D>(&self, data_src: &D, params: Params) -> Result<tiny_skia::Pixmap, Error>
where
D: plotive::data::Source + ?Sized;

/// Render the figure and return PNG data.
///
/// # Example
///
/// ```rust
/// use plotive::des;
/// use plotive::Prepare;
/// use plotive_pxl::{SavePng, Params};
/// use plotive_pxl::{PxlRender, Params};
///
/// // Create your figure design (this one has inline data for simplicity)
/// let fig = des::series::Line::new(
Expand All @@ -80,62 +97,49 @@ pub trait SavePng {
/// .into_figure();
///
/// // data source is not needed for inline data
/// fig.save_png("figure.png", &(), Default::default()).unwrap();
/// # std::fs::remove_file("figure.png").unwrap();
/// let data = fig.to_png_data(&(), Default::default()).unwrap();
/// ```
fn save_png<P, D>(&self, path: P, data_src: &D, params: Params) -> Result<(), Error>
fn to_png_data<D>(&self, data_src: &D, params: Params) -> Result<Vec<u8>, Error>
where
P: AsRef<Path>,
D: plotive::data::Source + ?Sized;
}

impl SavePng for plotive::des::Figure {
fn save_png<P, D>(&self, path: P, data_src: &D, params: Params) -> Result<(), Error>
where
P: AsRef<Path>,
D: plotive::data::Source + ?Sized,
{
use plotive::Prepare;

let prepared = self.prepare(data_src, params.fontdb)?;

prepared.save_png(path, &(), params)
let pixmap = self.to_pixmap(data_src, params)?;
let png_data = pixmap.encode_png()?;
Ok(png_data)
}
}

impl SavePng for drawing::PreparedFigure {
fn save_png<P, D>(&self, path: P, _data_src: &D, params: Params) -> Result<(), Error>
/// Save the figure as a PNG file at the given path.
///
/// # Example
///
/// ```rust
/// use plotive::des;
/// use plotive::Prepare;
/// use plotive_pxl::{PxlRender, Params};
///
/// // Create your figure design (this one has inline data for simplicity)
/// let fig = des::series::Line::new(
/// des::data_inline(vec![0.0, 1.0, 2.0]),
/// des::data_inline(vec![0.0, 1.0, 0.0]),
/// ).into_plot()
/// .into_figure();
///
/// // data source is not needed for inline data
/// fig.save_png("figure.png", &(), Default::default()).unwrap();
/// # std::fs::remove_file("figure.png").unwrap();
/// ```
fn save_png<P, D>(&self, path: P, data_src: &D, params: Params) -> Result<(), Error>
where
P: AsRef<Path>,
D: plotive::data::Source + ?Sized,
{
let size = self.size();
let witdth = (size.width() * params.scale) as u32;
let height = (size.height() * params.scale) as u32;

let mut surface =
PxlSurface::new(witdth, height).ok_or(Error::InvalidSurfaceSize(witdth, height))?;

self.draw(&mut surface, &params.style);

surface.save_png(path)?;
let pixmap = self.to_pixmap(data_src, params)?;
pixmap.save_png(path)?;
Ok(())
}
}

/// Trait for rasterizing a figure to a `tiny_skia::Pixmap`
pub trait ToPixmap {
/// Rasterizes the figure on a `tiny_skia::Pixmap`
///
/// The data source parameter is ignored when saving a prepared figure,
/// as the data has already been resolved.
/// Therefore, this parameter can be left to `&()` when saving a prepared figure.
fn to_pixmap<D>(&self, data_src: &D, params: Params) -> Result<tiny_skia::Pixmap, Error>
where
D: plotive::data::Source + ?Sized;
}

impl ToPixmap for plotive::des::Figure {
impl PxlRender for plotive::des::Figure {
fn to_pixmap<D>(&self, data_src: &D, params: Params) -> Result<tiny_skia::Pixmap, Error>
where
D: plotive::data::Source + ?Sized,
Expand All @@ -148,17 +152,17 @@ impl ToPixmap for plotive::des::Figure {
}
}

impl ToPixmap for drawing::PreparedFigure {
impl PxlRender for drawing::PreparedFigure {
fn to_pixmap<D>(&self, _data_src: &D, params: Params) -> Result<tiny_skia::Pixmap, Error>
where
D: plotive::data::Source + ?Sized,
{
let size = self.size();
let witdth = (size.width() * params.scale) as u32;
let height = (size.height() * params.scale) as u32;
let width = (size.width() * params.scale).round() as u32;
let height = (size.height() * params.scale).round() as u32;

let mut surface =
PxlSurface::new(witdth, height).ok_or(Error::InvalidSurfaceSize(witdth, height))?;
PxlSurface::new(width, height).ok_or(Error::InvalidSurfaceSize(width, height))?;

self.draw(&mut surface, &params.style);

Expand Down
24 changes: 24 additions & 0 deletions src/des/axis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,30 @@ pub enum Scale {
Shared(Ref),
}

impl From<(Option<f64>, Option<f64>)> for Scale {
fn from(bounds: (Option<f64>, Option<f64>)) -> Self {
Range(bounds.0, bounds.1).into()
}
}

impl From<(f64, f64)> for Scale {
fn from(bounds: (f64, f64)) -> Self {
Range(Some(bounds.0), Some(bounds.1)).into()
}
}

impl From<((), f64)> for Scale {
fn from(bounds: ((), f64)) -> Self {
Range(None, Some(bounds.1)).into()
}
}

impl From<(f64, ())> for Scale {
fn from(bounds: (f64, ())) -> Self {
Range(Some(bounds.0), None).into()
}
}

impl From<Range> for Scale {
fn from(range: Range) -> Self {
Scale::Linear(range)
Expand Down
Loading
Loading