GithubHelp home page GithubHelp logo

abey79 / vsvg Goto Github PK

View Code? Open in Web Editor NEW
101.0 1.0 12.0 18.08 MB

Fast and portable tools for plotter users

Home Page: http://whisk.rs

License: MIT License

Rust 94.60% Just 0.24% Python 1.90% WGSL 1.87% HTML 1.39%
egui rust svg generative-art hacktoberfest penplotter svg-files vector-graphics

vsvg's Introduction

The vsvg project (incl. whiskers and msvg)

What's this?

whiskers msvg vsvg/vsvg-viewer
image image image
whiskers is a Rust-based, Processing-like interactive sketching environment for generative plotter art. It's fast, it's web-ready, and it's a delight to use.

Try it here!
msvg is a (WIP!) fast browser for SVG collections. It smoothly addresses the challenge of browsing through large collections of generated SVGs, e.g. to find the best looking ones for plotting. vsvg and vsvg-viewer are the core crates behind whiskers and msvg. They implement the core data structures for manipulating vector data for plotter applications, as well as an ultra-performant, cross-platform, hardware-accelerated, and easy-to-extend viewer.

Documentation

The documentation is WIP—watch this space for updates.

In the meantime, each crate of the vsvg project has its own README with additional information:

Installing

There is currently no facilities to install vsvg unfortunately. It must be compiled and installed from source. Fortunately, this is actually not much more complicated than running a Python executable.

First, install Rust by running the command provided by the official Rust website:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Normally, this will add $HOME/.cargo/bin in your path.

Then, download the vsvg source code:

git clone https://github.com/abey79/vsvg
cd vsvg

Running the sketch examples

See whiskers's README.md.

Installing vsvg-cli

See vsvg-cli's README.md.

Design notes

A few design considerations can be found here. They concern the use of this project as basis for a possible future Rust-based vpype-core package.

Licence

This project is available under the MIT licence.

vsvg's People

Contributors

abey79 avatar afternoon2 avatar danieledapo avatar hapiel avatar karliss avatar reidab avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

vsvg's Issues

Exporting SVG

Working on my initial take at the SVG writer, it appears useful to come up with some quick and dirty spec, more or less inspired by what is currently implemented in vpype.

  1. No real need for an xml prolog as we're utf-8 (vpype includes one).

  2. Metadata in the correct format, as vpype currently does.

    <metadata>
      <rdf:RDF>
        <cc:Work>
          <dc:format>image/svg+xml</dc:format>
          <dc:source>vpype random -n 1 write -f svg -</dc:source>
          <dc:date>2023-03-28T11:08:16.164053</dc:date>
        </cc:Work>
      </rdf:RDF>
    </metadata>
  3. All path in their respective layers.

  4. Attributes that are uniform within a layer should appear at the layer level (as opposed to the path level).

  5. Attributes whose value corresponds to the SVG default should be omitted.

  6. Path should be converted to the closest corresponding SVG representation.

    • <polyline>/<polygon> for purely linear path (FlattenedPath and Path without Béziers)
    • <p> otherwise

    The motivation for this is that polygon/polyline are possibly leaner to parse.

  7. The resulting SVG should be human-readable (i.e. indented) on a best-effort basis.

  8. If page size is available, use it. Otherwise, use bounds and original coordinates, and adjusting viewBox accordingly (vpype shifts coordinates such as to have a 0, 0 top-left in viewBox).

  9. Use cm for width and height.

Viewer architecture/API

vsvg-viewer aims to offer a composable egui-based widget to display vsvg_core::Document in a GUI.

Here is a random list of considerations:

  • If used in vpype's show command, the viewer must be able to run/shutdown multiple times during execution. Apparently, this isn't yet possible with eframe on mac: emilk/egui#1918
  • run_native() doesn't make it easy to use RO refs (e.g. of a Document) in the closure. Because of that, I'm currently exposing DocumentData in the public API, but this should ideally be removed. Related issue: emilk/egui#2152
  • It's unclear to me how to handle error management in run_native()'s AppCreator function?

`Option` and `Vec` support for sketch params

One way to go about this is to have Widget:ui take label: Option<&str>. When passed None, the impl should skip rendering the label and just rendering the actual control. This way, macro code for vectors and option can manually handle the label part (for option, the label should actually be a checkbox, for vector it would be in index).

Interactive sketch

It would be fun to have sketches interactive, to be able to use input from the mouse or keyboard.

This could be very basic, such as something like:

