[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:27:12 EDT 2016


On 5 April 2016 at 16:29, 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.
>
> 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?


>
> 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/2099a843/attachment.html>


More information about the Pharo-dev mailing list