use std::cmp::min;
use std::marker::PhantomData;
use std::ptr::NonNull;
use std::slice;
use cast::i32;
use nalgebra::{Dim, Matrix, storage::Storage};
use crate::color::{Color, color_to_rgba};
use crate::drawing_ctx::set_source_color_on_cairo;
use crate::error::*;
use crate::rect::{IRect, Rect};
use crate::surface_utils::srgb;
use crate::util::clamp;
use super::{
AsCairoARGB, CairoARGB, EdgeMode, ImageSurfaceDataExt, Pixel, PixelOps, ToCairoARGB, ToPixel,
iterators::{PixelRectangle, Pixels},
};
pub enum Interpolation {
Nearest,
Smooth,
}
impl From<Interpolation> for cairo::Filter {
fn from(i: Interpolation) -> cairo::Filter {
match i {
Interpolation::Nearest => cairo::Filter::Nearest,
Interpolation::Smooth => cairo::Filter::Good,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum SurfaceType {
SRgb,
LinearRgb,
AlphaOnly,
}
impl SurfaceType {
pub fn combine(self, other: SurfaceType) -> SurfaceType {
match (self, other) {
(SurfaceType::AlphaOnly, t) => t,
(t, SurfaceType::AlphaOnly) => t,
(t1, t2) if t1 == t2 => t1,
_ => panic!(),
}
}
}
pub enum Operator {
Over,
In,
Out,
Atop,
Xor,
Multiply,
Screen,
Darken,
Lighten,
Overlay,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
HslHue,
HslSaturation,
HslColor,
HslLuminosity,
}
#[derive(Debug, Clone)]
pub struct ImageSurface<T> {
surface: cairo::ImageSurface,
data_ptr: NonNull<u8>, width: i32,
height: i32,
stride: isize,
surface_type: SurfaceType,
_state: PhantomData<T>,
}
#[derive(Debug, Clone)]
pub struct Shared;
pub type SharedImageSurface = ImageSurface<Shared>;
#[derive(Debug, Clone)]
pub struct Exclusive;
pub type ExclusiveImageSurface = ImageSurface<Exclusive>;
unsafe impl Sync for SharedImageSurface {}
pub trait BlurDirection {
const IS_VERTICAL: bool;
}
pub enum Vertical {}
pub enum Horizontal {}
impl BlurDirection for Vertical {
const IS_VERTICAL: bool = true;
}
impl BlurDirection for Horizontal {
const IS_VERTICAL: bool = false;
}
pub trait IsAlphaOnly {
const IS_ALPHA_ONLY: bool;
}
pub enum AlphaOnly {}
pub enum NotAlphaOnly {}
pub struct Rows<'a> {
surface: &'a SharedImageSurface,
next_row: i32,
}
pub struct RowsMut<'a> {
data: cairo::ImageSurfaceData<'a>,
width: i32,
height: i32,
stride: i32,
next_row: i32,
}
impl IsAlphaOnly for AlphaOnly {
const IS_ALPHA_ONLY: bool = true;
}
impl IsAlphaOnly for NotAlphaOnly {
const IS_ALPHA_ONLY: bool = false;
}
impl<T> ImageSurface<T> {
#[inline]
pub fn width(&self) -> i32 {
self.width
}
#[inline]
pub fn height(&self) -> i32 {
self.height
}
#[inline]
pub fn stride(&self) -> isize {
self.stride
}
}
impl ImageSurface<Shared> {
#[inline]
pub fn wrap(
surface: cairo::ImageSurface,
surface_type: SurfaceType,
) -> Result<SharedImageSurface, cairo::Error> {
assert_eq!(surface.format(), cairo::Format::ARgb32);
let reference_count =
unsafe { cairo::ffi::cairo_surface_get_reference_count(surface.to_raw_none()) };
assert_eq!(reference_count, 1);
let (width, height) = (surface.width(), surface.height());
if !(width > 0 && height > 0) {
return Err(cairo::Error::InvalidSize);
}
surface.flush();
let data_ptr = NonNull::new(unsafe {
cairo::ffi::cairo_image_surface_get_data(surface.to_raw_none())
})
.unwrap();
let stride = surface.stride() as isize;
Ok(SharedImageSurface {
surface,
data_ptr,
width,
height,
stride,
surface_type,
_state: PhantomData,
})
}
#[inline]
pub fn copy_from_surface(surface: &cairo::ImageSurface) -> Result<Self, cairo::Error> {
let copy =
cairo::ImageSurface::create(cairo::Format::ARgb32, surface.width(), surface.height())?;
{
let cr = cairo::Context::new(©)?;
cr.set_source_surface(surface, 0f64, 0f64)?;
cr.paint()?;
}
SharedImageSurface::wrap(copy, SurfaceType::SRgb)
}
#[inline]
pub fn empty(width: i32, height: i32, surface_type: SurfaceType) -> Result<Self, cairo::Error> {
let s = cairo::ImageSurface::create(cairo::Format::ARgb32, width, height)?;
SharedImageSurface::wrap(s, surface_type)
}
#[inline]
pub fn into_image_surface(self) -> Result<cairo::ImageSurface, cairo::Error> {
let reference_count =
unsafe { cairo::ffi::cairo_surface_get_reference_count(self.surface.to_raw_none()) };
if reference_count == 1 {
Ok(self.surface)
} else {
self.copy_surface(IRect::from_size(self.width, self.height))
}
}
pub fn from_image(
image: &image::DynamicImage,
content_type: Option<&str>,
mime_data: Option<Vec<u8>>,
) -> Result<SharedImageSurface, cairo::Error> {
let rgba_image = image.to_rgba8();
let width = i32(rgba_image.width()).map_err(|_| cairo::Error::InvalidSize)?;
let height = i32(rgba_image.height()).map_err(|_| cairo::Error::InvalidSize)?;
let mut surf = ExclusiveImageSurface::new(width, height, SurfaceType::SRgb)?;
rgba_image
.rows()
.zip(surf.rows_mut())
.flat_map(|(src_row, dest_row)| src_row.zip(dest_row.iter_mut()))
.for_each(|(src, dest)| *dest = src.to_pixel().premultiply().to_cairo_argb());
if let (Some(content_type), Some(bytes)) = (content_type, mime_data) {
surf.surface.set_mime_data(content_type, bytes)?;
}
surf.share()
}
#[inline]
fn is_alpha_only(&self) -> bool {
self.surface_type == SurfaceType::AlphaOnly
}
#[inline]
pub fn surface_type(&self) -> SurfaceType {
self.surface_type
}
#[inline]
pub fn get_pixel(&self, x: u32, y: u32) -> Pixel {
assert!(x < self.width as u32);
assert!(y < self.height as u32);
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe {
*(self
.data_ptr
.as_ptr()
.offset(y as isize * self.stride + x as isize * 4) as *const u32)
};
Pixel::from_u32(value)
}
#[inline]
pub fn get_pixel_by_offset(&self, offset: isize) -> Pixel {
assert!(offset < self.stride * self.height as isize);
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe { *(self.data_ptr.as_ptr().offset(offset) as *const u32) };
Pixel::from_u32(value)
}
#[inline]
pub fn set_as_source_surface(
&self,
cr: &cairo::Context,
x: f64,
y: f64,
) -> Result<(), cairo::Error> {
cr.set_source_surface(&self.surface, x, y)
}
pub fn to_cairo_pattern(&self) -> cairo::SurfacePattern {
cairo::SurfacePattern::create(&self.surface)
}
fn copy_surface(&self, bounds: IRect) -> Result<cairo::ImageSurface, cairo::Error> {
let output_surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
let cr = cairo::Context::new(&output_surface)?;
let r = cairo::Rectangle::from(bounds);
cr.rectangle(r.x(), r.y(), r.width(), r.height());
cr.clip();
cr.set_source_surface(&self.surface, 0f64, 0f64)?;
cr.paint()?;
Ok(output_surface)
}
pub fn scale_to(
&self,
width: i32,
height: i32,
bounds: IRect,
x: f64,
y: f64,
) -> Result<SharedImageSurface, cairo::Error> {
let output_surface = cairo::ImageSurface::create(cairo::Format::ARgb32, width, height)?;
{
let cr = cairo::Context::new(&output_surface)?;
let r = cairo::Rectangle::from(bounds);
cr.rectangle(r.x(), r.y(), r.width(), r.height());
cr.clip();
cr.scale(x, y);
self.set_as_source_surface(&cr, 0.0, 0.0)?;
cr.paint()?;
}
SharedImageSurface::wrap(output_surface, self.surface_type)
}
#[inline]
pub fn scale(
&self,
bounds: IRect,
x: f64,
y: f64,
) -> Result<(SharedImageSurface, IRect), cairo::Error> {
let new_width = (f64::from(self.width) * x).ceil() as i32;
let new_height = (f64::from(self.height) * y).ceil() as i32;
let new_bounds = bounds.scale(x, y);
Ok((
self.scale_to(new_width, new_height, new_bounds, x, y)?,
new_bounds,
))
}
pub fn extract_alpha(&self, bounds: IRect) -> Result<SharedImageSurface, cairo::Error> {
let mut output_surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
let output_stride = output_surface.stride() as usize;
{
let mut output_data = output_surface.data().unwrap();
for (x, y, Pixel { a, .. }) in Pixels::within(self, bounds) {
let output_pixel = Pixel {
r: 0,
g: 0,
b: 0,
a,
};
output_data.set_pixel(output_stride, output_pixel, x, y);
}
}
SharedImageSurface::wrap(output_surface, SurfaceType::AlphaOnly)
}
pub fn to_luminance_mask(&self) -> Result<SharedImageSurface, cairo::Error> {
let bounds = IRect::from_size(self.width, self.height);
let mut output_surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
let stride = output_surface.stride() as usize;
{
let mut data = output_surface.data().unwrap();
for (x, y, pixel) in Pixels::within(self, bounds) {
data.set_pixel(stride, pixel.to_luminance_mask(), x, y);
}
}
SharedImageSurface::wrap(output_surface, self.surface_type)
}
pub fn unpremultiply(&self, bounds: IRect) -> Result<SharedImageSurface, cairo::Error> {
if self.is_alpha_only() {
return Ok(self.clone());
}
let mut output_surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
let stride = output_surface.stride() as usize;
{
let mut data = output_surface.data().unwrap();
for (x, y, pixel) in Pixels::within(self, bounds) {
data.set_pixel(stride, pixel.unpremultiply(), x, y);
}
}
SharedImageSurface::wrap(output_surface, self.surface_type)
}
#[inline]
pub fn to_linear_rgb(&self, bounds: IRect) -> Result<SharedImageSurface, cairo::Error> {
match self.surface_type {
SurfaceType::LinearRgb | SurfaceType::AlphaOnly => Ok(self.clone()),
_ => srgb::linearize_surface(self, bounds),
}
}
#[inline]
pub fn to_srgb(&self, bounds: IRect) -> Result<SharedImageSurface, cairo::Error> {
match self.surface_type {
SurfaceType::SRgb | SurfaceType::AlphaOnly => Ok(self.clone()),
_ => srgb::unlinearize_surface(self, bounds),
}
}
pub fn convolve<R: Dim, C: Dim, S: Storage<f64, R, C>>(
&self,
bounds: IRect,
target: (i32, i32),
kernel: &Matrix<f64, R, C, S>,
edge_mode: EdgeMode,
) -> Result<SharedImageSurface, cairo::Error> {
assert!(kernel.nrows() >= 1);
assert!(kernel.ncols() >= 1);
let mut output_surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
let output_stride = output_surface.stride() as usize;
{
let mut output_data = output_surface.data().unwrap();
if self.is_alpha_only() {
for (x, y, _pixel) in Pixels::within(self, bounds) {
let kernel_bounds = IRect::new(
x as i32 - target.0,
y as i32 - target.1,
x as i32 - target.0 + kernel.ncols() as i32,
y as i32 - target.1 + kernel.nrows() as i32,
);
let mut a = 0.0;
for (x, y, pixel) in
PixelRectangle::within(self, bounds, kernel_bounds, edge_mode)
{
let kernel_x = (kernel_bounds.x1 - x - 1) as usize;
let kernel_y = (kernel_bounds.y1 - y - 1) as usize;
let factor = kernel[(kernel_y, kernel_x)];
a += f64::from(pixel.a) * factor;
}
let convert = |x: f64| (clamp(x, 0.0, 255.0) + 0.5) as u8;
let output_pixel = Pixel {
r: 0,
g: 0,
b: 0,
a: convert(a),
};
output_data.set_pixel(output_stride, output_pixel, x, y);
}
} else {
for (x, y, _pixel) in Pixels::within(self, bounds) {
let kernel_bounds = IRect::new(
x as i32 - target.0,
y as i32 - target.1,
x as i32 - target.0 + kernel.ncols() as i32,
y as i32 - target.1 + kernel.nrows() as i32,
);
let mut r = 0.0;
let mut g = 0.0;
let mut b = 0.0;
let mut a = 0.0;
for (x, y, pixel) in
PixelRectangle::within(self, bounds, kernel_bounds, edge_mode)
{
let kernel_x = (kernel_bounds.x1 - x - 1) as usize;
let kernel_y = (kernel_bounds.y1 - y - 1) as usize;
let factor = kernel[(kernel_y, kernel_x)];
r += f64::from(pixel.r) * factor;
g += f64::from(pixel.g) * factor;
b += f64::from(pixel.b) * factor;
a += f64::from(pixel.a) * factor;
}
let convert = |x: f64| (clamp(x, 0.0, 255.0) + 0.5) as u8;
let output_pixel = Pixel {
r: convert(r),
g: convert(g),
b: convert(b),
a: convert(a),
};
output_data.set_pixel(output_stride, output_pixel, x, y);
}
}
}
SharedImageSurface::wrap(output_surface, self.surface_type)
}
pub fn box_blur_loop<B: BlurDirection, A: IsAlphaOnly>(
&self,
output_surface: &mut cairo::ImageSurface,
bounds: IRect,
kernel_size: usize,
target: usize,
) {
assert_ne!(kernel_size, 0);
assert!(target < kernel_size);
assert_eq!(self.is_alpha_only(), A::IS_ALPHA_ONLY);
{
struct UnsafeSendPixelData<'a> {
width: u32,
height: u32,
stride: isize,
ptr: NonNull<u8>,
_marker: PhantomData<&'a mut ()>,
}
unsafe impl<'a> Send for UnsafeSendPixelData<'a> {}
impl<'a> UnsafeSendPixelData<'a> {
#[inline]
unsafe fn new(surface: &mut cairo::ImageSurface) -> Self {
assert_eq!(surface.format(), cairo::Format::ARgb32);
let ptr = surface.data().unwrap().as_mut_ptr();
Self {
width: surface.width() as u32,
height: surface.height() as u32,
stride: surface.stride() as isize,
ptr: NonNull::new(ptr).unwrap(),
_marker: PhantomData,
}
}
#[inline]
fn set_pixel(&mut self, pixel: Pixel, x: u32, y: u32) {
assert!(x < self.width);
assert!(y < self.height);
let value = pixel.to_u32();
#[allow(clippy::cast_ptr_alignment)]
unsafe {
let ptr = self
.ptr
.as_ptr()
.offset(y as isize * self.stride + x as isize * 4)
as *mut u32;
*ptr = value;
}
}
#[inline]
fn split_at_row(self, index: u32) -> (Self, Self) {
assert!(index <= self.height);
(
UnsafeSendPixelData {
width: self.width,
height: index,
stride: self.stride,
ptr: self.ptr,
_marker: PhantomData,
},
UnsafeSendPixelData {
width: self.width,
height: self.height - index,
stride: self.stride,
ptr: NonNull::new(unsafe {
self.ptr.as_ptr().offset(index as isize * self.stride)
})
.unwrap(),
_marker: PhantomData,
},
)
}
#[inline]
fn split_at_column(self, index: u32) -> (Self, Self) {
assert!(index <= self.width);
(
UnsafeSendPixelData {
width: index,
height: self.height,
stride: self.stride,
ptr: self.ptr,
_marker: PhantomData,
},
UnsafeSendPixelData {
width: self.width - index,
height: self.height,
stride: self.stride,
ptr: NonNull::new(unsafe {
self.ptr.as_ptr().offset(index as isize * 4)
})
.unwrap(),
_marker: PhantomData,
},
)
}
}
let output_data = unsafe { UnsafeSendPixelData::new(output_surface) };
let shift = (kernel_size - target) as i32;
let target = target as i32;
let kernel_size_f64 = kernel_size as f64;
let compute = |x: u32| (f64::from(x) / kernel_size_f64 + 0.5) as u8;
let (main_axis_min, main_axis_max, other_axis_min, other_axis_max) = if B::IS_VERTICAL {
(bounds.y0, bounds.y1, bounds.x0, bounds.x1)
} else {
(bounds.x0, bounds.x1, bounds.y0, bounds.y1)
};
let pixel = |i, j| {
let (x, y) = if B::IS_VERTICAL { (i, j) } else { (j, i) };
self.get_pixel(x as u32, y as u32)
};
let mut output_data = if B::IS_VERTICAL {
output_data.split_at_column(bounds.x0 as u32).1
} else {
output_data.split_at_row(bounds.y0 as u32).1
};
rayon::scope(|s| {
for i in other_axis_min..other_axis_max {
let (mut current, remaining) = if B::IS_VERTICAL {
output_data.split_at_column(1)
} else {
output_data.split_at_row(1)
};
output_data = remaining;
s.spawn(move |_| {
let mut set_pixel = |j, pixel| {
let (x, y) = if B::IS_VERTICAL { (0, j) } else { (j, 0) };
current.set_pixel(pixel, x, y);
};
let mut sum_r = 0;
let mut sum_g = 0;
let mut sum_b = 0;
let mut sum_a = 0;
for j in main_axis_min..min(main_axis_max, main_axis_min + shift) {
let Pixel { r, g, b, a } = pixel(i, j);
if !A::IS_ALPHA_ONLY {
sum_r += u32::from(r);
sum_g += u32::from(g);
sum_b += u32::from(b);
}
sum_a += u32::from(a);
}
set_pixel(
main_axis_min as u32,
Pixel {
r: compute(sum_r),
g: compute(sum_g),
b: compute(sum_b),
a: compute(sum_a),
},
);
let start_subtracting_at = main_axis_min + target + 1;
let stop_adding_at = main_axis_max - shift + 1;
for j in main_axis_min + 1..main_axis_max {
if j >= start_subtracting_at {
let old_pixel = pixel(i, j - target - 1);
if !A::IS_ALPHA_ONLY {
sum_r -= u32::from(old_pixel.r);
sum_g -= u32::from(old_pixel.g);
sum_b -= u32::from(old_pixel.b);
}
sum_a -= u32::from(old_pixel.a);
}
if j < stop_adding_at {
let new_pixel = pixel(i, j + shift - 1);
if !A::IS_ALPHA_ONLY {
sum_r += u32::from(new_pixel.r);
sum_g += u32::from(new_pixel.g);
sum_b += u32::from(new_pixel.b);
}
sum_a += u32::from(new_pixel.a);
}
set_pixel(
j as u32,
Pixel {
r: compute(sum_r),
g: compute(sum_g),
b: compute(sum_b),
a: compute(sum_a),
},
);
}
});
}
});
}
unsafe { cairo::ffi::cairo_surface_mark_dirty(output_surface.to_raw_none()) }
}
#[inline]
pub fn box_blur<B: BlurDirection>(
&self,
bounds: IRect,
kernel_size: usize,
target: usize,
) -> Result<SharedImageSurface, cairo::Error> {
let mut output_surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
if self.is_alpha_only() {
self.box_blur_loop::<B, AlphaOnly>(&mut output_surface, bounds, kernel_size, target);
} else {
self.box_blur_loop::<B, NotAlphaOnly>(&mut output_surface, bounds, kernel_size, target);
}
SharedImageSurface::wrap(output_surface, self.surface_type)
}
#[inline]
pub fn flood(&self, bounds: IRect, color: Color) -> Result<SharedImageSurface, cairo::Error> {
let output_surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
let rgba = color_to_rgba(&color);
if rgba.alpha > 0.0 {
let cr = cairo::Context::new(&output_surface)?;
let r = cairo::Rectangle::from(bounds);
cr.rectangle(r.x(), r.y(), r.width(), r.height());
cr.clip();
set_source_color_on_cairo(&cr, &color);
cr.paint()?;
}
SharedImageSurface::wrap(output_surface, self.surface_type)
}
#[inline]
pub fn offset(
&self,
bounds: Rect,
dx: f64,
dy: f64,
) -> Result<SharedImageSurface, cairo::Error> {
let output_surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
if let Some(output_bounds) = bounds.translate((dx, dy)).intersection(&bounds) {
let cr = cairo::Context::new(&output_surface)?;
let r = cairo::Rectangle::from(output_bounds);
cr.rectangle(r.x(), r.y(), r.width(), r.height());
cr.clip();
self.set_as_source_surface(&cr, dx, dy)?;
cr.paint()?;
}
SharedImageSurface::wrap(output_surface, self.surface_type)
}
#[inline]
pub fn paint_image(
&self,
bounds: Rect,
image: &SharedImageSurface,
rect: Option<Rect>,
interpolation: Interpolation,
) -> Result<SharedImageSurface, cairo::Error> {
let output_surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
if rect.is_none() || !rect.unwrap().is_empty() {
let cr = cairo::Context::new(&output_surface)?;
let r = cairo::Rectangle::from(bounds);
cr.rectangle(r.x(), r.y(), r.width(), r.height());
cr.clip();
image.set_as_source_surface(&cr, 0f64, 0f64)?;
if let Some(rect) = rect {
let mut matrix = cairo::Matrix::new(
rect.width() / f64::from(image.width()),
0.0,
0.0,
rect.height() / f64::from(image.height()),
rect.x0,
rect.y0,
);
matrix.invert();
cr.source().set_matrix(matrix);
cr.source().set_filter(cairo::Filter::from(interpolation));
}
cr.paint()?;
}
SharedImageSurface::wrap(output_surface, image.surface_type)
}
#[inline]
pub fn tile(&self, bounds: IRect) -> Result<SharedImageSurface, cairo::Error> {
assert!(!bounds.is_empty());
let output_surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, bounds.width(), bounds.height())?;
{
let cr = cairo::Context::new(&output_surface)?;
self.set_as_source_surface(&cr, f64::from(-bounds.x0), f64::from(-bounds.y0))?;
cr.paint()?;
}
SharedImageSurface::wrap(output_surface, self.surface_type)
}
#[inline]
pub fn paint_image_tiled(
&self,
bounds: IRect,
image: &SharedImageSurface,
x: i32,
y: i32,
) -> Result<SharedImageSurface, cairo::Error> {
let output_surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
{
let cr = cairo::Context::new(&output_surface)?;
let ptn = image.to_cairo_pattern();
ptn.set_extend(cairo::Extend::Repeat);
let mut mat = cairo::Matrix::identity();
mat.translate(f64::from(-x), f64::from(-y));
ptn.set_matrix(mat);
let r = cairo::Rectangle::from(bounds);
cr.rectangle(r.x(), r.y(), r.width(), r.height());
cr.clip();
cr.set_source(&ptn)?;
cr.paint()?;
}
SharedImageSurface::wrap(output_surface, image.surface_type)
}
#[inline]
pub fn compose(
&self,
other: &SharedImageSurface,
bounds: IRect,
operator: Operator,
) -> Result<SharedImageSurface, cairo::Error> {
let output_surface = other.copy_surface(bounds)?;
{
let cr = cairo::Context::new(&output_surface)?;
let r = cairo::Rectangle::from(bounds);
cr.rectangle(r.x(), r.y(), r.width(), r.height());
cr.clip();
self.set_as_source_surface(&cr, 0.0, 0.0)?;
cr.set_operator(operator.into());
cr.paint()?;
}
SharedImageSurface::wrap(
output_surface,
self.surface_type.combine(other.surface_type),
)
}
#[inline]
pub fn compose_arithmetic(
&self,
other: &SharedImageSurface,
bounds: IRect,
k1: f64,
k2: f64,
k3: f64,
k4: f64,
) -> Result<SharedImageSurface, cairo::Error> {
let mut output_surface = ExclusiveImageSurface::new(
self.width,
self.height,
self.surface_type.combine(other.surface_type),
)?;
composite_arithmetic(self, other, &mut output_surface, bounds, k1, k2, k3, k4);
output_surface.share()
}
pub fn rows(&self) -> Rows<'_> {
Rows {
surface: self,
next_row: 0,
}
}
}
impl<'a> Iterator for Rows<'a> {
type Item = &'a [CairoARGB];
fn next(&mut self) -> Option<Self::Item> {
if self.next_row == self.surface.height {
return None;
}
let row = self.next_row;
self.next_row += 1;
unsafe {
let row_ptr: *const u8 = self
.surface
.data_ptr
.as_ptr()
.offset(row as isize * self.surface.stride);
let row_of_u32: &[u32] =
slice::from_raw_parts(row_ptr as *const u32, self.surface.width as usize);
let pixels = row_of_u32.as_cairo_argb();
assert!(pixels.len() == self.surface.width as usize);
Some(pixels)
}
}
}
impl<'a> Iterator for RowsMut<'a> {
type Item = &'a mut [CairoARGB];
fn next(&mut self) -> Option<Self::Item> {
if self.next_row == self.height {
return None;
}
let row = self.next_row as usize;
self.next_row += 1;
unsafe {
let data_ptr = self.data.as_mut_ptr();
let row_ptr: *mut u8 = data_ptr.offset(row as isize * self.stride as isize);
let row_of_u32: &mut [u32] =
slice::from_raw_parts_mut(row_ptr as *mut u32, self.width as usize);
let pixels = row_of_u32.as_cairo_argb_mut();
assert!(pixels.len() == self.width as usize);
Some(pixels)
}
}
}
#[inline]
pub fn composite_arithmetic(
surface1: &SharedImageSurface,
surface2: &SharedImageSurface,
output_surface: &mut ExclusiveImageSurface,
bounds: IRect,
k1: f64,
k2: f64,
k3: f64,
k4: f64,
) {
output_surface.modify(&mut |data, stride| {
for (x, y, pixel, pixel_2) in
Pixels::within(surface1, bounds).map(|(x, y, p)| (x, y, p, surface2.get_pixel(x, y)))
{
let i1a = f64::from(pixel.a) / 255f64;
let i2a = f64::from(pixel_2.a) / 255f64;
let oa = k1 * i1a * i2a + k2 * i1a + k3 * i2a + k4;
let oa = clamp(oa, 0f64, 1f64);
if oa > 0f64 {
let compute = |i1, i2| {
let i1 = f64::from(i1) / 255f64;
let i2 = f64::from(i2) / 255f64;
let o = k1 * i1 * i2 + k2 * i1 + k3 * i2 + k4;
let o = clamp(o, 0f64, oa);
((o * 255f64) + 0.5) as u8
};
let output_pixel = Pixel {
r: compute(pixel.r, pixel_2.r),
g: compute(pixel.g, pixel_2.g),
b: compute(pixel.b, pixel_2.b),
a: ((oa * 255f64) + 0.5) as u8,
};
data.set_pixel(stride, output_pixel, x, y);
}
}
});
}
impl ImageSurface<Exclusive> {
#[inline]
pub fn new(
width: i32,
height: i32,
surface_type: SurfaceType,
) -> Result<ExclusiveImageSurface, cairo::Error> {
let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, width, height)?;
let (width, height) = (surface.width(), surface.height());
if !(width > 0 && height > 0) {
return Err(cairo::Error::InvalidSize);
}
let data_ptr = NonNull::new(unsafe {
cairo::ffi::cairo_image_surface_get_data(surface.to_raw_none())
})
.unwrap();
let stride = surface.stride() as isize;
Ok(ExclusiveImageSurface {
surface,
data_ptr,
width,
height,
stride,
surface_type,
_state: PhantomData,
})
}
#[inline]
pub fn share(self) -> Result<SharedImageSurface, cairo::Error> {
SharedImageSurface::wrap(self.surface, self.surface_type)
}
#[inline]
pub fn data(&mut self) -> cairo::ImageSurfaceData<'_> {
self.surface.data().unwrap()
}
#[inline]
pub fn modify(&mut self, draw_fn: &mut dyn FnMut(&mut cairo::ImageSurfaceData<'_>, usize)) {
let stride = self.stride() as usize;
let mut data = self.data();
draw_fn(&mut data, stride)
}
#[inline]
pub fn draw(
&mut self,
draw_fn: &mut dyn FnMut(cairo::Context) -> Result<(), Box<InternalRenderingError>>,
) -> Result<(), Box<InternalRenderingError>> {
let cr = cairo::Context::new(&self.surface)?;
draw_fn(cr)
}
pub fn rows_mut(&mut self) -> RowsMut<'_> {
let width = self.surface.width();
let height = self.surface.height();
let stride = self.surface.stride();
let data = self.surface.data().unwrap();
RowsMut {
width,
height,
stride,
data,
next_row: 0,
}
}
}
impl From<Operator> for cairo::Operator {
fn from(op: Operator) -> cairo::Operator {
use Operator::*;
use cairo::Operator as Cairo;
match op {
Over => Cairo::Over,
In => Cairo::In,
Out => Cairo::Out,
Atop => Cairo::Atop,
Xor => Cairo::Xor,
Multiply => Cairo::Multiply,
Screen => Cairo::Screen,
Darken => Cairo::Darken,
Lighten => Cairo::Lighten,
Overlay => Cairo::Overlay,
ColorDodge => Cairo::ColorDodge,
ColorBurn => Cairo::ColorBurn,
HardLight => Cairo::HardLight,
SoftLight => Cairo::SoftLight,
Difference => Cairo::Difference,
Exclusion => Cairo::Exclusion,
HslHue => Cairo::HslHue,
HslSaturation => Cairo::HslSaturation,
HslColor => Cairo::HslColor,
HslLuminosity => Cairo::HslLuminosity,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::surface_utils::iterators::Pixels;
#[test]
fn test_extract_alpha() {
const WIDTH: i32 = 32;
const HEIGHT: i32 = 64;
let bounds = IRect::new(8, 24, 16, 48);
let full_bounds = IRect::from_size(WIDTH, HEIGHT);
let mut surface = ExclusiveImageSurface::new(WIDTH, HEIGHT, SurfaceType::SRgb).unwrap();
{
let mut data = surface.data();
let mut counter = 0u16;
for x in data.iter_mut() {
*x = counter as u8;
counter = (counter + 1) % 256;
}
}
let surface = surface.share().unwrap();
let alpha = surface.extract_alpha(bounds).unwrap();
for (x, y, p, pa) in
Pixels::within(&surface, full_bounds).map(|(x, y, p)| (x, y, p, alpha.get_pixel(x, y)))
{
assert_eq!(pa.r, 0);
assert_eq!(pa.g, 0);
assert_eq!(pa.b, 0);
if !bounds.contains(x as i32, y as i32) {
assert_eq!(pa.a, 0);
} else {
assert_eq!(pa.a, p.a);
}
}
}
}