`
let mut x = 0; // somehow persist these values between updates
let mut y = 0;

impl App for MySketch {

fn update(&mut self, sketch: &mut Sketch, _ctx: &mut Context) -> anyhow::Result<()> {

    if mouse::is_pressed() {
        x = mouse::x();
        y = mouse::y();
    }
    sketch
        .circle(x, y, 10);

    Ok(())
}

}`

In this case of course the mouse pixel coordinates need to be converted to a value that is independent of zoom level.

But of course, a next step would be to have some kind of cumulative canvas. So that I can draw lines with my mouse, for example. This would be similar to processing, where each layer is rendered upon the previous layer. Of course this brings up questions, such as should it be possible to overlap lines, and whatnot. But I very much enjoyed in processing that I could easily create my own drawing applications, a kind of co-creative system.

Ultimately I guess that this kind of interaction would allow people to create games and whatnot, which can be paused at any moment to create plots. Not very different from your rusteroids ;)

Scrolling the mouse zoom on default

I prefer the mouse scroll wheel behaviour in vsketch over the one in whiskers.

That means:

  • Scrolling should zoom (currently moves the view up and down)

If moving the page up and down should remain available, it may be an option to have ctrl + scroll move vertically, and shift + scroll horizontally (latter already works)

SVG read panics with `opacity` style on top-level path

This is due to usvg generating a parent group for the path, which is then interpreted as a top-level group for top-level paths.

Example:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns="http://www.w3.org/2000/svg"
   version="1.2"
   height="1400mm"
   width="890mm"
   viewBox="0 0 988.88889 1555.5556">
 
  <path
     style="opacity:0.05"
     d="M 105.7223,389.04708 H 883.1667 V 1166.5041 H 105.7223 Z" />
</svg>

Flattened paths

The case for fully "flattened" representations (e.g. representations where everything is converted to polyline) remains. At the very least, this will be needed for the viewer (unless Bezier -> polyline can be figured out in a shader). HPGL and gcode export also come to mind. Finally, backward plug-in compatibility could also benefit from that, although this is not really a design goal. When flattening a document, keeping the Doc/Layer/Path hierarchy would still be needed though, i.e. for the viewer to handle layer visibility or properly colouring each path using its attached metadata.

To minimise code duplication, my plan is use the following data structures:

        CONCRETE                     GENERIC                    FLATTENED        
     IMPLEMENTATION              IMPLEMENTATION              IMPLEMENTATION      
                                                                                 
+-----------------------+   +-----------------------+   +-----------------------+
|         Path          |   |                       |   |     FlattenedPath     |
|                       |<--|      PathImpl<T>      |-->|                       |
|   PathImpl<BezPath>   |   |                       |   |  PathImpl<Polyline>   |
+-----------------------+   +-----------------------+   +-----------------------+
                                                                                 
+-----------------------+   +-----------------------+   +-----------------------+
|         Layer         |   |                       |   |    FlattenedLayer     |
|                       |<--|     LayerImpl<T>      |-->|                       |
|  LayerImpl<BezPath>   |   |                       |   |  LayerImpl<Polyline>  |
+-----------------------+   +-----------------------+   +-----------------------+
                                                                                 
+-----------------------+   +-----------------------+   +-----------------------+
|       Document        |   |                       |   |   FlattenedDocument   |
|                       |<--|    DocumentImpl<T>    |-->|                       |
| DocumentImpl<BezPath> |   |                       |   |DocumentImpl<Polyline> |
+-----------------------+   +-----------------------+   +-----------------------+
                                                                                 
                                                         Polyline = Vec<[f64; 2]>

The core of the Path/Layer/Document hierarchy is implemented with structures that are generic over the actual path data type. Then, two sets of concrete types are offered, one based on BezPath and another based on a simple vector of points. Any feature that is easy to implement generically is done in XXXImpl<T>. Features that would require too much work to cover both hierarchies and not strictly necessary for the flattened use cases are implemented for Path and friends only. Conversion between normal and flattened structure is offered, though obviously the round-trip would be destructive.

SVG import

The goal of SVG import is to convert SVG into a Path/Layer/Doc representation "as close as possible" to the original data, within the constraints of our chose data model.

