I started working on Athens by picking up a previous work by Cyrille (if i'm not mistaken). And his work also predated by Rome plugin , that was done by (and here my memory fails me)..
Originally it was looked as a simple wrapper of Cairo library, reflecting its design and API straightly and sharply. We had a discussion at that time about this and picked a direction. The reason why we don't want it to be just a Cairo wrapper is obvious. (if not, ask, i will list them here). The heaviest influence to Athens design comes from OpenVG standard. I like it, because it describes all things quite clear and with much detail. On 6 April 2016 at 11:07, Igor Stasenko <[email protected]> wrote: > > > On 6 April 2016 at 10:35, Nicolai Hess <[email protected]> wrote: > >> >> >> 2016-04-05 16:31 GMT+02:00 Igor Stasenko <[email protected]>: >> >>> >>> >>> On 5 April 2016 at 17:27, Igor Stasenko <[email protected]> wrote: >>> >>>> >>>> >>>> On 5 April 2016 at 16:29, 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. >>>>> >>>>> Yes, i understand that. You are forced to do that. And it is ugly not >>>> because of all you listed above, it ugly because you could just use a >>>> cascade: >>>> >>>> aCanvas setPaint: self shape fillPaint; >>>> fillPreserve; >>>> paintMode source; >>>> setStrokePaint: self shape strokePaint; >>>> stroke >>>> >>>> (something like that) >>>> but yeah.. that can wait .. since it is still work in progress. I agree. >>>> >>>> >>>>> 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? >>>>> >>>>> >>>> A good question. And i don't have an answer to it , ready for you. >>>> 1. Stroke can be expressed as a special kind of paint. And that how its >>>> done in Athens. >>>> >>>> 2. What fillPreserve is, is can't find it in source code? Some >>>> hot-swapping and preserving context state , i guess. >>>> >>>> 3. As for paint mode, it is already in Athens, so why you asking? You >>>> don't like how it is done or what? >>>> >>>> >>> or maybe you meant how to group those operation and express them as >>> command group? >>> I have no simple answer here. Because this is root points of the core of >>> graphics engine. From one side, you want such things be exposed to user, >>> and from other you want a higher dimension concepts/operations to be >>> allowed by combining those. >>> There's no simple way. I would just stop at this level, letting user to >>> decide how he wants to play with those pieces to achieve results he wants. >>> >> >> >> Grouping and Context save/restore are good additions - I think. >> >> But it is true, that we should care about the api of Athens and not just >> add things that happens to be possible, because we use cairo as a backend. >> >> I made some fixes for AthensBalloon (not all are integrated yet, some >> parts are just experimental and needs more tests), the idea is to have >> AthensBalloon at least not crash or throwing errors, even if not all >> features are supported. >> > > Much, much, much appreciated. The whole point of existence of Balloon > backend for Athens was to use it as a proving ground that Athens can stay > backend neutral, and its API allows to stay it like that. > From that perspective, any feature that offered by any backend should find > its way via API, but not thrown into play just because we can. > As i mentioned before, if we would be making Cairo wrapper, then there no > reason to call it Athens. It could be something like 'CairoPharo'. > > And i kept mentioned over and over again on all presentations related to > Athens, that it is not Cairo. > > But at the moment, no one cares about non-cairo-athens. All users just >> directly use AthensCairoCanvas/AthensCairoSurface. >> I had a bug report for discussion about how to make some kind of factory >> that would create the appropriate Athens backend. >> >> Is there still some interest on AthensBalloon or to make athens more >> independent from cairo? >> >> >> > > It always been. But didn't have much time to make that happen. > For instance, i dream to make an OpenGL backend for Athens.. but i had no > chance to put my hands on that topic so far. > > >> >>> >>> >>>> >>>>> 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. >>>> >>> >>> >>> >>> -- >>> Best regards, >>> Igor Stasenko. >>> >> >> > > > -- > Best regards, > Igor Stasenko. > -- Best regards, Igor Stasenko.
