Add support for source clipping to the Image element

This allows rendering only a sub-rectangle of the original image, which
we can use right away in the sliding puzzle demo.
This commit is contained in:
Simon Hausmann 2020-11-20 17:29:59 +01:00
parent 7583394751
commit 3d85e45ec3
13 changed files with 219 additions and 25 deletions

View file

@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
- VerticalLayout / HorizontalLayout
- Placeholder text in line edit
- global components (#96)
- `Image` element: New source-clip-{x, y, width, height} properties
## [0.0.2] - 2020-12-22

View file

@ -37,6 +37,7 @@ extern const cbindgen_private::ItemVTable BorderRectangleVTable;
extern const cbindgen_private::ItemVTable TextVTable;
extern const cbindgen_private::ItemVTable TouchAreaVTable;
extern const cbindgen_private::ItemVTable ImageVTable;
extern const cbindgen_private::ItemVTable ClippedImageVTable;
extern const cbindgen_private::ItemVTable PathVTable;
extern const cbindgen_private::ItemVTable FlickableVTable;
extern const cbindgen_private::ItemVTable WindowVTable;
@ -126,6 +127,7 @@ private:
using cbindgen_private::BorderRectangle;
using cbindgen_private::Clip;
using cbindgen_private::ClippedImage;
using cbindgen_private::Flickable;
using cbindgen_private::Image;
using cbindgen_private::Path;

View file

@ -88,6 +88,8 @@ An Image can be used to represent an image loaded from an image file
* **`source`** (*image*): The image to load. In order to reference image, one uses the `img!"..."` macro
which loads the file relative to the directory containing the .60 file.
* **`source-clip-x`**, **`source-clip-y`**, **`source-clip-width`**, **`source-clip-height`** (*int*): properties in source
image coordinates that, when specified, can be used to render only a portion of the specified image.
### Example

View file

@ -5,7 +5,6 @@ https://flutter.github.io/samples/slide_puzzle
This will allow to compare SixtyFPS and Flutter.
Remaining feature to implement to have parity:
* Images on the tiles in the "Berlin" theme.
* Fonts.
* "Spring" animation instead of a bezier curve.
* Animation when clicking on a tile that cannot be moved.

View file

@ -21,6 +21,7 @@ struct Theme := {
name: string,
window-background-color: color,
game-background-color: color,
game-use-background-image: bool,
game-border: length,
game-radius: length,
game-text-color: color,
@ -133,6 +134,7 @@ export MainWindow := Window {
name: "SIMPLE",
window-background-color: #ffffff,
game-background-color: #ffffff,
game-use-background-image: false,
game-border: 1px,
game-radius: 2px,
game-text-color: #858585,
@ -152,6 +154,7 @@ export MainWindow := Window {
name: "BERLIN",
window-background-color: #ffffff88,
game-background-color: #ffffffcc,
game-use-background-image: true,
game-border: 0px,
game-radius: 2px,
game-text-color: #858585,
@ -171,6 +174,7 @@ export MainWindow := Window {
name: "PLASTER",
window-background-color: #424244,
game-background-color: #f8f4e9,
game-use-background-image: false,
game-border: 5px,
game-radius: 20px,
game-text-color: #858585,
@ -279,7 +283,17 @@ export MainWindow := Window {
+ (parent.height - (4*pieces_size + 3*pieces_spacing))/2;
animate px , py { duration: 170ms; easing: cubic-bezier(0.17,0.76,0.4,1.75); }
Rectangle {
if (current-theme.game-use-background-image) : Image {
height: 100%; width: 100%;
// https://commons.wikimedia.org/wiki/File:Berlin_potsdamer_platz.jpg Belappetit, CC BY-SA 3.0
source: img!"berlin.jpg";
source-clip-x: mod(i, 4) * 1024 / 4;
source-clip-y: floor(i / 4) * 683 / 4;
source-clip-width: 1024 / 4;
source-clip-height: 683 / 4;
}
if (!current-theme.game-use-background-image) : Rectangle {
width: 100%;
height: 100%;
color: i >= 8 ? current-theme.piece-background-2 : current-theme.piece-background-1;

View file

@ -34,7 +34,7 @@ BorderRectangle := Rectangle {
export { BorderRectangle as Rectangle }
export Image := _ {
Image := _ {
property <resource> source;
property <length> x;
property <length> y;
@ -42,6 +42,15 @@ export Image := _ {
property <length> height;
}
export ClippedImage := Image {
property <int> source-clip-x;
property <int> source-clip-y;
property <int> source-clip-width;
property <int> source-clip-height;
}
export { ClippedImage as Image }
export Text := _ {
property <string> text;
property <string> font_family;

View file

@ -43,6 +43,8 @@ use std::rc::Rc;
/// 2D Rectangle
pub type Rect = euclid::default::Rect<f32>;
/// 2D Rectangle with integer coordinates
pub type IntRect = euclid::default::Rect<i32>;
/// 2D Point
pub type Point = euclid::default::Point2D<f32>;
/// 2D Size
@ -276,7 +278,7 @@ pub enum HighLevelRenderingPrimitive {
/// Optional rendering variables:
/// * [`RenderingVariable::ScaledWidth`]: The image will be scaled to the specified width.
/// * [`RenderingVariable::ScaledHeight`]: The image will be scaled to the specified height.
Image { source: crate::Resource },
Image { source: crate::Resource, source_clip_rect: IntRect },
/// Renders the specified `text` with a font that matches the specified family (`font_family`) and the given
/// pixel size (`font_size`).
///
@ -1213,6 +1215,16 @@ pub(crate) mod ffi {
height: f32,
}
/// Expand IntRect so that cbindgen can see it. ( is in fact euclid::default::Rect<i32>)
#[cfg(cbindgen)]
#[repr(C)]
struct IntRect {
x: i32,
y: i32,
width: i32,
height: i32,
}
/// Expand Point so that cbindgen can see it. ( is in fact euclid::default::PointD2<f32>)
#[cfg(cbindgen)]
#[repr(C)]

View file

@ -26,7 +26,7 @@ When adding an item or a property, it needs to be kept in sync with different pl
use super::component::ComponentVTable;
use super::eventloop::ComponentWindow;
use super::graphics::{Color, HighLevelRenderingPrimitive, PathData, Rect, Resource};
use super::graphics::{Color, HighLevelRenderingPrimitive, IntRect, PathData, Rect, Resource};
use super::input::{
FocusEvent, InputEventResult, KeyEvent, KeyEventResult, KeyboardModifiers, MouseEvent,
MouseEventType,
@ -305,6 +305,7 @@ impl Item for Image {
) -> HighLevelRenderingPrimitive {
HighLevelRenderingPrimitive::Image {
source: Self::FIELD_OFFSETS.source.apply_pin(self).get(),
source_clip_rect: IntRect::default(),
}
}
@ -362,6 +363,103 @@ ItemVTable_static! {
pub static ImageVTable for Image
}
#[repr(C)]
#[derive(FieldOffsets, Default, BuiltinItem)]
#[pin]
/// The implementation of the `ClippedImage` element
pub struct ClippedImage {
pub source: Property<Resource>,
pub x: Property<f32>,
pub y: Property<f32>,
pub width: Property<f32>,
pub height: Property<f32>,
pub source_clip_x: Property<i32>,
pub source_clip_y: Property<i32>,
pub source_clip_width: Property<i32>,
pub source_clip_height: Property<i32>,
pub cached_rendering_data: CachedRenderingData,
}
impl Item for ClippedImage {
fn init(self: Pin<&Self>, _window: &ComponentWindow) {}
fn geometry(self: Pin<&Self>) -> Rect {
euclid::rect(
Self::FIELD_OFFSETS.x.apply_pin(self).get(),
Self::FIELD_OFFSETS.y.apply_pin(self).get(),
Self::FIELD_OFFSETS.width.apply_pin(self).get(),
Self::FIELD_OFFSETS.height.apply_pin(self).get(),
)
}
fn rendering_primitive(
self: Pin<&Self>,
_window: &ComponentWindow,
) -> HighLevelRenderingPrimitive {
HighLevelRenderingPrimitive::Image {
source: Self::FIELD_OFFSETS.source.apply_pin(self).get(),
source_clip_rect: euclid::rect(
Self::FIELD_OFFSETS.source_clip_x.apply_pin(self).get(),
Self::FIELD_OFFSETS.source_clip_y.apply_pin(self).get(),
Self::FIELD_OFFSETS.source_clip_width.apply_pin(self).get(),
Self::FIELD_OFFSETS.source_clip_height.apply_pin(self).get(),
),
}
}
fn rendering_variables(
self: Pin<&Self>,
_window: &ComponentWindow,
) -> SharedArray<RenderingVariable> {
let mut vars = SharedArray::default();
let width = Self::FIELD_OFFSETS.width.apply_pin(self).get();
let height = Self::FIELD_OFFSETS.height.apply_pin(self).get();
if width > 0. {
vars.push(RenderingVariable::ScaledWidth(width));
}
if height > 0. {
vars.push(RenderingVariable::ScaledHeight(height));
}
vars
}
fn layouting_info(self: Pin<&Self>, _window: &crate::eventloop::ComponentWindow) -> LayoutInfo {
// FIXME: should we use the image size here
Default::default()
}
fn input_event(
self: Pin<&Self>,
_: MouseEvent,
_window: &ComponentWindow,
_self_component: &VRc<ComponentVTable, vtable::Dyn>,
_self_index: usize,
) -> InputEventResult {
InputEventResult::EventIgnored
}
fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult {
KeyEventResult::EventIgnored
}
fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {}
}
impl ItemConsts for ClippedImage {
const cached_rendering_data_offset: const_field_offset::FieldOffset<
ClippedImage,
CachedRenderingData,
> = ClippedImage::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
}
ItemVTable_static! {
/// The VTable for `ClippedImage`
#[no_mangle]
pub static ClippedImageVTable for ClippedImage
}
#[derive(Copy, Clone, Debug, PartialEq, strum_macros::EnumString, strum_macros::Display)]
#[repr(C)]
#[allow(non_camel_case_types)]

View file

@ -489,6 +489,7 @@ fn generate_component<'id>(
rtti.extend(
[
rtti_for::<Image>(),
rtti_for::<ClippedImage>(),
rtti_for::<Text>(),
rtti_for::<Rectangle>(),
rtti_for::<BorderRectangle>(),

View file

@ -18,8 +18,8 @@ use sixtyfps_corelib::eventloop::ComponentWindow;
use sixtyfps_corelib::{
graphics::{
Color, Frame as GraphicsFrame, GraphicsBackend, GraphicsWindow,
HighLevelRenderingPrimitive, Point, Rect, RenderingPrimitivesBuilder, RenderingVariable,
Resource, RgbaColor, Size,
HighLevelRenderingPrimitive, IntRect, Point, Rect, RenderingPrimitivesBuilder,
RenderingVariable, Resource, RgbaColor, Size,
},
SharedArray,
};
@ -413,7 +413,7 @@ impl RenderingPrimitivesBuilder for GLRenderingPrimitivesBuilder {
let rect = Rect::new(Point::default(), Size::new(*width, *height));
smallvec![self.fill_rectangle(&rect, *border_radius, *border_width)]
}
HighLevelRenderingPrimitive::Image { source } => {
HighLevelRenderingPrimitive::Image { source, source_clip_rect } => {
match source {
#[cfg(not(target_arch = "wasm32"))]
Resource::AbsoluteFilePath(path) => {
@ -430,7 +430,8 @@ impl RenderingPrimitivesBuilder for GLRenderingPrimitivesBuilder {
smallvec![GLRenderingPrimitivesBuilder::create_image(
&self.context,
&mut *self.texture_atlas.borrow_mut(),
image
image,
source_clip_rect
)]
}
#[cfg(target_arch = "wasm32")]
@ -447,12 +448,14 @@ impl RenderingPrimitivesBuilder for GLRenderingPrimitivesBuilder {
let shared_primitive = shared_primitive.clone();
let window = self.window.clone();
let event_loop_proxy = self.event_loop_proxy.clone();
let source_clip_rect = *source_clip_rect;
move || {
let texture_primitive =
GLRenderingPrimitivesBuilder::create_image(
&context,
&mut *atlas.borrow_mut(),
&html_image,
&source_clip_rect,
);
*shared_primitive.borrow_mut() = Some(texture_primitive);
@ -485,7 +488,8 @@ impl RenderingPrimitivesBuilder for GLRenderingPrimitivesBuilder {
smallvec![GLRenderingPrimitivesBuilder::create_image(
&self.context,
&mut *self.texture_atlas.borrow_mut(),
image
image,
&source_clip_rect
)]
}
Resource::EmbeddedRgbaImage { width, height, data } => {
@ -498,7 +502,8 @@ impl RenderingPrimitivesBuilder for GLRenderingPrimitivesBuilder {
smallvec![GLRenderingPrimitivesBuilder::create_image(
&self.context,
&mut *self.texture_atlas.borrow_mut(),
image
image,
&source_clip_rect
)]
}
Resource::None => SmallVec::new(),
@ -627,6 +632,7 @@ impl GLRenderingPrimitivesBuilder {
context: &Rc<glow::Context>,
atlas: &mut TextureAtlas,
image: impl texture::UploadableAtlasImage,
source_rect: &IntRect,
) -> GLRenderingPrimitive {
let rect =
Rect::new(Point::new(0.0, 0.0), Size::new(image.width() as f32, image.height() as f32));
@ -642,8 +648,10 @@ impl GLRenderingPrimitivesBuilder {
&context,
&vec![vertex1, vertex2, vertex3, vertex1, vertex3, vertex4],
);
let texture_vertices =
GLArrayBuffer::new(&context, &atlas_allocation.normalized_texture_coordinates());
let texture_vertices = GLArrayBuffer::new(
&context,
&atlas_allocation.normalized_texture_coordinates_with_source_rect(source_rect),
);
GLRenderingPrimitive::Texture { vertices, texture_vertices, texture: atlas_allocation }
}

View file

@ -9,7 +9,11 @@
LICENSE END */
use super::{GLContext, Vertex};
use glow::HasContext;
use pathfinder_geometry::{rect::RectI, vector::Vector2I};
use pathfinder_geometry::{
rect::{RectF, RectI},
vector::{Vector2F, Vector2I},
};
use sixtyfps_corelib::graphics::IntRect;
use std::{cell::RefCell, rc::Rc};
pub struct GLTexture {
@ -202,12 +206,25 @@ impl Drop for AtlasAllocation {
}
impl AtlasAllocation {
pub(crate) fn normalized_texture_coordinates(&self) -> [Vertex; 6] {
pub(crate) fn normalized_texture_coordinates_with_source_rect(
&self,
source_rect: &IntRect,
) -> [Vertex; 6] {
let atlas_width = self.atlas.texture.width as f32;
let atlas_height = self.atlas.texture.height as f32;
let origin = self.texture_coordinates.origin();
let size = self.texture_coordinates.size();
let texture_coordinates = RectI::new(origin, size);
let texture_coordinates = RectF::new(
Vector2F::new(
(origin.x() + source_rect.min_x()) as f32,
(origin.y() + source_rect.min_y()) as f32,
),
if source_rect.is_empty() {
Vector2F::new(size.x() as f32, size.y() as f32)
} else {
Vector2F::new(source_rect.width() as f32, source_rect.height() as f32)
},
);
let tex_left = ((texture_coordinates.min_x() as f32) + 0.5) / atlas_width;
let tex_top = ((texture_coordinates.min_y() as f32) + 0.5) / atlas_height;
@ -221,6 +238,9 @@ impl AtlasAllocation {
[tex_vertex1, tex_vertex2, tex_vertex3, tex_vertex1, tex_vertex3, tex_vertex4]
}
pub(crate) fn normalized_texture_coordinates(&self) -> [Vertex; 6] {
self.normalized_texture_coordinates_with_source_rect(&IntRect::default())
}
}
impl GLAtlasTexture {

View file

@ -163,7 +163,10 @@ impl Item for NativeButton {
option.state |= QStyle::State_Enabled;
qApp->style()->drawControl(QStyle::CE_PushButton, &option, &p, nullptr);
});
return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() };
return HighLevelRenderingPrimitive::Image {
source: imgarray.to_resource(),
source_clip_rect: Default::default(),
};
}
fn rendering_variables(
@ -294,7 +297,10 @@ impl Item for NativeCheckBox {
option.state |= QStyle::State_Enabled;
qApp->style()->drawControl(QStyle::CE_CheckBox, &option, &p, nullptr);
});
return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() };
return HighLevelRenderingPrimitive::Image {
source: imgarray.to_resource(),
source_clip_rect: Default::default(),
};
}
fn rendering_variables(
@ -444,7 +450,10 @@ impl Item for NativeSpinBox {
auto text_rect = style->subControlRect(QStyle::CC_SpinBox, &option, QStyle::SC_SpinBoxEditField, nullptr);
p.drawText(text_rect, QString::number(value));
});
return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() };
return HighLevelRenderingPrimitive::Image {
source: imgarray.to_resource(),
source_clip_rect: Default::default(),
};
}
fn rendering_variables(
@ -651,7 +660,10 @@ impl Item for NativeSlider {
auto style = qApp->style();
style->drawComplexControl(QStyle::CC_Slider, &option, &p, nullptr);
});
return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() };
return HighLevelRenderingPrimitive::Image {
source: imgarray.to_resource(),
source_clip_rect: Default::default(),
};
}
fn rendering_variables(
@ -931,7 +943,10 @@ impl Item for NativeGroupBox {
QStyle::SH_GroupBox_TextLabelColor, &option));
qApp->style()->drawComplexControl(QStyle::CC_GroupBox, &option, &p, nullptr);
});
return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() };
return HighLevelRenderingPrimitive::Image {
source: imgarray.to_resource(),
source_clip_rect: Default::default(),
};
}
fn rendering_variables(
@ -1085,7 +1100,10 @@ impl Item for NativeLineEdit {
option.state |= QStyle::State_Enabled;
qApp->style()->drawPrimitive(QStyle::PE_PanelLineEdit, &option, &p, nullptr);
});
return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() };
return HighLevelRenderingPrimitive::Image {
source: imgarray.to_resource(),
source_clip_rect: Default::default(),
};
}
fn rendering_variables(
@ -1336,7 +1354,10 @@ impl Item for NativeScrollView {
data.pressed == 1,
);
return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() };
return HighLevelRenderingPrimitive::Image {
source: imgarray.to_resource(),
source_clip_rect: Default::default(),
};
}
fn rendering_variables(
@ -1584,7 +1605,10 @@ impl Item for NativeStandardListViewItem {
qApp->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &option, &p, nullptr);
qApp->style()->drawControl(QStyle::CE_ItemViewItem, &option, &p, nullptr);
});
return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() };
return HighLevelRenderingPrimitive::Image {
source: imgarray.to_resource(),
source_clip_rect: Default::default(),
};
}
fn rendering_variables(
@ -1716,7 +1740,10 @@ impl Item for NativeComboBox {
qApp->style()->drawComplexControl(QStyle::CC_ComboBox, &option, &p, nullptr);
qApp->style()->drawControl(QStyle::CE_ComboBoxLabel, &option, &p, nullptr);
});
return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() };
return HighLevelRenderingPrimitive::Image {
source: imgarray.to_resource(),
source_clip_rect: Default::default(),
};
}
fn rendering_variables(

View file

@ -45,6 +45,7 @@ fn gen_corelib(include_dir: &Path) -> anyhow::Result<()> {
"Rectangle",
"BorderRectangle",
"Image",
"ClippedImage",
"TouchArea",
"Flickable",
"Text",