In the scope of vpype2, this includes:

  • sort paths into a Document/Layer/Path structure—this includes correctly interpreting inkscape layers
  • curved path elements should be preserved (#1)
  • as much metadata as possible should be extracted (#4)

usvg offers most of that, but doesn't provide access to original attributes: RazrFalcon/resvg#588

Possible avenues:

  1. fork usvg and add raw attribute to nodes
  2. fork usvg and add ref to roxml element to nodes
  3. migrate to svg (a lot more things would have to be done manually)
  4. use svg in parallel to usvg just to extract top-level groups attributes
    4b) use roxmltree in parallel to/before usvg just to extract top-level group attributes
  5. what else?

Of these, (4) appears to be the most immediate hack, though it's not exactly clean. Option (3) would provide the most flexibility in the long term.

Path Index implementation

Like when I first implemented that in vpype, I come to the conclusion that the remove operation on a spatial index is way too costly. So back to an occupancy bool vector and occasional reindexing. This time, I'll try to optimise the reindex trigger based on benchmarks.

As for crates: kiddo 2 seems interesting but still too buggy in 2.0.0-beta.5. I'll use kdtree from the time being. Maybe there are faster option given that I dont need remove?

Layer ID determination

I currently don't have a plan to revolutionise the way vpype identifies its layers: namely using a non-zero integer value, with higher-ID layers drawn on top of lower-ID layers.

vpype currently uses the following approach to determine layer IDs (from vpype read --help):

By default, the read command attempts to preserve the layer structure of the SVG. In this context, top-level groups () are each considered a layer. If any, all non-group, top-level SVG elements are imported into layer 1.

The following logic is used to determine in which layer each SVG top-level group is imported:

  • If a inkscape:label attribute is present and contains digit characters, it is stripped of non-digit characters the resulting number is used as target layer. If the resulting number is 0, layer 1 is used instead.
  • If the previous step fails, the same logic is applied to the id attribute.
  • If both previous steps fail, the target layer matches the top-level group's order of appearance.

However, the stripping non-digit character strategy has led to unexpected behaviour: abey79/vpype#594. Further, EMSL introduced an informal spec on layer names here, which would be worth considering (especially for SVG output).

For the time being, I'll use "first group of digit" rather "all digits" to extract a layer ID from inkscape:label or id.

Fine grain parameter dragging control

Click and drag on a parameter value changes the value in steps of 1. (Can this step size be changed in code?)

Holding shift changes the step size to 0.1

It could be nice to add a smaller step size, like 0.01, perhaps by holding shift + alt?

Also, if step size could be changed in the code, that would also make very small or very big changes easier.

Mutability of Path/Layer/Document

For operations on Path/Layer/Document structures (e.g. transforms, crop, etc.), I'm using for the moment an immutable pattern (e.g. fn ops(self, ...) -> Self {}), though clearly the goal is not to have purely immutable structure (e.g. paths addition/removal in layers, layer addition/removal in document, etc.). I'm not sure yet of the implication and if this is a good idea or not.

usvg drops "point" paths

usvg currently drops path in the form of M x,y l 0,0, which could be used to represent points.

Elementary path structure

vpype uses one-dimensional Numpy array of complex as basic path type. This means that anything curvy must be linearised and transformed into a polyline. This is why the read command has a -q/--quantization option to control the accuracy of this transformation. One design goal of vpype 2 is to no longer degrade curved paths into polylines.

Another design goal is to support compound paths, i.e. paths made of several, possibly-closing sub-paths. This is used to represent shapes with holes. Proper support for shapes with holes is a strong prerequisite towards a robust hatch filling feature for vpype.

One approach is to support all of SVG primitives: elliptic arcs (including full circles and ellipses), quadratic Beziers, and cubic Bézier. The drawback is the added complexity of dealing with so many primitives. Another approach would be to support only polylines and cubic Bézier. They provide an exact approximation of quadratic bezier and a good approximation of arcs, while generally be nice to work with.

As it turns out, the usvg crate offers facilities to normalise any SVG primitive into paths made of polylines and cubic Bézier only. Its output relies on the BezPath structure from the kurbo crate, which offers a dual representation: draw commands (MoveTo, LineTo, CurveTo, ClosePath) and segments (LineSegment, BezierSegment). Each representation has advantages in different circumstances. Conveniently, BezPath also supports compound paths.

Consequently, my current plan is to use krubo::BezPath as fundamental structure for path representation, and build a Path/Layer/Document hierarchy around it.

Window location not persistent on scaled monitor

Windows issue.
I have 3 monitors, on 2 of them the gui works fine, but on the monitor with the scaled interface (windows is set to scale the size of text/apps/other items to 125%), the gui jumps around when it's closed and reran, often jumping to partially outside of view. Annoying, since that's my main monitor for viewing the gui.

Add support for dashed lines

kurbo apparently has support for dashed line, in the form of an iterator-to-iterator transform. Sketch should support filters, and this should be one of those filters.

Next gen renderer

Lots of thinking going on on what should be the next gen renderer for vpype/vsketch.

Main issue: geometry shaders are basically bad, which makes the vpype 1.x approach problematic. For example, WGSL don't support them.

I current see the following approaches.

1) CPU-based non-overlapping triangulation

