[Pharo-dev] [ bloc ] I do not understand why some behavior is not in the right place

Igor Stasenko siguctua at gmail.com
Tue Apr 5 10:12:20 EDT 2016

On 5 April 2016 at 16:51, Aliaksei Syrel <alex.syrel at gmail.com> 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 <alex.syrel at gmail.com>
> 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 <alex.syrel at gmail.com>
>> 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 <siguctua at gmail.com>
>>> 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 <siguctua at gmail.com> wrote:
>>>>> On 5 April 2016 at 04:00, Ben Coman <btc at openinworld.com> wrote:
>>>>>> On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua at gmail.com>
>>>>>> 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.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.pharo.org/pipermail/pharo-dev_lists.pharo.org/attachments/20160405/5d25223e/attachment.html>

More information about the Pharo-dev mailing list