Vector images?
Mark Waddingham
mark at livecode.com
Wed Oct 21 05:28:23 EDT 2015
On 2015-10-20 21:35, Scott Rossi wrote:
> Mark, what you describe sounds great. Even a subset of SVG
> capabilities
> is very welcome.
>
> But I have to ask, partly because I'm clueless when it comes to low
> level
> programming, but also because I'm curious: does it make sense to have
> two
> "realms" of graphics? There are card based (native) graphics, and then
> there are the graphics inside widgets. I recall reading that all of
> the
> graphics rendering in LC was going to be moved over to the skia
> graphics
> library. Is this what enables the display of SVG in widgets or is
> graphics rendering in widgets based on something else? What prevents
> the
> display of SVG graphics outside of a widget? As it stands, there are
> graphics capabilities within widgets that don't apply to native
> graphics.
> Would it not make more sense to have a single universal approach to all
> graphics in LC?
There are a number of things to answer in your question, so I shall
attempt to do so (apologies in advance, what follows is quite long - I
thought it useful to provide a fair amount of background - answers to
your direct questions are at the end!)
--
First and foremost, the most important thing to bear in mind is that
widgets are a way to write engine controls in both a way which is an
order of magnitude easier than doing so in C++ *and* in a much more
accessible way (the number of people in the community at present who
either have the necessary knowledge and/or interest in writing C++ is
very very small). So, widgets are not special, they aren't doing
anything the engine doesn't already have the facilities for - they just
allow said facilities to be exposed at the LiveCode Script level in a
much easier and quicker way than we could ever do in C++.
--
Moving on from that, the current 'stack' we have inside the engine for
graphics is as follows:
Skia (entirely internal - not exposed at any level)
=> This is the 'rasterization' engine - it essentially converts 'paint
this shape here' requests into actual pixels). It is a rather large and
fiddly C++ class library which we have, over time, managed to persuade
to work in the ways that we need it to.
LibGraphics (internal engine API - available for use by the engine)
=> This is a high-level, standard, PostScript-like C API allowing
paths to be stroked and filled with various kinds of paints,
transparency layers to be used and composited with bitmap effects,
images to be rendered etc. It is very similar to CoreGraphics (but
actually has a number of features beyond which CG offers). e.g.
MCGContextCreate(-> myContext)
MCGContextSetRGBAFillColor(myContext, 1, 0, 0, 0.5)
MCGContextAddRectangle(myContext, [0, 0, 400, 400])
MCGContextFill(myContext)
Above this we actually have two abstractions which sit side-by-side:
MCGraphicsContext (internal engine compatibility class - available for
use by the 'classic' engine controls)
=> This is a wrapper around LibGraphics which emulates the old
graphics abstraction in the engine and is used by all existing engine
controls.
Canvas (LCB syntax extension - available for use by LCB)
=> This is a thin wrapper around LibGraphics providing 'nice' syntax
to LCB to allow widgets to paint themselves:
set the fill paint of this canvas to color [1, 0, 0, 0.5]
fill rectangle [0, 0, 400, 400] on this canvas
So - all the existing ('classic') engine controls internally use an old
abstraction called MCGraphicsContext, whilst all new 'engine' controls
use Canvas (if written in LCB) or LibGraphics directly (if written in
C++ - however, we do not intend to write any new engine controls in C++
as we have LCB - it would be a somewhat large 'waste of effort').
--
With the above in mind let's look at what SVG support actually entails.
Firstly, SVG is 'just' an interchange format (although a very powerful
and really quite complex one if implemented fully) - it is a high-level
description of marks on a page. It is similar to the representational
power of PostScript (Level 3 - which includes transparency) but is
declarative (i.e. you describe explicitly what the elements are) rather
than imperative (i.e. you run 'code' to generate the elements -
PostScript is a language). SVG is actually (like all W3C standards)
abstract - it is a well-defined data-structure which has a reference
'encoding' as XML. So an SVG rendering 'stack' notionally looks like
this:
XML Importer - takes SVG XML and converts it to an XML document that
can be manipulated)
SVG-XML Processor - processes the XML document and produces the SVG
data structure)
SVG Renderer - takes the SVG data structure and turns it into actual
graphics operations)
Graphics Library - needs to provide the operations needed to render
SVG primitives
'Unfortunately', Skia does not support SVG rendering directly - it was
going to once it seems, however that aspect was abandoned long ago as
not being worthwhile. I'm guessing this is because full support of SVG
is far outside the realms of a (low-level) graphics (rasterization /
abstraction) library. Instead Skia provides the underlying services for
rasterizing SVG (e.g. the ability to render paths with arbitrary
transforms with various different kinds of paint, process things with
various kinds of filter etc.).
Now, obviously we only use Skia at a very low-level - it is strictly
'hidden' behind our well-defined LibGraphics API (so that, if necessary,
we can change the rasterization engine at some point, or abstract it to
do various other 'neat' things - like 'export snapshot as SVG'). So, the
first part of the work I've done is to add an MCGSvgRef abstraction to
libgraphics. This neatly wraps up the top three things in the above list
- it loads and parses an SVG XML document and then converts it to an
internal form which can be rendered (note that the entire focus here is
rendering - not editing or introspection on the SVG Document). There is
then a simple call to render such a thing (which basically iterates over
the internal form and calls appropriate LibGraphics APIs to do so). (I
should point out that I didn't write the main piece of code which does
the Importing / Processing - that would be
https://github.com/memononen/nanosvg - I wrapped that up in a nice
package and hooked it up to LibGraphics). The C API is actually this
(for those that are interested):
bool MCGSvgCreate(const void *p_data, size_t p_data_size, MCGSvgRef&
r_svg)
MCGSvgRef MCGSvgRetain(MCGSvgRef self)
void MCGSvgRelease(MCGSvgRef self)
bool MCGSvgIsValid(MCGSvgRef self)
MCGRectangle MCGSvgGetViewBox(MCGSvgRef self)
MCGRectangle MCGSvgGetBoundingBox(MCGSvgRef self)
void MCGSvgRender(MCGSvgRef self, MCGContextRef target)
With this nice simple abstraction in place (which is now generally
available as part of the API exposed by LibGraphics to anything in the
engine that might want to use it), I then wrapped it in LCB syntax for
use by widgets. Indeed, it did not need very much syntax:
svg from file <filename>
svg from resource file <filename>
svg from string <xmlstring>
the bounding box of <svg object>
the viewing box of <svg object>
draw [ from <srcRect> of ] <svg object> into <dstRect> of <canvas>
So, at this point, any part of the engine can use the C-based API
mentioned above, and any LCB code can use the 'nice' LCB syntax above.
--
The final piece to the current piece of work is a very simple (it is
around 150 lines of LCB code, including GPL header comments) 'SVG View'
widget which allows you to give it SVG as an XML string, and then tell
it what portion to render into the widgets rect. (Here is the actual
source -
https://github.com/runrevmark/livecode/blob/lcb-canvas_svg/extensions/widgets/svgview/svgview.lcb)
--
Now with that all out of the way, let me see if I can answer your direct
questions...
Q: does it make sense to have two "realms" of graphics? There are card
based (native) graphics, and then there are the graphics inside widgets.
A: There aren't 'two realms of graphics' - what you are seeing is the
graphics 'stack' at different levels. By 'card based graphics' I presume
you mean the 'graphic' object. This is a 'widget' which allows you to
display geometric shapes on a card. It sits on MCGraphicsContext
internally which sits on LibGraphics. Similarly, any widget sits on
Canvas syntax which sits on LibGraphics. Due to the graphics object
current implementation atop MCGraphicsContext it has a number of flaws
which are not fixable without (essentially) rewriting it...
Q: I recall reading that all of the graphics rendering in LC was going
to be moved over to the skia graphics library. Is this what enables the
display of SVG in widgets or is graphics rendering in widgets based on
something else?
A: All graphics rendering in LC has been using Skia since around 6.5.
Existing C++ engine controls do so through the MCGraphicsContext wrapper
around LibGraphics (which actually sits on Skia). SVG rendering support
has been added at the LibGraphics level (using a third-party SVG parser
/ processor), which has then been exposed to the LCB Canvas syntax for
widgets to use.
Q: What prevents the display of SVG graphics outside of a widget?
A: Nothing - the SVG support is available at the LibGraphics level and
so any part of the (C++) engine could use it. Really this question
exposes a slight misunderstanding about widgets. Widgets are no
different from the other engine controls - they are just written in LCB
rather than C++ which means they are a great deal easier and faster to
write *and* most importantly, a great many more people will be able to
write them. (Thus providing, over time, a great many more 'engine'
features for everybody).
Q: As it stands, there are graphics capabilities within widgets that
don't apply to native graphics. Would it not make more sense to have a
single universal approach to all graphics in LC?
A: Internally and from an implementation perspective (whether it be
available at the C++ or LCB level) there is a universal approach to
graphics in LiveCode. However, I think what you are asking here is 'why
does there appear to be more graphics facilities available at the Canvas
API level than we can see through the current graphic object'... The
answer here is simple - because we have not yet rewritten the graphic
object. The current behavior of the graphic object is such that trying
to 'move it forward' into the modern era is, essentially, pointless - it
would have to be done with a big hefty flag saying 'use new behavior'
(as otherwise all existing stacks using it would not work the same as
they did before), and that is equivalent to just rewriting the object
from scratch. This is the much prophesized 'shape' object. Which we will
write in LCB (and, indeed, will actually just be quite a simple
'wrapper' around the Canvas syntax in the first instance at least).
Warmest Regards,
Mark.
--
Mark Waddingham ~ mark at livecode.com ~ http://www.livecode.com/
LiveCode: Everyone can create apps
More information about the use-livecode
mailing list