This basically re-implements vpype 1's approach, i.e. generating non-overlapping triangles covering the fat line and using SDF for round ending/joins + AA rendering.

I've made an initial implementation in 8274167 and following.

Issues:

  • I'm reusing vertices and using an index buffer, but that's likely a dead-end as tex coord cannot be reused (unless I'm missing something).
  • The triangle count is hard to predict for a given path, because of the bonus triangles in 90+° joints. This makes pre-allocation and parallel processing more difficult.
  • The line width must be adjusted to account for AA, and this is scale-dependent. This means that the triangulation might have to be redone during zoom-in/zoom-out.

1b) GPU compute of the same

No idea how good an idea this would be.

2) Instanced drawing and SDF-based overlap control

This needs to be prototyped. The idea is to have a simple 2-triangle primitive instanced for all segment point pairs, and address over-draw at the SDF level. This probably requires precomputing the miter vectors, to be used as half-plan separator for the SDF.

Advantages:

  • Instance buffer has predictable size (basically same as segment count).
  • AA scale-depend thickness adjustment would happen on GPU side, so no CPU computation nor memory transfer.
  • All points (but extremities) would be duplicated in instance buffer (that that must happen anyways for (1) due to tex coord)
  • Instance buffer smaller than corresponding triangle buffer.

Drawer API

Currently, vpype's offer in terms of drawing primitive is very weak: just a few functions to create corresponding np.array(dtype=complex): vpype.rect(), vpype.circle(), etc. To complement that, significant additional work is done by vsketch to implement its Processing-like API. Given the relative simplicity of the basic path type, this works relatively well.

The present work introduces a more complex path type that include cubic Bézier. Proper conversion from primitive (circle, arcs, elliptic arcs, rounded rects, etc.) has consequently become a bit more complex. To address that, I propose a plan to introduce a "Drawer API" to vpype-core with the following functionalities:

  • A minima, rect, circle, etc. (basically everything in SVG).
  • Maybe a Processing-like shape creation API?
  • Maybe a transform stack?

Advantages:

  • Offering a high-level API through the Rust-Python boundary increases the performance.
  • Provides a unified Drawing API that can be used both for vpype's commands and vsketch's API.
  • Take advantage of kurbo's and usvg's facilities.

Implement rulers

vpype's rulers are nice, let's have the same here.

This time, let's just use egui instead of rolling fancy shaders like I did for vpype.

Metadata handling

I'm still in the process of sorting that out.

There are at least two design goals:

  • The data structure should support the hierarchical nature of metadata, i.e. color is looked up for a path but not defined, the look-up should escalate to the layer, then to the document, then to default values.
  • Cloning metadata should be cheap. For example, flattening a document should not copy all the metadata upon cloning, but only lazily upon mutation—if any.

Maybe a HashMap<_, Cow<_>>? Or immutable data structure from the im crate?

Layout operations in the whiskers viewer

I use the whiskers viewer to preview my work before saving it as an svg.
It has some very basic layout settings, such as 'centered' or 'page size'. I would love to have a few more.
The additions I propose could easily be done with vpype, but as this provides no visual feedback, I prefer to do it in this viewer so I can swiftly play with different variations. I could do them in inkscape, but that adds yet another step in the workflow. Or I could implement them in the sketch itself, with a parameter, which is probably what I'll be doing next, but as these are the same for each sketch it could be nice to have them available in the viewer just like how page size is available.

Scale would be the most important for me, for example, if I decide to plot my a5 sketch on a4, I could set the scale to 2, and don't need to modify my code. But there may be other low hanging fruit, such as:

  • Scale
  • Rotate
  • Crop
  • Mirror
  • Translate

Implement `Draw` for `Layer`

It would be very convenient to have Draw implemented on Layer for quick and dirty visualisation (e.g. beatable).

Online pixelart sketch

The functionality of vpype-pixelart should be offered as an online sketch. Things that are needed:

  • support for image input
  • support for enum in sketch params
  • support for "length" type (pen size param)
    • Length = scalar + unit
    • all Draw API should accept impl Into<f64> instead of f64
    • adequate UI widget

Improve `show_central_panel` hook

  • When we have rulers (#69), DocumentWidget should probably egui-allocate everything but provide a struct with some rects (rulers, drawing zone, etc.) to the hook.
  •  Provide scale/offset to the hook to allow the hook to convert between svg and screen pixels

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.