Commit a9841ea9 authored by RazrFalcon's avatar RazrFalcon

Fixed object bounding box calculation. x3

Fixed nested objectBoundigBox support.

Refactoring.

Closes #46
parent 9da06f68
......@@ -17,6 +17,7 @@ This changelog also contains important changes in dependencies.
### Fixed
- Object bounding box calculation.
- Pattern scaling.
- Nested `objectBoundigBox` support.
- (usvg) `color` on `use` resolving.
- (usvg) `offset` attribute resolving inside the `stop` element.
- (usvg) Ungrouping of groups with non-inheritable attributes.
......
......@@ -34,7 +34,8 @@ pub fn apply(
clip_cr.transform(cp.transform.to_native());
if cp.units == usvg::Units::ObjectBoundingBox {
let m = cairo::Matrix::from_bbox(bbox);
let m = try_opt_warn!(cairo::Matrix::from_bbox(bbox), (),
"ClipPath '{}' cannot be used on a zero-sized object.", cp.id);
clip_cr.transform(m);
}
......
......@@ -47,7 +47,7 @@ impl ImageExt for cairo::ImageSurface {
self.get_height() as u32
}
fn clone_image(&self) -> Result<Self, Error> {
fn try_clone(&self) -> Result<Self, Error> {
let new_image = create_image(self.width(), self.height())?;
let cr = cairo::Context::new(&new_image);
......@@ -57,7 +57,7 @@ impl ImageExt for cairo::ImageSurface {
Ok(new_image)
}
fn clip_image(&mut self, region: ScreenRect) {
fn clip(&mut self, region: ScreenRect) {
let cr = cairo::Context::new(self);
cr.set_source_rgba(0.0, 0.0, 0.0, 0.0);
cr.set_operator(cairo::Operator::Clear);
......@@ -70,6 +70,13 @@ impl ImageExt for cairo::ImageSurface {
cr.fill();
}
fn clear(&mut self) {
let cr = cairo::Context::new(self);
cr.set_operator(cairo::Operator::Clear);
cr.set_source_rgba(0.0, 0.0, 0.0, 0.0);
cr.paint();
}
fn into_srgb(&mut self) {
if let Ok(ref mut data) = self.get_data() {
from_premultiplied(data);
......@@ -223,23 +230,13 @@ impl Filter<cairo::ImageSurface> for CairoFilter {
}
fn apply_offset(
filter: &usvg::Filter,
fe: &usvg::FeOffset,
units: usvg::Units,
bbox: Rect,
ts: &usvg::Transform,
input: Image,
) -> Result<Image, Error> {
let (sx, sy) = ts.get_scale();
let (dx, dy) = if filter.primitive_units == usvg::Units::ObjectBoundingBox {
(fe.dx * sx * bbox.width, fe.dy * sy * bbox.height)
} else {
(fe.dx * sx, fe.dy * sy)
};
if dx.is_fuzzy_zero() && dy.is_fuzzy_zero() {
return Ok(input);
}
let (dx, dy) = try_opt!(Self::resolve_offset(fe, units, bbox, ts), Ok(input));
// TODO: do not use an additional buffer
let mut buffer = create_image(input.width(), input.height())?;
......
......@@ -19,7 +19,7 @@ pub fn prepare_linear(
cr: &cairo::Context,
) {
let grad = cairo::LinearGradient::new(g.x1, g.y1, g.x2, g.y2);
prepare_base(&g.base, &grad, opacity, bbox);
prepare_base(&g.base, &grad, opacity, bbox, &g.id);
cr.set_source(&cairo::Pattern::LinearGradient(grad));
}
......@@ -30,7 +30,7 @@ pub fn prepare_radial(
cr: &cairo::Context
) {
let grad = cairo::RadialGradient::new(g.fx, g.fy, 0.0, g.cx, g.cy, *g.r);
prepare_base(&g.base, &grad, opacity, bbox);
prepare_base(&g.base, &grad, opacity, bbox, &g.id);
cr.set_source(&cairo::Pattern::RadialGradient(grad));
}
......@@ -39,6 +39,7 @@ fn prepare_base<G>(
grad: &G,
opacity: usvg::Opacity,
bbox: Rect,
id: &str,
) where G: cairo::Gradient {
let spread_method = match g.spread_method {
usvg::SpreadMethod::Pad => cairo::Extend::Pad,
......@@ -50,7 +51,8 @@ fn prepare_base<G>(
let mut matrix = g.transform.to_native();
if g.units == usvg::Units::ObjectBoundingBox {
let m = cairo::Matrix::from_bbox(bbox);
let m = try_opt_warn!(cairo::Matrix::from_bbox(bbox), (),
"Gradient '{}' cannot be used on a zero-sized object.", id);
matrix = cairo::Matrix::multiply(&matrix, &m);
}
......
......@@ -29,7 +29,8 @@ pub fn apply(
mask_cr.set_matrix(sub_cr.get_matrix());
let r = if mask.units == usvg::Units::ObjectBoundingBox {
mask.rect.bbox_transform(bbox)
try_opt_warn!(mask.rect.bbox_transform(bbox), (),
"Mask '{}' cannot be used on a zero-sized object.", mask.id)
} else {
mask.rect
};
......@@ -38,7 +39,9 @@ pub fn apply(
mask_cr.clip();
if mask.content_units == usvg::Units::ObjectBoundingBox {
mask_cr.transform(cairo::Matrix::from_bbox(bbox));
let m = try_opt_warn!(cairo::Matrix::from_bbox(bbox), (),
"Mask '{}' cannot be used on a zero-sized object.", mask.id);
mask_cr.transform(m);
}
super::render_group(node, opt, layers, &mask_cr);
......@@ -46,7 +49,7 @@ pub fn apply(
{
let mut data = try_opt_warn!(mask_surface.get_data().ok(), (),
"Failed to borrow a surface for mask: {:?}.", mask.id);
"Failed to borrow a surface for mask '{}'.", mask.id);
mask::image_to_mask(&mut data, layers.image_size());
}
......
......@@ -68,11 +68,12 @@ impl ConvTransform<cairo::Matrix> for usvg::Transform {
}
impl TransformFromBBox for cairo::Matrix {
fn from_bbox(bbox: Rect) -> Self {
debug_assert!(!bbox.width.is_fuzzy_zero());
debug_assert!(!bbox.height.is_fuzzy_zero());
Self::new(bbox.width, 0.0, 0.0, bbox.height, bbox.x, bbox.y)
fn from_bbox(bbox: Rect) -> Option<Self> {
if bbox.is_valid() {
Some(Self::new(bbox.width, 0.0, 0.0, bbox.height, bbox.x, bbox.y))
} else {
None
}
}
}
......
......@@ -22,7 +22,8 @@ pub fn apply(
cr: &cairo::Context,
) {
let r = if pattern.units == usvg::Units::ObjectBoundingBox {
pattern.rect.bbox_transform(bbox)
try_opt_warn!(pattern.rect.bbox_transform(bbox), (),
"Pattern '{}' cannot be used on a zero-sized object.", pattern.id)
} else {
pattern.rect
};
......@@ -48,7 +49,11 @@ pub fn apply(
// We don't use Transform::from_bbox(bbox) because `x` and `y` should be
// ignored for some reasons...
sub_cr.scale(bbox.width, bbox.height);
if bbox.is_valid() {
sub_cr.scale(bbox.width, bbox.height);
} else {
return;
}
}
let mut layers = super::create_layers(img_size, opt);
......
......@@ -30,7 +30,9 @@ pub fn apply(
clip_p.apply_transform(&cp.transform.to_native());
if cp.units == usvg::Units::ObjectBoundingBox {
clip_p.apply_transform(&qt::Transform::from_bbox(bbox));
let ts = try_opt_warn!(qt::Transform::from_bbox(bbox), (),
"ClipPath '{}' cannot be used on a zero-sized object.", cp.id);
clip_p.apply_transform(&ts);
}
clip_p.set_composition_mode(qt::CompositionMode::Clear);
......
......@@ -43,11 +43,11 @@ impl ImageExt for qt::Image {
self.height()
}
fn clone_image(&self) -> Result<Self, Error> {
fn try_clone(&self) -> Result<Self, Error> {
self.try_clone().ok_or(Error::AllocFailed)
}
fn clip_image(&mut self, region: ScreenRect) {
fn clip(&mut self, region: ScreenRect) {
let mut brush = qt::Brush::new();
brush.set_color(0, 0, 0, 0);
......@@ -61,6 +61,10 @@ impl ImageExt for qt::Image {
p.draw_rect(0.0, region.bottom() as f64, self.width() as f64, self.height() as f64);
}
fn clear(&mut self) {
self.fill(0, 0, 0, 0);
}
fn into_srgb(&mut self) {
for p in self.data_mut().as_rgba_mut() {
p.r = filter::LINEAR_RGB_TO_SRGB_TABLE[p.r as usize];
......@@ -164,23 +168,13 @@ impl Filter<qt::Image> for QtFilter {
}
fn apply_offset(
filter: &usvg::Filter,
fe: &usvg::FeOffset,
units: usvg::Units,
bbox: Rect,
ts: &usvg::Transform,
input: Image,
) -> Result<Image, Error> {
let (sx, sy) = ts.get_scale();
let (dx, dy) = if filter.primitive_units == usvg::Units::ObjectBoundingBox {
(fe.dx * sx * bbox.width, fe.dy * sy * bbox.height)
} else {
(fe.dx * sx, fe.dy * sy)
};
if dx.is_fuzzy_zero() && dy.is_fuzzy_zero() {
return Ok(input);
}
let (dx, dy) = try_opt!(Self::resolve_offset(fe, units, bbox, ts), Ok(input));
// TODO: do not use an additional buffer
let mut buffer = create_image(input.width(), input.height())?;
......
......@@ -19,7 +19,7 @@ pub fn prepare_linear(
prepare_base(&g.base, opacity, &mut grad);
brush.set_linear_gradient(grad);
apply_ts(&g.base, bbox, brush);
apply_ts(&g.base, bbox, &g.id, brush);
}
pub fn prepare_radial(
......@@ -32,7 +32,7 @@ pub fn prepare_radial(
prepare_base(&g.base, opacity, &mut grad);
brush.set_radial_gradient(grad);
apply_ts(&g.base, bbox, brush);
apply_ts(&g.base, bbox, &g.id, brush);
}
fn prepare_base(
......@@ -61,6 +61,7 @@ fn prepare_base(
fn apply_ts(
g: &usvg::BaseGradient,
bbox: Rect,
id: &str,
brush: &mut qt::Brush,
) {
// We don't use `QGradient::setCoordinateMode` because it works incorrectly.
......@@ -68,7 +69,9 @@ fn apply_ts(
// See QTBUG-67995
if g.units == usvg::Units::ObjectBoundingBox {
let mut ts = usvg::Transform::from_bbox(bbox);
let mut ts = try_opt_warn!(usvg::Transform::from_bbox(bbox), (),
"Gradient '{}' cannot be used on a zero-sized object.", id);
ts.append(&g.transform);
brush.set_transform(ts.to_native());
} else {
......
......@@ -26,7 +26,8 @@ pub fn apply(
mask_p.set_transform(&sub_p.get_transform());
let r = if mask.units == usvg::Units::ObjectBoundingBox {
mask.rect.bbox_transform(bbox)
try_opt_warn!(mask.rect.bbox_transform(bbox), (),
"Mask '{}' cannot be used on a zero-sized object.", mask.id)
} else {
mask.rect
};
......@@ -34,7 +35,9 @@ pub fn apply(
mask_p.set_clip_rect(r.x, r.y, r.width, r.height);
if mask.content_units == usvg::Units::ObjectBoundingBox {
mask_p.apply_transform(&qt::Transform::from_bbox(bbox));
let ts = try_opt_warn!(qt::Transform::from_bbox(bbox), (),
"Mask '{}' cannot be used on a zero-sized object.", mask.id);
mask_p.apply_transform(&ts);
}
super::render_group(node, opt, layers, &mut mask_p);
......
......@@ -60,11 +60,12 @@ impl ConvTransform<qt::Transform> for usvg::Transform {
}
impl TransformFromBBox for qt::Transform {
fn from_bbox(bbox: Rect) -> Self {
debug_assert!(!bbox.width.is_fuzzy_zero());
debug_assert!(!bbox.height.is_fuzzy_zero());
Self::new(bbox.width, 0.0, 0.0, bbox.height, bbox.x, bbox.y)
fn from_bbox(bbox: Rect) -> Option<Self> {
if bbox.is_valid() {
Some(Self::new(bbox.width, 0.0, 0.0, bbox.height, bbox.x, bbox.y))
} else {
None
}
}
}
......
......@@ -18,7 +18,8 @@ pub fn apply(
brush: &mut qt::Brush,
) {
let r = if pattern.units == usvg::Units::ObjectBoundingBox {
pattern.rect.bbox_transform(bbox)
try_opt_warn!(pattern.rect.bbox_transform(bbox), (),
"Pattern '{}' cannot be used on a zero-sized object.", pattern.id)
} else {
pattern.rect
};
......@@ -47,7 +48,11 @@ pub fn apply(
// We don't use Transform::from_bbox(bbox) because `x` and `y` should be
// ignored for some reasons...
p.scale(bbox.width, bbox.height);
if bbox.is_valid() {
p.scale(bbox.width, bbox.height);
} else {
return;
}
}
let mut layers = super::create_layers(img_size, opt);
......
......@@ -24,8 +24,9 @@ pub trait ImageExt
fn width(&self) -> u32;
fn height(&self) -> u32;
fn clone_image(&self) -> Result<Self, Error>;
fn clip_image(&mut self, region: ScreenRect);
fn try_clone(&self) -> Result<Self, Error>;
fn clip(&mut self, region: ScreenRect);
fn clear(&mut self);
fn into_srgb(&mut self);
fn into_linear_rgb(&mut self);
......@@ -85,7 +86,7 @@ impl<T: ImageExt> Image<T> {
pub fn take(self) -> Result<T, Error> {
match Rc::try_unwrap(self.image) {
Ok(v) => Ok(v),
Err(v) => v.clone_image(),
Err(v) => v.try_clone(),
}
}
......@@ -128,12 +129,20 @@ pub trait Filter<T: ImageExt> {
opt: &Options,
canvas: &mut T,
) {
match Self::_apply(filter, bbox, ts, opt, canvas) {
let res = Self::_apply(filter, bbox, ts, opt, canvas);
// Clear on error.
if res.is_err() {
canvas.clear();
}
match res {
Ok(_) => {}
Err(Error::AllocFailed) =>
warn!("Memory allocation failed while processing the '{}' filter. Skipped.", filter.id),
Err(Error::InvalidRegion) =>
warn!("Filter '{}' has an invalid region.", filter.id),
Err(Error::InvalidRegion) => {
warn!("Filter '{}' has an invalid region.", filter.id);
}
Err(Error::ZeroSizedObject) =>
warn!("Filter '{}' cannot be used on a zero-sized object.", filter.id),
}
......@@ -170,7 +179,7 @@ pub trait Filter<T: ImageExt> {
}
usvg::FilterKind::FeOffset(ref fe) => {
let input = Self::get_input(&fe.input, region, &results, canvas)?;
Self::apply_offset(filter, fe, bbox, ts, input)
Self::apply_offset(fe, filter.primitive_units, bbox, ts, input)
}
usvg::FilterKind::FeComposite(ref fe) => {
let input1 = Self::get_input(&fe.input1, region, &results, canvas)?;
......@@ -208,7 +217,7 @@ pub trait Filter<T: ImageExt> {
let color_space = result.color_space;
let mut buffer = result.take()?;
buffer.clip_image(subregion2);
buffer.clip(subregion2);
result = Image {
image: Rc::new(buffer),
......@@ -247,8 +256,8 @@ pub trait Filter<T: ImageExt> {
) -> Result<Image<T>, Error>;
fn apply_offset(
filter: &usvg::Filter,
fe: &usvg::FeOffset,
units: usvg::Units,
bbox: Rect,
ts: &usvg::Transform,
input: Image<T>,
......@@ -315,11 +324,38 @@ pub trait Filter<T: ImageExt> {
let (sx, sy) = ts.get_scale();
Some(if units == usvg::Units::ObjectBoundingBox {
let (std_dx, std_dy) = if units == usvg::Units::ObjectBoundingBox {
(*fe.std_dev_x * sx * bbox.width, *fe.std_dev_y * sy * bbox.height)
} else {
(*fe.std_dev_x * sx, *fe.std_dev_y * sy)
})
};
if std_dx.is_fuzzy_zero() && std_dy.is_fuzzy_zero() {
None
} else {
Some((std_dx, std_dy))
}
}
fn resolve_offset(
fe: &usvg::FeOffset,
units: usvg::Units,
bbox: Rect,
ts: &usvg::Transform,
) -> Option<(f64, f64)> {
let (sx, sy) = ts.get_scale();
let (dx, dy) = if units == usvg::Units::ObjectBoundingBox {
(fe.dx * sx * bbox.width, fe.dy * sy * bbox.height)
} else {
(fe.dx * sx, fe.dy * sy)
};
if dx.is_fuzzy_zero() && dy.is_fuzzy_zero() {
None
} else {
Some((dx, dy))
}
}
}
......@@ -551,14 +587,13 @@ fn calc_region(
let path = utils::rect_to_path(filter.rect);
let region_ts = if filter.units == usvg::Units::ObjectBoundingBox {
// Not 0.0, because bbox min size is 1x1.
// See utils::path_bbox().
if bbox.width.fuzzy_eq(&1.0) || bbox.height.fuzzy_eq(&1.0) {
return Err(Error::ZeroSizedObject);
}
let bbox_ts = match usvg::Transform::from_bbox(bbox) {
Some(v) => v,
None => return Err(Error::ZeroSizedObject),
};
let mut ts2 = ts.clone();
ts2.append(&usvg::Transform::from_bbox(bbox));
ts2.append(&bbox_ts);
ts2
} else {
*ts
......@@ -613,7 +648,9 @@ fn calc_subregion<T: ImageExt>(
primitive.height.unwrap_or(1.0),
);
return r.bbox_transform(bbox).bbox_transform(ts_bbox).to_screen_rect();
return r.bbox_transform(bbox).unwrap()
.bbox_transform(ts_bbox).unwrap()
.to_screen_rect();
} else {
filter_region
}
......@@ -630,7 +667,7 @@ fn calc_subregion<T: ImageExt>(
primitive.height.unwrap_or(1.0),
);
region.to_rect().bbox_transform(subregion_bbox)
region.to_rect().bbox_transform(subregion_bbox).unwrap_or(filter_region.to_rect())
} else {
let (dx, dy) = ts.get_translate();
let (sx, sy) = ts.get_scale();
......
......@@ -101,7 +101,7 @@ fn size_scale(s1: ScreenSize, s2: ScreenSize, expand: bool) -> ScreenSize {
/// Additional `Rect` methods.
pub trait RectExt {
pub trait RectExt: Sized {
/// Creates a new `Rect` for bounding box calculation.
///
/// Shorthand for `Rect::new(f64::MAX, f64::MAX, 1.0, 1.0)`.
......@@ -111,7 +111,7 @@ pub trait RectExt {
fn expand(&mut self, r: Rect);
/// Transforms the `Rect` using the provided `bbox`.
fn bbox_transform(&self, bbox: Rect) -> Self;
fn bbox_transform(&self, bbox: Rect) -> Option<Self>;
/// Transforms the `Rect` using the provided `Transform`.
///
......@@ -151,13 +151,17 @@ impl RectExt for Rect {
}
}
fn bbox_transform(&self, bbox: Rect) -> Self {
let x = self.x * bbox.width + bbox.x;
let y = self.y * bbox.height + bbox.y;
let w = self.width * bbox.width;
let h = self.height * bbox.height;
fn bbox_transform(&self, bbox: Rect) -> Option<Self> {
if bbox.is_valid() {
let x = self.x * bbox.width + bbox.x;
let y = self.y * bbox.height + bbox.y;
let w = self.width * bbox.width;
let h = self.height * bbox.height;
Self::new(x, y, w, h)
Some(Self::new(x, y, w, h))
} else {
None
}
}
fn transform(&self, ts: &usvg::Transform) -> Self {
......
......@@ -15,12 +15,16 @@ pub(crate) trait ConvTransform<T> {
}
pub(crate) trait TransformFromBBox {
fn from_bbox(bbox: Rect) -> Self;
pub(crate) trait TransformFromBBox: Sized {
fn from_bbox(bbox: Rect) -> Option<Self>;
}
impl TransformFromBBox for usvg::Transform {
fn from_bbox(bbox: Rect) -> Self {
Self::new(bbox.width, 0.0, 0.0, bbox.height, bbox.x, bbox.y)
fn from_bbox(bbox: Rect) -> Option<Self> {
if bbox.is_valid() {
Some(Self::new(bbox.width, 0.0, 0.0, bbox.height, bbox.x, bbox.y))
} else {
None
}
}
}
......@@ -69,7 +69,7 @@ pub fn abs_transform(
/// Calculates path's bounding box.
///
/// Minimum size is 1x1.
/// Width and/or height can be zero.
pub fn path_bbox(
segments: &[usvg::PathSegment],
stroke: Option<&usvg::Stroke>,
......@@ -150,11 +150,8 @@ pub fn path_bbox(
maxy += w;
}
let mut width = maxx - minx;
if width < 1.0 { width = 1.0; }
let mut height = maxy - miny;
if height < 1.0 { height = 1.0; }
let width = maxx - minx;
let height = maxy - miny;
(minx as f64, miny as f64, width as f64, height as f64).into()
}
......
a-opacity-007.svg
e-clipPath-041.svg
e-clipPath-042.svg
e-filter-060.svg
e-filter-061.svg
e-filter-062.svg
e-mask-016.svg
e-pattern-024.svg
a-opacity-007.svg
e-clipPath-041.svg
e-clipPath-042.svg
e-filter-060.svg
e-filter-061.svg
e-filter-062.svg
e-mask-016.svg
e-pattern-024.svg
......@@ -949,3 +949,5 @@ a-opacity-007,3e792210
e-clipPath-041,108b66ef
e-clipPath-042,108b66ef
e-filter-060,7fbfbc22
e-filter-061,d6667c63
e-filter-062,32cc3451
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