On 5 April 2016 at 16:51, Aliaksei Syrel <[email protected]> wrote:
> Let me now clarify terminology behind path / shape (with fill and stroke). > > We decided to not invent a wheel and rely on our knowledge of english. > Instead it is better to be in sync with terminology used among graphics and > vector designers. > > As an example let's stick with Adobe's naming conventions (Photoshop, > After Effects and Illustrator are widely used). [1] > > Yeah, who are me, and who Adobe :) > *Path* > >> A path consists of *segments* and *vertices*. Segments are the lines or >> curves that connect vertices. Vertices define where each segment of a path >> starts and ends. > > > A path itself has no visual appearance in rendered output; it is >> essentially a collection of information about how to place or modify other >> visual elements. > > > Right, but as you can see, that is just a specific way how you can define a shape (shape in my terms). > *Shape* > >> [ ... ] layers contain vector graphics objects called *shapes*. By >> default, a shape consists of a path, a stroke, and a fill. > > > You can modify a shape path by applying *path operations*, such as Wiggle >> Paths and Pucker & Bloat. You apply a stroke to a path or fill the area >> defined by a path with color by applying *paint operations*. > > > Maybe an idea of having BlShape that holds path, fill and stroke is not > that bad as you thought? Anyway we found even better way. > > Sure, but now, maybe you can see, why my definition of shape is more generic. It doesn't says it can only be filled or stoked. It is orthogonal. I take the definition of shape in its purest form: anything that can have some form, designating location(s) on surface. It even more generic than Adobe's Path, since path is just a single case of it. >From that perspective, yes your BlShape conforms to Adobe's shape, but also inherits its limitations. > P.S. There is always a *reason* behind our decision. I am not the only > person that made that decision. it was discussed multiple times. > > Of course. But if we started talking about authorities, i don't think if you ask a person on the street, what shape is, his answer will start from 'Adobe defines it as .... ' :) > Cheers, > Alex > > [1] > https://helpx.adobe.com/after-effects/using/overview-shape-layers-paths-vector.html > <https://helpx.adobe.com/after-effects/using/overview-shape-layers-paths-vector.html> > > On Tue, Apr 5, 2016 at 3:29 PM, Aliaksei Syrel <[email protected]> > wrote: > >> Now let's take a look at this code: >> >> drawOnSpartaCanvas: aCanvas >> >>> aCanvas >>> clipPreserveBy: self shape during: [ >>> aCanvas paintGroup: [ >>> aCanvas setPaint: self shape fillPaint. >>> aCanvas fillPreserve. >>> aCanvas paintMode source. >>> aCanvas setStrokePaint: self shape strokePaint. >>> aCanvas stroke ] ] >> >> >> You may be curious why it is so ugly :) Make it work - make it right - >> make it fast. We are on the first etappe, because I invested zero time in >> rendering stuff. >> >> What you see is the minimal amount of cairo primitive calls that are >> needed to render not overlapping fill and stroke. Clipping is needed to >> make sure that stroke does not get rendered outside of a path. Group is >> needed to have transparent target in order to make source paint mode work >> as expected. Compared to image_surface group, it in this case allows to >> preserve clip and current cairo state which is pushed to stack during >> push_group and restored during pop_group_to_source. fillPreserve allows to >> reuse the same path as used for clipping before saving cpu time on loading >> path. >> >> It is implemented in canvas specific method after dispatch though canvas, >> so we are allowed to use canvas specific api, for example groups. >> >> How to model stroke, fillPreserve and paintModein terms of Athens? >> >> >> Cheers, >> Alex >> >> On Tue, Apr 5, 2016 at 3:15 PM, Aliaksei Syrel <[email protected]> >> wrote: >> >>> Hello Igor >>> >>> Thanks for extensive design explanation and effort! >>> Issues you mentioned in previous emails are important and need to be >>> addressed :) >>> fill(), stroke() fillPreserve() strokePreserve() need to disappear in >>> the end. We will come back to them later. >>> >>> Let me tell a few words about Sparta. >>> Sparta implements Athens interface api (excluding some experimental >>> stuff to test possible performance boost in a few places) and does not have >>> task to remove Athens style and abstractions. Ideally Sparta will be >>> AthensCairo for bloc. I'm looking forward for your help :) >>> >>> Here are some aspects in AthensCairo that Sparta tries to address in >>> first place: >>> >>> - *Clipping in local coordinates*. It is critical in Bloc. You >>> implemented AthensCairo to have vector based rendering in Morphic and >>> Pharo >>> in general. Morphic lives in global coordinates, so your choice to clip >>> in >>> global coordinate is perfect! At the same time global clipping in bloc >>> adds >>> complexity. Sparta clips always in local coordinates (user space in cairo >>> terminology). >>> - *Clip by arbitrary path*. Athens and AthenCairo expect to see >>> aRectangle as clipping region - your wise choice for morphic. In bloc I >>> would have clipping by arbitrary path. clipBy:during: gets aPath. >>> Rectangle/Color is polymorphic with path/paint in Sparta >>> - *Support of groups*. (maybe user-level aspect? like shadows) >>> Groups are powerful in cairo (do they exist outside of cairo?) and allow >>> to >>> draw both transparent fill and stroke without overlapping using only one >>> path. On class side of BlElement there are examples (exampleCircle) that >>> show such behavior. >>> - *Do not maintain and set pathTransformation before each >>> render-dependent action.* Questionable but what if Canvas will not >>> maintain current state of pathTransform? Instead all transformations can >>> be >>> directly applied on cairo_t using native calls. If there is a need to get >>> actual matrix we can ask cairo directly. From my perspective it >>> simplifies >>> transformation stuff a little bit. >>> - *Benefit from cairo_save and cairo_restore.* AthensCairo maintains >>> state manually by setting transformation matrix and clip. Instead we >>> could >>> save and restore state without caring about clip/matrix which simplifies >>> code. Check SpartaCanvas>>#clipBy:during: >>> >>> >>> Cheers, >>> Alex >>> >>> On Tue, Apr 5, 2016 at 2:12 PM, Igor Stasenko <[email protected]> >>> wrote: >>> >>>> >>>> Couple more words about that fill() function abstraction. >>>> Now you probably understand why there's no notion of stroke operation >>>> in Athens. >>>> Because instead of introducing it that way, by adding new kind of a >>>> function >>>> stroke(shape,paint) >>>> from our perspective, it falls into our more generic fill() function, >>>> except that >>>> instead of literally filling the shape we deciding to paint a stroke: >>>> fill(shape, strokePaint). >>>> >>>> As i said, there's nothing that tells that fill() function must affect >>>> only areas enclosed by the shape. >>>> For instance, you could imagine, that i'm in contrary, may want to fill >>>> everything , but the area(s) enclosed by given shape. And that still can be >>>> represented as invocation of our generic fill() function, except that we >>>> will use a different kind of paint, that will fill everything outside >>>> designated region, i.e.: >>>> fill(shape, fillOutsidePaint) >>>> >>>> >>>> >>>> On 5 April 2016 at 14:33, Igor Stasenko <[email protected]> wrote: >>>> >>>>> >>>>> >>>>> On 5 April 2016 at 04:00, Ben Coman <[email protected]> wrote: >>>>> >>>>>> On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <[email protected]> >>>>>> wrote: >>>>>> > >>>>>> > Some more bashing today.. (don't take it personal, i may be wrong) >>>>>> > >>>>>> > BlPath hierarchy.. and BlShape. >>>>>> > >>>>>> > Why you redefining what is shape and what is path? >>>>>> > Of course, you are free to do it in Bloc.. >>>>>> > But in terms of Athens, all of BlPath are actually - shapes.. >>>>>> > And BlShape is some kind of encapsulation of shape, paints and >>>>>> transform. >>>>>> > It is a dumb state holder without any extra logic. >>>>>> > >>>>>> > My rule of thumb: do not produce dumb state holders. They has to be >>>>>> smart, >>>>>> > else it makes no sense in creating separate entity and designating >>>>>> it as >>>>>> > something else than any other bunch of data thrown into single >>>>>> clump, >>>>>> > sitting there deaf, blind, dead and silent until someone else will >>>>>> grab it >>>>>> > somewhere >>>>>> > and start using it for own purpose. >>>>>> > >>>>>> > Sure, i could understand, why you potentially may want such >>>>>> object(s) >>>>>> > around, >>>>>> > but it is not shape anymore and i wouldn't call it like that. >>>>>> Because shape >>>>>> > are shape, and has nothing to do with paints and transform, >>>>>> > it don't knows and don't cares whether it will be filled or stroked >>>>>> or both, >>>>>> > and how many times, and if there will be single paint or thousand. >>>>>> > Such kind of properties is simply orthogonal to what shape existing >>>>>> for, >>>>>> > because it exists only to define geometry. >>>>>> > >>>>>> > I think all of that came from not understanding the roles of >>>>>> objects and how >>>>>> > they interact in Athens. >>>>>> >>>>>> Can you point us to documentation that describes Athen's architecture >>>>>> for these interactions? >>>>>> (sorry I haven't checked class comments, but I'm looking to start with >>>>>> something at higher level anyway) >>>>>> >>>>> >>>>> No, i can't point it out. And you are right , this is nobody else's >>>>> fault than my own. I feel ashamed. Sure how i could demand that people >>>>> understand the concepts, if i didn't explained then anywhere (or if i did, >>>>> it is not in easily reachable place). >>>>> >>>>> So, lets fix that. I will write it down here, and you can pick it up >>>>> and find suitable place for it. >>>>> >>>>> ---------- >>>>> Basic abstractions behind Athens. >>>>> >>>>> Since Athens is about drawing graphics, we need a media where all >>>>> drawing operations will appear. We call that media a surface. >>>>> The surface is abstract. It can have set dimensions, or don't. We >>>>> don't define if it representing some kind of physical surface (like part >>>>> of >>>>> the display screen), or how it storing the data inside. We leaving an >>>>> introduction of such details to concrete surface implementation. >>>>> All that matters is that surface is a final target of all our drawing >>>>> operations. >>>>> Therefore, in Athens, a surface is usually a starting point where all >>>>> begins from, and you doing so by creating a specific surface. >>>>> It is surface's responsibility then, to provide user a means how he >>>>> can draw on it, and therefore there is a number of factory methods, that >>>>> allowing you to create a canvas, paints and shapes. All those three are >>>>> specific implementation of AthensCanvas, AthensPaint and AthensShape >>>>> protocols, suitable to be used with specific surface implementation that >>>>> you using. >>>>> >>>>> Canvas. >>>>> Canvas represents a basic drawing context. We don't allow a direct >>>>> operations with surface, but instead we provide a context, that contains >>>>> and carries all information that represents a current stage of drawing >>>>> operations. >>>>> This includes things like, current coordinate transformation(s), >>>>> currently selected paint and shape, and paint mode. >>>>> >>>>> In order to obtain canvas, one must use #drawDuring: message sent to >>>>> surface with block as argument. The given block receives an instance of >>>>> AthensCanvas as a single parameter. We intentionally enclosing all >>>>> possible >>>>> drawing operations within a block to make sure that when we leave, we can >>>>> safely release all resources that was allocated, required to hold the >>>>> drawing context state. By exposing it in such form, we also making sure >>>>> that nothing can alter the surface outside a given block. That way, it >>>>> gives users a definitive answer, whether he finished drawing operations or >>>>> not, and if it safe to operate with surface for things like saving it to >>>>> file, or using it as a source for more complex operations, like acting as >>>>> a >>>>> paint to fill area(s) inside another surface etc. >>>>> >>>>> Paints and shapes. >>>>> A starting point is answering a question, how we can represent a >>>>> simplest, elementary drawing operation on a surface without putting too >>>>> much constraints. >>>>> We doing so by postulating that any elementary drawing operation can >>>>> be expressed by a function: >>>>> >>>>> fill(paint, shape) >>>>> >>>>> Please, note that 'fill' here is not a literally fill given shape with >>>>> given paint. We call it 'fill' for simplicity reason. It can anything that >>>>> altering the surface, but always taking into account given parameters: >>>>> paint and shape. >>>>> >>>>> Then, from that perspective we can clearly say what are the roles and >>>>> responsibility of shapes and paints. >>>>> >>>>> The shape defines the affected region, its geometry and location, >>>>> while paint defines how that region will be altered. >>>>> In this way, most of more complex operations can be expressed as a >>>>> series of such function invocations by using various paints and shapes. >>>>> >>>>> Such representation also gives us a minimal set of roles, a building >>>>> bricks, that we need to introduce in order to represent any kind of >>>>> drawing >>>>> operation we may need, as well as a minimal functionality in order to >>>>> implement such function(s). And therefore a minimal protocol(s), that all >>>>> paints and shapes should implement. >>>>> >>>>> Since there potentially infinite number of various paint kinds and >>>>> shape kinds, we cannot make a single function that will implement all >>>>> possible permutations in order to fill shape with concrete paint. >>>>> To solve that we introducing a straight dispatch mechanism, where we >>>>> delegate the responsibility of implementing a concrete case, first to >>>>> shape, and then to paint. >>>>> >>>>> The API representing this function in canvas by #draw protocol. >>>>> It takes currently selected paint and currently selected shape and >>>>> starting dispatch: >>>>> >>>>> draw >>>>> "Fill the currently selected shape with currently selected paint" >>>>> ^ shape paintFillsUsing: paint on: self >>>>> >>>>> So, first it goes to the shape, by sending #paintFillsUsing:on: , >>>>> then shape dispatching it further to paint by sending appropriate >>>>> message >>>>> (be it #athensFillPath:on: or #athensFillRectangle:on: or anything >>>>> else, if you want to introduce new kind of shape representation and >>>>> implement it accordingly). >>>>> Such dispatch gives us an ability to easily extend the framework by >>>>> introducing new kind of shapes and paints , by implementing new kind of >>>>> fill() functions for them. >>>>> >>>>> ----------- >>>>> >>>>> I hope that will make clear at least part of things what is there, >>>>> behind the scenes. >>>>> >>>>> >>>>>> cheers -ben >>>>>> >>>>>> >>>>> >>>>> -- >>>>> Best regards, >>>>> Igor Stasenko. >>>>> >>>> >>>> >>>> >>>> -- >>>> Best regards, >>>> Igor Stasenko. >>>> >>> >>> >> > -- Best regards, Igor Stasenko.
