WIP: API to expose image loading from C++ and Rust

This commit is contained in:
Olivier Goffart 2021-05-28 12:13:49 +02:00 committed by Olivier Goffart
parent 4584c40544
commit 0b3fecf300
15 changed files with 140 additions and 68 deletions

View file

@ -11,26 +11,39 @@ LICENSE END */
#include <string_view>
#include "sixtyfps_image_internal.h"
#include "sixtyfps_string.h"
#include "sixtyfps_sharedvector.h"
namespace sixtyfps {
struct ImageReference
/// An image type that can be displayed by the Image element
struct Image
{
public:
ImageReference() : data(Data::None()) { }
ImageReference(const SharedString &file_path) : data(Data::AbsoluteFilePath(file_path)) { }
Image() : data(Data::None()) { }
friend bool operator==(const ImageReference &a, const ImageReference &b) {
static Image load_from_path(const SharedString &file_path) {
Image img;
img.data = Data::AbsoluteFilePath(file_path);
return img;
}
static Image load_from_argb(int width, int height, const SharedVector<uint32_t> &data) {
Image img;
img.data = Data::EmbeddedRgbaImage(width, height, data);
return img;
}
friend bool operator==(const Image &a, const Image &b) {
return a.data == b.data;
}
friend bool operator!=(const ImageReference &a, const ImageReference &b) {
friend bool operator!=(const Image &a, const Image &b) {
return a.data != b.data;
}
private:
using Tag = cbindgen_private::types::ImageReference::Tag;
using Data = cbindgen_private::types::ImageReference;
using Data = cbindgen_private::types::Image;
Data data;
};

View file

@ -195,7 +195,11 @@ fn to_eval_value<'cx>(
}
},
Type::Image => {
Ok(Value::Image(ImageReference::AbsoluteFilePath(val.to_string(cx)?.value().into())))
let path = val.to_string(cx)?.value();
Ok(Value::Image(
sixtyfps_corelib::graphics::Image::load_from_path(std::path::Path::new(&path))
.or_else(|_| cx.throw_error(format!("cannot load image {:?}", path)))?,
))
}
Type::Bool => Ok(Value::Bool(val.downcast_or_throw::<JsBoolean, _>(cx)?.value())),
Type::Struct { fields, .. } => {
@ -243,7 +247,7 @@ fn to_js_value<'cx>(
Value::Number(n) => JsNumber::new(cx, n).as_value(cx),
Value::String(s) => JsString::new(cx, s.as_str()).as_value(cx),
Value::Bool(b) => JsBoolean::new(cx, b).as_value(cx),
Value::Image(r) => match r {
Value::Image(r) => match r.0 {
ImageReference::None => JsUndefined::new().as_value(cx),
ImageReference::AbsoluteFilePath(path) => JsString::new(cx, path.as_str()).as_value(cx),
ImageReference::EmbeddedData { .. } | ImageReference::EmbeddedRgbaImage { .. } => {

View file

@ -224,8 +224,9 @@ pub mod re_exports {
init_component_items, Component, ComponentRefPin, ComponentVTable,
};
pub use sixtyfps_corelib::graphics::{
Brush, GradientStop, LinearGradientBrush, PathArcTo, PathCubicTo, PathData, PathElement,
PathEvent, PathLineTo, PathMoveTo, PathQuadraticTo, Point, Rect, Size,
Brush, GradientStop, Image, ImageReference, LinearGradientBrush, PathArcTo, PathCubicTo,
PathData, PathElement, PathEvent, PathLineTo, PathMoveTo, PathQuadraticTo, Point, Rect,
Size,
};
pub use sixtyfps_corelib::input::{
FocusEvent, InputEventResult, KeyEvent, KeyEventResult, KeyboardModifiers, MouseEvent,
@ -242,7 +243,6 @@ pub mod re_exports {
pub use sixtyfps_corelib::window::ComponentWindow;
pub use sixtyfps_corelib::Color;
pub use sixtyfps_corelib::ComponentVTable_static;
pub use sixtyfps_corelib::ImageReference;
pub use sixtyfps_corelib::SharedString;
pub use sixtyfps_corelib::SharedVector;
pub use sixtyfps_rendering_backend_default::native_widgets::*;

View file

@ -255,7 +255,7 @@ impl CppType for Type {
}
Type::Array(i) => Some(format!("std::shared_ptr<sixtyfps::Model<{}>>", i.cpp_type()?)),
Type::Image => Some("sixtyfps::ImageReference".to_owned()),
Type::Image => Some("sixtyfps::Image".to_owned()),
Type::Builtin(elem) => elem.native_class.cpp_type.clone(),
Type::Enumeration(enumeration) => Some(format!("sixtyfps::{}", enumeration.name)),
Type::Brush => Some("sixtyfps::Brush".to_owned()),
@ -1613,8 +1613,8 @@ fn compile_expression(
}
Expression::ImageReference(resource_ref) => {
match resource_ref {
crate::expression_tree::ImageReference::None => format!(r#"sixtyfps::ImageReference()"#),
crate::expression_tree::ImageReference::AbsolutePath(path) => format!(r#"sixtyfps::ImageReference(sixtyfps::SharedString(u8"{}"))"#, escape_string(path.as_str())),
crate::expression_tree::ImageReference::None => format!(r#"sixtyfps::Image()"#),
crate::expression_tree::ImageReference::AbsolutePath(path) => format!(r#"sixtyfps::Image::load_from_path(sixtyfps::SharedString(u8"{}"))"#, escape_string(path.as_str())),
crate::expression_tree::ImageReference::EmbeddedData(_) => unimplemented!("The C++ generator does not support resource embedding yet")
}
}

View file

@ -39,7 +39,7 @@ fn rust_type(ty: &Type) -> Option<proc_macro2::TokenStream> {
Type::LogicalLength => Some(quote!(f32)),
Type::Percent => Some(quote!(f32)),
Type::Bool => Some(quote!(bool)),
Type::Image => Some(quote!(sixtyfps::re_exports::ImageReference)),
Type::Image => Some(quote!(sixtyfps::re_exports::Image)),
Type::Struct { fields, name: None, .. } => {
let elem = fields.values().map(|v| rust_type(v)).collect::<Option<Vec<_>>>()?;
// This will produce a tuple
@ -1335,14 +1335,18 @@ fn compile_expression(expr: &Expression, component: &Rc<Component>) -> TokenStre
Expression::ImageReference(resource_ref) => {
match resource_ref {
crate::expression_tree::ImageReference::None => {
quote!(sixtyfps::re_exports::ImageReference::None)
quote!(sixtyfps::re_exports::Image::default())
}
crate::expression_tree::ImageReference::AbsolutePath(path) => {
quote!(sixtyfps::re_exports::ImageReference::AbsoluteFilePath(sixtyfps::re_exports::SharedString::from(#path)))
quote!(sixtyfps::re_exports::Image::load_from_path(sixtyfps::re_exports::SharedString::from(#path)))
},
crate::expression_tree::ImageReference::EmbeddedData(resource_id) => {
let symbol = format_ident!("SFPS_EMBEDDED_RESOURCE_{}", resource_id);
quote!(sixtyfps::re_exports::ImageReference::EmbeddedData(#symbol.into()))
quote!(
sixtyfps::re_exports::Image(
sixtyfps::re_exports::ImageReference::EmbeddedData(#symbol.into())
)
)
}
}
}

View file

@ -42,31 +42,8 @@ pub use path::*;
mod brush;
pub use brush::*;
/// A resource is a reference to binary data, for example images. They can be accessible on the file
/// system or embedded in the resulting binary. Or they might be URLs to a web server and a downloaded
/// is necessary before they can be used.
///
/// TODO! If we want to make this type public API, we should not make it an enum, but an opaque type instead
#[derive(Clone, PartialEq, Debug)]
#[repr(u8)]
pub enum ImageReference {
/// A resource that does not represent any data.
None,
/// A resource that points to a file in the file system
AbsoluteFilePath(crate::SharedString),
/// A resource that is embedded in the program and accessible via pointer
/// The format is the same as in a file
EmbeddedData(super::slice::Slice<'static, u8>),
/// Raw ARGB
#[allow(missing_docs)]
EmbeddedRgbaImage { width: u32, height: u32, data: super::sharedvector::SharedVector<u32> },
}
impl Default for ImageReference {
fn default() -> Self {
ImageReference::None
}
}
mod image;
pub use self::image::*;
/// CachedGraphicsData allows the graphics backend to store an arbitrary piece of data associated with
/// an item, which is typically computed by accessing properties. The dependency_tracker is used to allow

View file

@ -0,0 +1,65 @@
/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
SPDX-License-Identifier: GPL-3.0-only
This file is also available under commercial licensing terms.
Please contact info@sixtyfps.io for more information.
LICENSE END */
use crate::slice::Slice;
use crate::{SharedString, SharedVector};
/// A resource is a reference to binary data, for example images. They can be accessible on the file
/// system or embedded in the resulting binary. Or they might be URLs to a web server and a downloaded
/// is necessary before they can be used.
#[derive(Clone, PartialEq, Debug)]
#[repr(u8)]
pub enum ImageReference {
/// A resource that does not represent any data.
None,
/// A resource that points to a file in the file system
AbsoluteFilePath(SharedString),
/// A resource that is embedded in the program and accessible via pointer
/// The format is the same as in a file
EmbeddedData(Slice<'static, u8>),
/// Raw ARGB
#[allow(missing_docs)]
EmbeddedRgbaImage { width: u32, height: u32, data: SharedVector<u32> },
}
impl Default for ImageReference {
fn default() -> Self {
ImageReference::None
}
}
/// Error generated if an image cannot be loaded for any reasons.
#[derive(Default, Debug, PartialEq)]
pub struct LoadImageError(());
/// An image type that can be displayed by the Image element
#[repr(transparent)]
#[derive(Default, Clone, Debug, PartialEq)]
// FIXME: the inner should be private
pub struct Image(pub ImageReference);
impl Image {
/// Create an Image from a path to a file containing an image
pub fn load_from_path(path: &std::path::Path) -> Result<Self, LoadImageError> {
Ok(Image(ImageReference::AbsoluteFilePath(path.to_str().ok_or(LoadImageError(()))?.into())))
}
/// Create an image from argb pixels
pub fn load_from_argb(
width: u32,
height: u32,
data: SharedVector<u32>,
) -> Result<Self, LoadImageError> {
if data.len() != width as usize * height as usize {
Err(LoadImageError(()))
} else {
Ok(Image(ImageReference::EmbeddedRgbaImage { width, height, data }))
}
}
}

View file

@ -20,7 +20,7 @@ When adding an item or a property, it needs to be kept in sync with different pl
- Don't forget to update the documentation
*/
use super::{Item, ItemConsts, ItemRc};
use crate::graphics::{ImageReference, Rect};
use crate::graphics::Rect;
use crate::input::{
FocusEvent, InputEventFilterResult, InputEventResult, KeyEvent, KeyEventResult, MouseEvent,
};
@ -55,7 +55,7 @@ impl Default for ImageFit {
#[pin]
/// The implementation of the `Image` element
pub struct ImageItem {
pub source: Property<ImageReference>,
pub source: Property<crate::graphics::Image>,
pub x: Property<f32>,
pub y: Property<f32>,
pub width: Property<f32>,
@ -72,7 +72,7 @@ impl Item for ImageItem {
}
fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo {
let natural_size = window.0.image_size(&self.source());
let natural_size = window.0.image_size(&self.source().0);
LayoutInfo {
preferred_width: natural_size.width,
preferred_height: natural_size.height,
@ -121,7 +121,7 @@ impl ItemConsts for ImageItem {
#[pin]
/// The implementation of the `ClippedImage` element
pub struct ClippedImage {
pub source: Property<ImageReference>,
pub source: Property<crate::graphics::Image>,
pub x: Property<f32>,
pub y: Property<f32>,
pub width: Property<f32>,
@ -143,7 +143,7 @@ impl Item for ClippedImage {
}
fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo {
let natural_size = window.0.image_size(&self.source());
let natural_size = window.0.image_size(&self.source().0);
LayoutInfo {
preferred_width: natural_size.width,
preferred_height: natural_size.height,
@ -185,4 +185,4 @@ impl ItemConsts for ClippedImage {
ClippedImage,
CachedRenderingData,
> = ClippedImage::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
}
}

View file

@ -32,7 +32,7 @@ declare_ValueType![
f32,
f64,
crate::SharedString,
crate::ImageReference,
crate::graphics::Image,
crate::Color,
crate::PathData,
crate::animations::EasingCurve,

View file

@ -10,7 +10,8 @@ LICENSE END */
use core::convert::TryInto;
use sixtyfps_compilerlib::langtype::Type as LangType;
use sixtyfps_corelib::{Brush, ImageReference, PathData, SharedString, SharedVector};
use sixtyfps_corelib::graphics::Image;
use sixtyfps_corelib::{Brush, PathData, SharedString, SharedVector};
use std::collections::HashMap;
use std::iter::FromIterator;
use std::path::{Path, PathBuf};
@ -40,6 +41,8 @@ pub enum ValueType {
Struct,
/// Correspond to `brush` or `color` type in .60. For color, this is then a [`Brush::SolidColor`]
Brush,
/// Correspond to `image` type in .60.
Image,
/// The type is not a public type but something internal.
Other = -1,
}
@ -92,7 +95,7 @@ pub enum Value {
Bool(bool),
#[doc(hidden)]
/// Correspond to the `image` type in .60
Image(ImageReference),
Image(Image),
/// An Array in the .60 language.
Array(SharedVector<Value>),
/// A more complex model which is not created by the interpreter itself (Value::Array can also be used for model)
@ -212,7 +215,7 @@ macro_rules! declare_value_conversion {
declare_value_conversion!(Number => [u32, u64, i32, i64, f32, f64, usize, isize] );
declare_value_conversion!(String => [SharedString] );
declare_value_conversion!(Bool => [bool] );
declare_value_conversion!(Image => [ImageReference] );
declare_value_conversion!(Image => [Image] );
declare_value_conversion!(Struct => [Struct] );
declare_value_conversion!(Brush => [Brush] );
declare_value_conversion!(PathElements => [PathData]);

View file

@ -18,7 +18,6 @@ use sixtyfps_compilerlib::object_tree::{Element, ElementRc};
use sixtyfps_compilerlib::*;
use sixtyfps_compilerlib::{diagnostics::BuildDiagnostics, object_tree::PropertyDeclaration};
use sixtyfps_corelib::component::{Component, ComponentRef, ComponentRefPin, ComponentVTable};
use sixtyfps_corelib::graphics::ImageReference;
use sixtyfps_corelib::item_tree::{
ItemTreeNode, ItemVisitorRefMut, ItemVisitorVTable, TraversalOrder, VisitChildrenResult,
};
@ -750,7 +749,7 @@ pub(crate) fn generate_component<'id>(
Type::Angle => animated_property_info::<f32>(),
Type::PhysicalLength => animated_property_info::<f32>(),
Type::LogicalLength => animated_property_info::<f32>(),
Type::Image => property_info::<ImageReference>(),
Type::Image => property_info::<sixtyfps_corelib::graphics::Image>(),
Type::Bool => property_info::<bool>(),
Type::Callback { .. } => {
custom_callbacks.insert(name.clone(), builder.add_field_type::<Callback>());

View file

@ -16,7 +16,7 @@ use corelib::graphics::{GradientStop, LinearGradientBrush, PathElement};
use corelib::items::{ItemRef, PropertyAnimation};
use corelib::rtti::AnimatedBindingKind;
use corelib::window::ComponentWindow;
use corelib::{Brush, Color, ImageReference, PathData, SharedString, SharedVector};
use corelib::{Brush, Color, PathData, SharedString, SharedVector};
use sixtyfps_compilerlib::expression_tree::{
BindingExpression, BuiltinFunction, EasingCurve, Expression, Path as ExprPath,
PathElement as ExprPathElement,
@ -468,15 +468,18 @@ pub fn eval_expression(e: &Expression, local_context: &mut EvalLocalContext) ->
}
}
Expression::ImageReference(resource_ref) => {
match resource_ref {
Value::Image(match resource_ref {
sixtyfps_compilerlib::expression_tree::ImageReference::None => {
Value::Image(ImageReference::None)
Ok(Default::default())
}
sixtyfps_compilerlib::expression_tree::ImageReference::AbsolutePath(path) => {
Value::Image(ImageReference::AbsoluteFilePath(path.into()))
corelib::graphics::Image::load_from_path(std::path::Path::new(path))
}
sixtyfps_compilerlib::expression_tree::ImageReference::EmbeddedData(_) => panic!("Resource embedding is not supported by the interpreter")
}
}.unwrap_or_else(|_| {
eprintln!("Could not load image {:?}",resource_ref );
Default::default()
}))
}
Expression::Condition { condition, true_expr, false_expr } => {
match eval_expression(&**condition, local_context).try_into()

View file

@ -22,7 +22,7 @@ use std::pin::Pin;
use std::rc::Rc;
use sixtyfps_corelib::graphics::{
Brush, Color, FontRequest, ImageReference, IntRect, Point, Rect, RenderingCache, Size,
Brush, Color, FontRequest, Image, IntRect, Point, Rect, RenderingCache, Size,
};
use sixtyfps_corelib::item_rendering::{CachedRenderingData, ItemRenderer};
use sixtyfps_corelib::items::{
@ -30,7 +30,8 @@ use sixtyfps_corelib::items::{
};
use sixtyfps_corelib::properties::Property;
use sixtyfps_corelib::window::ComponentWindow;
use sixtyfps_corelib::SharedString;
use sixtyfps_corelib::{ImageReference, SharedString};
mod graphics_window;
use graphics_window::*;
@ -241,6 +242,7 @@ impl OpenGLContext {
(Self::Current(windowed_context), renderer)
}
#[cfg(target_arch = "wasm32")]
{
use wasm_bindgen::JsCast;
@ -1353,7 +1355,7 @@ impl GLItemRenderer {
fn draw_image_impl(
&mut self,
item_cache: &CachedRenderingData,
source_property: std::pin::Pin<&Property<ImageReference>>,
source_property: std::pin::Pin<&Property<Image>>,
source_clip_rect: IntRect,
target_width: std::pin::Pin<&Property<f32>>,
target_height: std::pin::Pin<&Property<f32>>,
@ -1374,7 +1376,7 @@ impl GLItemRenderer {
.borrow_mut()
.load_item_graphics_cache_with_function(item_cache, || {
self.shared_data
.load_image_resource(&source_property.get())
.load_image_resource(&source_property.get().0)
.and_then(|cached_image| {
cached_image.as_renderable(
// The condition at the entry of the function ensures that width/height are positive

View file

@ -10,7 +10,7 @@ LICENSE END */
use cpp::*;
use items::{ImageFit, TextHorizontalAlignment, TextVerticalAlignment};
use sixtyfps_corelib::graphics::{Brush, FontRequest, Point, Rect, RenderingCache, Size};
use sixtyfps_corelib::graphics::{Brush, FontRequest, Image, Point, Rect, RenderingCache, Size};
use sixtyfps_corelib::input::{InternalKeyCode, KeyEvent, KeyEventType, MouseEvent};
use sixtyfps_corelib::item_rendering::{CachedRenderingData, ItemRenderer};
use sixtyfps_corelib::items::{self, FillRule, ItemRef, TextOverflow, TextWrap};
@ -820,7 +820,7 @@ impl QtItemRenderer<'_> {
fn draw_image_impl(
&mut self,
item_cache: &CachedRenderingData,
source_property: Pin<&Property<ImageReference>>,
source_property: Pin<&Property<Image>>,
dest_rect: qttypes::QRectF,
source_rect: Option<qttypes::QRectF>,
target_width: std::pin::Pin<&Property<f32>>,
@ -851,7 +851,7 @@ impl QtItemRenderer<'_> {
None
};
load_image_from_resource(&source_property.get(), source_size, image_fit).map_or(
load_image_from_resource(&source_property.get().0, source_size, image_fit).map_or(
QtRenderingCacheItem::Invalid,
|mut pixmap: qttypes::QPixmap| {
let colorize = colorize_property.map_or(Brush::default(), |c| c.get());

View file

@ -110,6 +110,7 @@ fn gen_corelib(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> {
"SharedString",
"SharedVector",
"ImageReference",
"Image",
"Color",
"PathData",
"PathElement",
@ -179,7 +180,7 @@ fn gen_corelib(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> {
.write_to_file(include_dir.join("sixtyfps_properties_internal.h"));
for (rust_types, extra_excluded_types, internal_header) in [
(vec!["ImageReference"], vec![], "sixtyfps_image_internal.h"),
(vec!["ImageReference", "Image"], vec![], "sixtyfps_image_internal.h"),
(
vec!["Color", "sixtyfps_color_brighter", "sixtyfps_color_darker"],
vec![],
@ -248,6 +249,7 @@ fn gen_corelib(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> {
.with_src(crate_dir.join("graphics/color.rs"))
.with_src(crate_dir.join("graphics/path.rs"))
.with_src(crate_dir.join("graphics/brush.rs"))
.with_src(crate_dir.join("graphics/image.rs"))
.with_src(crate_dir.join("animations.rs"))
// .with_src(crate_dir.join("input.rs"))
.with_src(crate_dir.join("item_rendering.rs"))