[ bloc ] I do not understand why some behavior is not in the right place
BlElement >>drawStrokeOnAthensCanvas: aCanvas "Actually render stroke of a receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil" | pathTransform strokePath strokeScaling strokeOffset | "fast exit if stroke has no width or it is transparent" (self shape strokePaint width <= 0 or: [ self shape strokePaint isTransparent ]) ifTrue: [ ^ self ]. pathTransform := aCanvas pathTransform. strokePath := self shape path strokePathOn: aCanvas. strokeScaling := self shape path strokeScaling. strokeOffset := self shape path strokeOffset. pathTransform restoreAfter:[ pathTransform translateBy: strokeOffset * strokeScaling. pathTransform scaleBy: strokeScaling. aCanvas setPaint: self shape strokePaint; drawShape: strokePath ] BlElement >>drawFillOnAthensCanvas: aCanvas "Actually render fill of a receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil" | pathTransform fillPath fillScaling fillOffset | pathTransform := aCanvas pathTransform. "fast exit if fill is transparent" (self shape fillPaint isTransparent) ifTrue: [ ^ self ]. fillPath := self shape path fillPathOn: aCanvas. fillScaling := self shape path fillScaling. fillOffset := self shape path fillOffset. pathTransform restoreAfter: [ pathTransform translateBy: fillOffset * fillScaling. pathTransform scaleBy: fillScaling. aCanvas setPaint: self shape fillPaint; drawShape: fillPath ]
Hi Stef, Which build/version are you using? These methods are removed in the latest version. You can download the latest version from the Moose CI. From the bloc project you need the bleeding edge build. The link to this version is here: https://ci.inria.fr/moose/job/bloc/PHARO=alpha,VERSION=bleedingEdge,VM=vmLat... Cheers, Andrei On Mon, Apr 4, 2016 at 5:17 PM, stepharo <stepharo@free.fr> wrote:
BlElement >>drawStrokeOnAthensCanvas: aCanvas "Actually render stroke of a receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil" | pathTransform strokePath strokeScaling strokeOffset |
"fast exit if stroke has no width or it is transparent" (self shape strokePaint width <= 0 or: [ self shape strokePaint isTransparent ]) ifTrue: [ ^ self ].
pathTransform := aCanvas pathTransform. strokePath := self shape path strokePathOn: aCanvas. strokeScaling := self shape path strokeScaling. strokeOffset := self shape path strokeOffset.
pathTransform restoreAfter:[ pathTransform translateBy: strokeOffset * strokeScaling. pathTransform scaleBy: strokeScaling. aCanvas setPaint: self shape strokePaint; drawShape: strokePath ]
BlElement >>drawFillOnAthensCanvas: aCanvas "Actually render fill of a receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil" | pathTransform fillPath fillScaling fillOffset | pathTransform := aCanvas pathTransform.
"fast exit if fill is transparent" (self shape fillPaint isTransparent) ifTrue: [ ^ self ].
fillPath := self shape path fillPathOn: aCanvas. fillScaling := self shape path fillScaling. fillOffset := self shape path fillOffset.
pathTransform restoreAfter: [ pathTransform translateBy: fillOffset * fillScaling. pathTransform scaleBy: fillScaling. aCanvas setPaint: self shape fillPaint; drawShape: fillPath ]
What is it? Why people so obsessed to overcomplicate everything? How drawFillOnAthensCanvas would look like, if you do things right? BlElement >>drawOnAthensCanvas: aCanvas ^ aCanvas draw: self shape. pfff.... On 4 April 2016 at 18:17, stepharo <stepharo@free.fr> wrote:
BlElement >>drawStrokeOnAthensCanvas: aCanvas "Actually render stroke of a receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil" | pathTransform strokePath strokeScaling strokeOffset |
"fast exit if stroke has no width or it is transparent" (self shape strokePaint width <= 0 or: [ self shape strokePaint isTransparent ]) ifTrue: [ ^ self ].
pathTransform := aCanvas pathTransform. strokePath := self shape path strokePathOn: aCanvas. strokeScaling := self shape path strokeScaling. strokeOffset := self shape path strokeOffset.
pathTransform restoreAfter:[ pathTransform translateBy: strokeOffset * strokeScaling. pathTransform scaleBy: strokeScaling. aCanvas setPaint: self shape strokePaint; drawShape: strokePath ]
BlElement >>drawFillOnAthensCanvas: aCanvas "Actually render fill of a receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil" | pathTransform fillPath fillScaling fillOffset | pathTransform := aCanvas pathTransform.
"fast exit if fill is transparent" (self shape fillPaint isTransparent) ifTrue: [ ^ self ].
fillPath := self shape path fillPathOn: aCanvas. fillScaling := self shape path fillScaling. fillOffset := self shape path fillOffset.
pathTransform restoreAfter: [ pathTransform translateBy: fillOffset * fillScaling. pathTransform scaleBy: fillScaling. aCanvas setPaint: self shape fillPaint; drawShape: fillPath ]
-- Best regards, Igor Stasenko.
Hi, Please take the latest version :). Cheers, Doru
On Apr 4, 2016, at 8:28 AM, Igor Stasenko <siguctua@gmail.com> wrote:
What is it?
Why people so obsessed to overcomplicate everything? How drawFillOnAthensCanvas would look like, if you do things right?
BlElement >>drawOnAthensCanvas: aCanvas ^ aCanvas draw: self shape.
pfff....
On 4 April 2016 at 18:17, stepharo <stepharo@free.fr> wrote: BlElement >>drawStrokeOnAthensCanvas: aCanvas "Actually render stroke of a receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil" | pathTransform strokePath strokeScaling strokeOffset |
"fast exit if stroke has no width or it is transparent" (self shape strokePaint width <= 0 or: [ self shape strokePaint isTransparent ]) ifTrue: [ ^ self ].
pathTransform := aCanvas pathTransform. strokePath := self shape path strokePathOn: aCanvas. strokeScaling := self shape path strokeScaling. strokeOffset := self shape path strokeOffset.
pathTransform restoreAfter:[ pathTransform translateBy: strokeOffset * strokeScaling. pathTransform scaleBy: strokeScaling. aCanvas setPaint: self shape strokePaint; drawShape: strokePath ]
BlElement >>drawFillOnAthensCanvas: aCanvas "Actually render fill of a receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil" | pathTransform fillPath fillScaling fillOffset | pathTransform := aCanvas pathTransform.
"fast exit if fill is transparent" (self shape fillPaint isTransparent) ifTrue: [ ^ self ].
fillPath := self shape path fillPathOn: aCanvas. fillScaling := self shape path fillScaling. fillOffset := self shape path fillOffset.
pathTransform restoreAfter: [ pathTransform translateBy: fillOffset * fillScaling. pathTransform scaleBy: fillScaling. aCanvas setPaint: self shape fillPaint; drawShape: fillPath ]
-- Best regards, Igor Stasenko.
-- www.tudorgirba.com www.feenk.com "To utilize feedback, you first have to acquire it."
I saw in bleedingEdge it is different and looks better. Now I was wondering why shape did not do the rendering and I thought that this is may be because a shape can be used for doing something else than rendering. For example clipping or as igor mentioned defining the shape of the interaction drawOnSpartaCanvas: aCanvas "Actually render receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil" self assert: self shape path context isNotNil. aCanvas clipPreserveBy: self shape during: [ aCanvas paintGroup: [ aCanvas setPaint: self shape fillPaint. aCanvas fillPreserve. aCanvas paintMode source. aCanvas setStrokePaint: self shape strokePaint. aCanvas stroke ] ] unprotectedFullDrawOnSpartaCanvas: aCanvas "Draw the full structure on the given Canvas withing drawing bounds without caring about any errors and visibility. I am responsible for clipping and transforming canvas's path to local coordinates to allow simpler actual drawing in drawOnAthensCanvas: Additional checks should be implemented in fullDrawOnAthensCanvas: aCanvas is an instance of AthensCanvas. aCanvas must not be nil. @see BlElement>>#fullDrawOnAthensCanvas: @see BlElement>>#drawOnAthensCanvas:" self shape adaptTo: self. self shape path context: aCanvas. aCanvas transform: self transformation during: [ self clippingStrategy clip: self on: aCanvas during: [ self drawOnSpartaCanvas: aCanvas ] childrenDuring: [ self drawChildrenOnSpartaCanvas: aCanvas ] ]
On 4 April 2016 at 19:00, stepharo <stepharo@free.fr> wrote:
I saw in bleedingEdge it is different and looks better. Now I was wondering why shape did not do the rendering and I thought that this is may be because a shape can be used for doing something else than rendering. For example clipping or as igor mentioned defining the shape of the interaction
drawOnSpartaCanvas: aCanvas "Actually render receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil"
self assert: self shape path context isNotNil.
aCanvas clipPreserveBy: self shape during: [ aCanvas paintGroup: [ aCanvas setPaint: self shape fillPaint. aCanvas fillPreserve. aCanvas paintMode source. aCanvas setStrokePaint: self shape strokePaint. aCanvas stroke ] ]
where i can download and look at this code?
unprotectedFullDrawOnSpartaCanvas: aCanvas "Draw the full structure on the given Canvas withing drawing bounds without caring about any errors and visibility. I am responsible for clipping and transforming canvas's path to local coordinates to allow simpler actual drawing in drawOnAthensCanvas: Additional checks should be implemented in fullDrawOnAthensCanvas: aCanvas is an instance of AthensCanvas. aCanvas must not be nil. @see BlElement>>#fullDrawOnAthensCanvas: @see BlElement>>#drawOnAthensCanvas:" self shape adaptTo: self. self shape path context: aCanvas.
aCanvas transform: self transformation during: [ self clippingStrategy clip: self on: aCanvas during: [ self drawOnSpartaCanvas: aCanvas ] childrenDuring: [ self drawChildrenOnSpartaCanvas: aCanvas ] ]
-- Best regards, Igor Stasenko.
Le 04/04/2016 18:56, Igor Stasenko a écrit :
where i can download and look at this code?
Hi Igor, Via Pharo Launcher under Moose Jenkins -> bloc. Or at https://ci.inria.fr/moose/job/bloc/
-- Best regards, Igor Stasenko.
-- Cyril Ferlicot http://www.synectique.eu 165 Avenue Bretagne Lille 59000 France
Nevermind, i found. Here some comments. Q. So, why you think i made Color to be polymorphic with Athens Paint protocol? A. Exactly to avoid producing extra entities like this: (BlColorPaint new color: (Color blue alpha: 0.5)) Because this BlColorPaint adds nothing , has no any extra information, no behavior.. Nothing. With Athens API any object can serve as paint, as long as it implements its protocol. What is rationale behind making BlPaint hierarchy? One more thing, i discovered at random chance: AthensSurface>>createShadowPaint: aColor "Answer an instance of AthensPaint, valid for use with given surface" What is this nonsense? I removed notion of shadow in old Canvas, just to see that someone introducing it in Athens? Okay, guys.. Do you realise that you polluting a base feature set with user-level feature. There is no 'shadow' as a concept in graphics framework. It is a user-level concept. Pff.. Shadow is thing you drawing on screen. It has nothing different from drawing anything else on screen from graphical engine perspective. Why you doing that ? You ripping me apart. BlShadowPaint. If you want to draw a shadow, don't call it paint. Just call it shadow. It has nothing to do with paint. I know it is convenient to put it that way and pretend that if you draw same shape with some offset and blur it slightly and use semitransparent color it will appear as a shadow. But it is not basic element or operation for any imaginable graphical engine. It is purely user-level feature, that should not pollute nor interfere with basic feature set. The main problem behind this 'shadows' in Canvas, that people were drawing full morph with 'shadow color' filter.. Now think you paint SystemWindow twice.. once for foreground, and second time for shadow.. with all its submorphs , etc, yadda yadda.. That's like shooting bacteria with earth-sized terrawatt laser. And you seem to like it.. and want to reintroduce.. In same horrible way as before. You have a shadow. Can i have a pony then? :) -- Best regards, Igor Stasenko.
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. Shapes define geometry. Paints define how that geometry will be filled/altered. The shapes and paints are related in a way that paints are used to fill shapes. But there's nothing tells that single shape can be filled only once with prescribed paint, and only then stroked.. or vise versa. The order and amount of operations, combinations and effects are up to the user. And they have to stay like so, if you don't intend to shrink feature set and hardwire, what users can do and what they cannot. I really hoping that you had no such intent. Be on your place, if you want to have a number of commonly used shapes, just make a factory class, so i can tell: CommonShapes rectangle: a@b to: c@d except, that Rectangle is a shape already.. and wrapping it with own class just adds more complexity, creating one extra beloved dumb state holder and nothing else. But i would imagine i could have: CommonShapes circle: 100@100 or CommonShapes roundedRectangle: 100@100 radius: 5 in any case, since there could be infinite number of 'possibly useful objects' when we talking about geometry.. your BlPath hierarchy will tend to grow and grow over time, ad infinity. Well, people love when there's order, like in that comic where guy painted 'cat' label on his cat, 'car' label on his car.. and all around in same way. Can't blame for that.. So, maybe. Whatever.. But your examples demonstating insane amount of bloat.. For example: exampleLine self new layoutStrategy: BlLinearLayout horizontal; layoutParamsDo: [ :lp | lp horizontal wrapContent. lp vertical wrapContent ]; addChild:(self new layoutParams: BlLinearLayoutParams new; shape: (BlShape new fillPaint: (BlColorPaint new color: (Color blue alpha: 0.5)); strokePaint: (BlStrokePaint new paint: (BlColorPaint new color: (Color green alpha: 1)); lineStyle: BlStrokeLineStyle new; width: 5); path: (BlLinePath new)); extent: 400 @ 200); openInWorld Guys, are you serious, you will find happy users, that is forced to produce so much code in order to draw a single, simple line on the screen? You must be kidding. I see that it is indeed comes from the requirement that BlElement can hot-swap its shape on the fly.. And so, you are forced to define all the properties and construct all the things in place. But there should be some sanity limit, where you should stop asking user for so much details. But if you want to go to such extreme, then all your examples would look like that: BlElement new content: (MyFancyPony new); openInWorld. Where MyFancyPony, is a content - an object that defines shape, and knows how to render itself and so on. A smart object, that knows how to behave and draw itself. Not just holding state. And number of properties and/or behavior is then up to the user. And he is free to use anything fancy or not to deliver content on the screen. That would be much better. But then it will duplicate the features of BlElement (or at least a significant subset of it) - being able to render itself. But still. P.S. Now i see BlElement.. why nobody said that it is replacement for Morph? I was thinking that in addition to BlMorph, you have BlElement :) Sure, you don't need two compositions. As long as there one thing that represent UI elements, that can form hierarchy its ok. So, i retract my comments about Elements.. And apologize. -- Best regards, Igor Stasenko.
Hi igor I like your feedback. Could you summarize it based on the latest alpha version and now that you saw the code? In the bloc version you discussed with alain (in the house I was renovating) an element had a view that were responsibe of the rendering but it was not good. Stef Le 4/4/16 20:51, Igor Stasenko a écrit :
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.
Shapes define geometry. Paints define how that geometry will be filled/altered. The shapes and paints are related in a way that paints are used to fill shapes. But there's nothing tells that single shape can be filled only once with prescribed paint, and only then stroked.. or vise versa. The order and amount of operations, combinations and effects are up to the user. And they have to stay like so, if you don't intend to shrink feature set and hardwire, what users can do and what they cannot. I really hoping that you had no such intent.
Be on your place, if you want to have a number of commonly used shapes, just make a factory class, so i can tell:
CommonShapes rectangle: a@b to: c@d
except, that Rectangle is a shape already.. and wrapping it with own class just adds more complexity, creating one extra beloved dumb state holder and nothing else.
But i would imagine i could have:
CommonShapes circle: 100@100 or CommonShapes roundedRectangle: 100@100 radius: 5
in any case, since there could be infinite number of 'possibly useful objects' when we talking about geometry.. your BlPath hierarchy will tend to grow and grow over time, ad infinity. Well, people love when there's order, like in that comic where guy painted 'cat' label on his cat, 'car' label on his car.. and all around in same way. Can't blame for that.. So, maybe. Whatever..
But your examples demonstating insane amount of bloat..
For example:
exampleLine self new layoutStrategy: BlLinearLayout horizontal; layoutParamsDo: [ :lp | lp horizontal wrapContent. lp vertical wrapContent ]; addChild:(self new layoutParams: BlLinearLayoutParams new; shape: (BlShape new fillPaint: (BlColorPaint new color: (Color blue alpha: 0.5)); strokePaint: (BlStrokePaint new paint: (BlColorPaint new color: (Color green alpha: 1)); lineStyle: BlStrokeLineStyle new; width: 5); path: (BlLinePath new)); extent: 400 @ 200); openInWorld
Guys, are you serious, you will find happy users, that is forced to produce so much code in order to draw a single, simple line on the screen? You must be kidding.
I see that it is indeed comes from the requirement that BlElement can hot-swap its shape on the fly.. And so, you are forced to define all the properties and construct all the things in place. But there should be some sanity limit, where you should stop asking user for so much details.
But if you want to go to such extreme, then all your examples would look like that:
BlElement new content: (MyFancyPony new); openInWorld.
Where MyFancyPony, is a content - an object that defines shape, and knows how to render itself and so on. A smart object, that knows how to behave and draw itself. Not just holding state. And number of properties and/or behavior is then up to the user. And he is free to use anything fancy or not to deliver content on the screen.
That would be much better. But then it will duplicate the features of BlElement (or at least a significant subset of it) - being able to render itself. But still.
P.S. Now i see BlElement.. why nobody said that it is replacement for Morph? I was thinking that in addition to BlMorph, you have BlElement :) Sure, you don't need two compositions. As long as there one thing that represent UI elements, that can form hierarchy its ok. So, i retract my comments about Elements.. And apologize.
-- Best regards, Igor Stasenko.
On 4 April 2016 at 23:21, stepharo <stepharo@free.fr> wrote:
Hi igor
I like your feedback. Could you summarize it based on the latest alpha version and now that you saw the code?
My biggest source of concern, when i look at it is attempt to funnel features of Athens to obfuscate it down to the point that only simple, prescribed and rigid constructions will be possible. Athens is not perfect. As nothing is perfect. More than that: it proposing a quite limited subset of features comparing to other modern graphical engines. That's why i really surprised to see how people want to 'simplify' it even more, down to simplest possible constructs and throwing away the plethora of other possibilities. What was the point to make all those features, if you don't want to expose them to user? Or exposing them in a way, that most of them become hardly reachable. You cannot have a richer feature set than base engine provides, especially by shrinking that base.
In the bloc version you discussed with alain (in the house I was renovating) an element had a view that were responsibe of the rendering but it was not good.
I don't remember. The only view as a concept i can think of, we were discussing was a so-called 'viewport'. Is like defining a physical area on screen which are controlled by GUI, while other areas can be occupied by something else (be it other GUI kind, or 3D application etc). But in usual desktop, desktop covers whole screen and there seems like no huge outcry 'we need special views', so i would put it into 'good to have but not absolutely necessary' box.
Stef
Le 4/4/16 20:51, Igor Stasenko a écrit :
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.
Shapes define geometry. Paints define how that geometry will be filled/altered. The shapes and paints are related in a way that paints are used to fill shapes. But there's nothing tells that single shape can be filled only once with prescribed paint, and only then stroked.. or vise versa. The order and amount of operations, combinations and effects are up to the user. And they have to stay like so, if you don't intend to shrink feature set and hardwire, what users can do and what they cannot. I really hoping that you had no such intent.
Be on your place, if you want to have a number of commonly used shapes, just make a factory class, so i can tell:
CommonShapes rectangle: a@b to: c@d
except, that Rectangle is a shape already.. and wrapping it with own class just adds more complexity, creating one extra beloved dumb state holder and nothing else.
But i would imagine i could have:
CommonShapes circle: 100@100 or CommonShapes roundedRectangle: 100@100 radius: 5
in any case, since there could be infinite number of 'possibly useful objects' when we talking about geometry.. your BlPath hierarchy will tend to grow and grow over time, ad infinity. Well, people love when there's order, like in that comic where guy painted 'cat' label on his cat, 'car' label on his car.. and all around in same way. Can't blame for that.. So, maybe. Whatever..
But your examples demonstating insane amount of bloat..
For example:
exampleLine self new layoutStrategy: BlLinearLayout horizontal; layoutParamsDo: [ :lp | lp horizontal wrapContent. lp vertical wrapContent ]; addChild:(self new layoutParams: BlLinearLayoutParams new; shape: (BlShape new fillPaint: (BlColorPaint new color: (Color blue alpha: 0.5)); strokePaint: (BlStrokePaint new paint: (BlColorPaint new color: (Color green alpha: 1)); lineStyle: BlStrokeLineStyle new; width: 5); path: (BlLinePath new)); extent: 400 @ 200); openInWorld
Guys, are you serious, you will find happy users, that is forced to produce so much code in order to draw a single, simple line on the screen? You must be kidding.
I see that it is indeed comes from the requirement that BlElement can hot-swap its shape on the fly.. And so, you are forced to define all the properties and construct all the things in place. But there should be some sanity limit, where you should stop asking user for so much details.
But if you want to go to such extreme, then all your examples would look like that:
BlElement new content: (MyFancyPony new); openInWorld.
Where MyFancyPony, is a content - an object that defines shape, and knows how to render itself and so on. A smart object, that knows how to behave and draw itself. Not just holding state. And number of properties and/or behavior is then up to the user. And he is free to use anything fancy or not to deliver content on the screen.
That would be much better. But then it will duplicate the features of BlElement (or at least a significant subset of it) - being able to render itself. But still.
P.S. Now i see BlElement.. why nobody said that it is replacement for Morph? I was thinking that in addition to BlMorph, you have BlElement :) Sure, you don't need two compositions. As long as there one thing that represent UI elements, that can form hierarchy its ok. So, i retract my comments about Elements.. And apologize.
-- Best regards, Igor Stasenko.
-- Best regards, Igor Stasenko.
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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) cheers -ben
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
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@gmail.com> wrote:
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
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@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@gmail.com> wrote:
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
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@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@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@gmail.com> wrote:
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
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] *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.
*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. 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. Cheers, Alex [1] https://helpx.adobe.com/after-effects/using/overview-shape-layers-paths-vect... <https://helpx.adobe.com/after-effects/using/overview-shape-layers-paths-vect...> On Tue, Apr 5, 2016 at 3:29 PM, Aliaksei Syrel <alex.syrel@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@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@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@gmail.com> wrote:
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
On 5 April 2016 at 16:51, Aliaksei Syrel <alex.syrel@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-vect... <https://helpx.adobe.com/after-effects/using/overview-shape-layers-paths-vect...>
On Tue, Apr 5, 2016 at 3:29 PM, Aliaksei Syrel <alex.syrel@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@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@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@gmail.com> wrote:
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
On 5 April 2016 at 16:29, Aliaksei Syrel <alex.syrel@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@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@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@gmail.com> wrote:
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
On 5 April 2016 at 17:27, Igor Stasenko <siguctua@gmail.com> wrote:
On 5 April 2016 at 16:29, Aliaksei Syrel <alex.syrel@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?
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.
Cheers, Alex
On Tue, Apr 5, 2016 at 3:15 PM, Aliaksei Syrel <alex.syrel@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@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@gmail.com> wrote:
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
-- Best regards, Igor Stasenko.
2016-04-05 16:31 GMT+02:00 Igor Stasenko <siguctua@gmail.com>:
On 5 April 2016 at 17:27, Igor Stasenko <siguctua@gmail.com> wrote:
On 5 April 2016 at 16:29, Aliaksei Syrel <alex.syrel@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?
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. 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?
Cheers, Alex
On Tue, Apr 5, 2016 at 3:15 PM, Aliaksei Syrel <alex.syrel@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@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@gmail.com> wrote:
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
-- Best regards, Igor Stasenko.
On 6 April 2016 at 10:35, Nicolai Hess <nicolaihess@gmail.com> wrote:
2016-04-05 16:31 GMT+02:00 Igor Stasenko <siguctua@gmail.com>:
On 5 April 2016 at 17:27, Igor Stasenko <siguctua@gmail.com> wrote:
On 5 April 2016 at 16:29, Aliaksei Syrel <alex.syrel@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?
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 <alex.syrel@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@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@gmail.com> wrote:
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
-- Best regards, Igor Stasenko.
-- Best regards, Igor Stasenko.
2016-04-06 10:07 GMT+02:00 Igor Stasenko <siguctua@gmail.com>:
On 6 April 2016 at 10:35, Nicolai Hess <nicolaihess@gmail.com> wrote:
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.
Cool. I played with AthensBalloon a way back; but some stuff wasn't working. Good to know it has improved, and this is within Athens objective to be backend-neutral.
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.
It is interesting to know, because this means you probably already have an idea about how to implement that :) Regards, Thierry
On 6 April 2016 at 11:14, Thierry Goubier <thierry.goubier@gmail.com> wrote:
2016-04-06 10:07 GMT+02:00 Igor Stasenko <siguctua@gmail.com>:
On 6 April 2016 at 10:35, Nicolai Hess <nicolaihess@gmail.com> wrote:
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.
Cool. I played with AthensBalloon a way back; but some stuff wasn't working. Good to know it has improved, and this is within Athens objective to be backend-neutral.
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.
It is interesting to know, because this means you probably already have an idea about how to implement that :)
Of course i do. There are an open-source ShivaVG project that implements
OpenVG using OpenGL. And i was looking how i could use it for insights and 'steal' some code in order to do it :) https://sourceforge.net/projects/shivavg/
Regards,
Thierry
-- Best regards, Igor Stasenko.
That explains, why, there initially certain features of Cairo, that not exposed by Athens. It is not because we can't or just don't care.. we do.. But only after we can see that it consistent with rest of API and can be easily implemented on most of other potential backends. Yes, i am talking about that damn 'minimal common denominator' problem. :) Oh, and also i forgot to mention, that there could be also 2 more potential backends: - OpenVG and - Quartz, that is a vector graphics library for Mac OS On 6 April 2016 at 11:23, Igor Stasenko <siguctua@gmail.com> wrote:
On 6 April 2016 at 11:14, Thierry Goubier <thierry.goubier@gmail.com> wrote:
2016-04-06 10:07 GMT+02:00 Igor Stasenko <siguctua@gmail.com>:
On 6 April 2016 at 10:35, Nicolai Hess <nicolaihess@gmail.com> wrote:
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.
Cool. I played with AthensBalloon a way back; but some stuff wasn't working. Good to know it has improved, and this is within Athens objective to be backend-neutral.
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.
It is interesting to know, because this means you probably already have an idea about how to implement that :)
Of course i do. There are an open-source ShivaVG project that implements
OpenVG using OpenGL. And i was looking how i could use it for insights and 'steal' some code in order to do it :)
https://sourceforge.net/projects/shivavg/
Regards,
Thierry
-- Best regards, Igor Stasenko.
-- Best regards, Igor Stasenko.
On Wed, Apr 6, 2016 at 4:36 PM, Igor Stasenko <siguctua@gmail.com> wrote:
That explains, why, there initially certain features of Cairo, that not exposed by Athens. It is not because we can't or just don't care.. we do.. But only after we can see that it consistent with rest of API and can be easily implemented on most of other potential backends. Yes, i am talking about that damn 'minimal common denominator' problem. :)
Oh, and also i forgot to mention, that there could be also 2 more potential backends: - OpenVG and - Quartz, that is a vector graphics library for Mac OS
and Vulkan? and thanks for writing up that explanation on Athens. cheers -ben
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 <siguctua@gmail.com> wrote:
On 6 April 2016 at 10:35, Nicolai Hess <nicolaihess@gmail.com> wrote:
2016-04-05 16:31 GMT+02:00 Igor Stasenko <siguctua@gmail.com>:
On 5 April 2016 at 17:27, Igor Stasenko <siguctua@gmail.com> wrote:
On 5 April 2016 at 16:29, Aliaksei Syrel <alex.syrel@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?
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 <alex.syrel@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@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@gmail.com> wrote:
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
-- Best regards, Igor Stasenko.
-- Best regards, Igor Stasenko.
-- Best regards, Igor Stasenko.
2016-04-06 10:07 GMT+02:00 Igor Stasenko <siguctua@gmail.com>:
On 6 April 2016 at 10:35, Nicolai Hess <nicolaihess@gmail.com> wrote:
2016-04-05 16:31 GMT+02:00 Igor Stasenko <siguctua@gmail.com>:
On 5 April 2016 at 17:27, Igor Stasenko <siguctua@gmail.com> wrote:
On 5 April 2016 at 16:29, Aliaksei Syrel <alex.syrel@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?
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.
+1 ( I started to put my hands on this)
Cheers, Alex
On Tue, Apr 5, 2016 at 3:15 PM, Aliaksei Syrel <alex.syrel@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@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@gmail.com> wrote:
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
-- Best regards, Igor Stasenko.
-- Best regards, Igor Stasenko.
On 5 April 2016 at 16:15, Aliaksei Syrel <alex.syrel@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.
/me feel happiness :)
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).
No, my code is not perfect. Don't say it like that. It was just a mere attempt to marry Morphic with Athens, in order to make things work.. But if it works, it doesn't means it perfect. It just works. I don't need false credit(s) :)
Ideally , of course, all clipping should be done in local coordinates. There should be no, or as little as possible things, that require absolute coordinates. And so, i am happy to hear that, and that we are on same side here, i can only welcome any steps towards that direction.
- *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
That is also a step forward. Ideally it should be possible in Athens as
well, to clip using any shape. But.. yeah.. we needed to implement simplest things first, before introducing more. So, ideally what you need is to extend functionality of #clipBy:during: , of AthensCanvas, and allow any shape, not just Rectangle.
- *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.
Hmm.. That is a higher level/dimension concept. I doubt that graphics engine needs to support it out of the box. Grouping operations is up to the user.. there's no single fixed way how one can group simple operations of fill() function.. and in what order whatever.
I do not object that grouping operations are not useful. I just objecting that it should be part of core API. Any kind of grouping is allowed at the user level, so in Bloc you are free to introduce it the way you like or want. I would say more about it, if you would motivate , what you can see, how graphical engine could potentially allow better performance/ease of use in case if it will support groups. For instance , in OpenGL there are a command lists, which you can compile once and then execute it as many time as you want. That is useful in terms that it can prepare a bunch of operations and optimize them for later use. This is basically a simple command pattern, implemented in C :)
From that perspective, if you mean support of groups in that way.. then it is good direction. But if not, then i am not convinced.
- *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.
Review and optimization. I can only welcome that.
- *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:
Sure, using backend to its full potential is again can be only welcomed.
But Cairo is not the only backend for Athens. So, if you can expose such operation at more backend neutral level in Athens, then it is welcome as well. I was thinking about that, but never got my hands to it.. How to expose it in backend-neutral way. So, if you found how - do it. I can only welcome that.
Cheers, Alex
On Tue, Apr 5, 2016 at 2:12 PM, Igor Stasenko <siguctua@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@gmail.com> wrote:
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
Made a quick gist of of this: https://gist.github.com/philippeback/ef4d128e953de226cf40639641f83e04 On Tue, Apr 5, 2016 at 1:33 PM, Igor Stasenko <siguctua@gmail.com> wrote:
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
On 5 April 2016 at 15:58, phil@highoctane.be <phil@highoctane.be> wrote:
Made a quick gist of of this: https://gist.github.com/philippeback/ef4d128e953de226cf40639641f83e04
Thanks. Would be nice if someone with better English than mine skim trough it and fix bad language :)
On Tue, Apr 5, 2016 at 1:33 PM, Igor Stasenko <siguctua@gmail.com> wrote:
On 5 April 2016 at 04:00, Ben Coman <btc@openinworld.com> wrote:
On Tue, Apr 5, 2016 at 2:51 AM, Igor Stasenko <siguctua@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.
On 05/04/2016 15:08, Igor Stasenko wrote:
On 5 April 2016 at 15:58, phil@highoctane.be <mailto:phil@highoctane.be> <phil@highoctane.be <mailto:phil@highoctane.be>> wrote:
Made a quick gist of of this: https://gist.github.com/philippeback/ef4d128e953de226cf40639641f83e04
Thanks. Would be nice if someone with better English than mine skim trough it and fix bad language :)
This should be added in the meta data of Athens's configuration I think. It would help people to find it.
-- Best regards, Igor Stasenko.
-- Cyril Ferlicot http://www.synectique.eu 165 Avenue Bretagne Lille 59000 France
Hi, Indeed, this was not clear. The original idea of the shape was to be primarily responsible for the clipping, but in the meantime the stroke and fill filtered in the implementation. Alex, Andrei and I will now change the âshapeâ to only be responsible for clipping and leave the drawing to the element. We will also propose a solution for dealing with drawing and interaction. Please stay tuned and thanks for this discussion. Cheers, Doru
On Apr 4, 2016, at 9:00 AM, stepharo <stepharo@free.fr> wrote:
I saw in bleedingEdge it is different and looks better. Now I was wondering why shape did not do the rendering and I thought that this is may be because a shape can be used for doing something else than rendering. For example clipping or as igor mentioned defining the shape of the interaction
drawOnSpartaCanvas: aCanvas "Actually render receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil"
self assert: self shape path context isNotNil.
aCanvas clipPreserveBy: self shape during: [ aCanvas paintGroup: [ aCanvas setPaint: self shape fillPaint. aCanvas fillPreserve. aCanvas paintMode source. aCanvas setStrokePaint: self shape strokePaint. aCanvas stroke ] ]
unprotectedFullDrawOnSpartaCanvas: aCanvas "Draw the full structure on the given Canvas withing drawing bounds without caring about any errors and visibility. I am responsible for clipping and transforming canvas's path to local coordinates to allow simpler actual drawing in drawOnAthensCanvas: Additional checks should be implemented in fullDrawOnAthensCanvas: aCanvas is an instance of AthensCanvas. aCanvas must not be nil. @see BlElement>>#fullDrawOnAthensCanvas: @see BlElement>>#drawOnAthensCanvas:" self shape adaptTo: self. self shape path context: aCanvas.
aCanvas transform: self transformation during: [ self clippingStrategy clip: self on: aCanvas during: [ self drawOnSpartaCanvas: aCanvas ] childrenDuring: [ self drawChildrenOnSpartaCanvas: aCanvas ] ]
-- www.tudorgirba.com www.feenk.com "To utilize feedback, you first have to acquire it."
Doru I have the impression that clipping and interaction can be defined by a "shape" then the drawing can be either drawn in a canvas or delegate to something else (for example for the cursor if there is an hardware rendering). I like this discussion and the feedback of igor because he is kicking our asses much better than me in fact because he has real argument while I just got feelings :) Stef Le 4/4/16 19:10, Tudor Girba a écrit :
Hi,
Indeed, this was not clear. The original idea of the shape was to be primarily responsible for the clipping, but in the meantime the stroke and fill filtered in the implementation. Alex, Andrei and I will now change the âshapeâ to only be responsible for clipping and leave the drawing to the element. We will also propose a solution for dealing with drawing and interaction.
Please stay tuned and thanks for this discussion.
Cheers, Doru
On Apr 4, 2016, at 9:00 AM, stepharo <stepharo@free.fr> wrote:
I saw in bleedingEdge it is different and looks better. Now I was wondering why shape did not do the rendering and I thought that this is may be because a shape can be used for doing something else than rendering. For example clipping or as igor mentioned defining the shape of the interaction
drawOnSpartaCanvas: aCanvas "Actually render receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil"
self assert: self shape path context isNotNil.
aCanvas clipPreserveBy: self shape during: [ aCanvas paintGroup: [ aCanvas setPaint: self shape fillPaint. aCanvas fillPreserve. aCanvas paintMode source. aCanvas setStrokePaint: self shape strokePaint. aCanvas stroke ] ]
unprotectedFullDrawOnSpartaCanvas: aCanvas "Draw the full structure on the given Canvas withing drawing bounds without caring about any errors and visibility. I am responsible for clipping and transforming canvas's path to local coordinates to allow simpler actual drawing in drawOnAthensCanvas: Additional checks should be implemented in fullDrawOnAthensCanvas: aCanvas is an instance of AthensCanvas. aCanvas must not be nil. @see BlElement>>#fullDrawOnAthensCanvas: @see BlElement>>#drawOnAthensCanvas:" self shape adaptTo: self. self shape path context: aCanvas.
aCanvas transform: self transformation during: [ self clippingStrategy clip: self on: aCanvas during: [ self drawOnSpartaCanvas: aCanvas ] childrenDuring: [ self drawChildrenOnSpartaCanvas: aCanvas ] ]
-- www.tudorgirba.com www.feenk.com
"To utilize feedback, you first have to acquire it."
On 4 April 2016 at 23:17, stepharo <stepharo@free.fr> wrote:
Doru
I have the impression that clipping and interaction can be defined by a "shape" then the drawing can be either drawn in a canvas or delegate to something else (for example for the cursor if there is an hardware rendering).
I like this discussion and the feedback of igor because he is kicking our asses much better than me in fact because he has real argument while I just got feelings :)
It is easy to kick asses.. much easier than doing things. And please, explain to guys, that i am not some kind of trolly crazy angry idiot, who sits in the corner making giggles and throwing paperballs into student(s), because its fun. It is because i also made similar mistakes, or have to fix other's mistakes at own time, and really don't want people repeating them over and over again, like ShadowCanvas, now its ShadowPaint.
Stef
Le 4/4/16 19:10, Tudor Girba a écrit :
Hi,
Indeed, this was not clear. The original idea of the shape was to be primarily responsible for the clipping, but in the meantime the stroke and fill filtered in the implementation. Alex, Andrei and I will now change the âshapeâ to only be responsible for clipping and leave the drawing to the element. We will also propose a solution for dealing with drawing and interaction.
Please stay tuned and thanks for this discussion.
Cheers, Doru
On Apr 4, 2016, at 9:00 AM, stepharo <stepharo@free.fr> wrote:
I saw in bleedingEdge it is different and looks better. Now I was wondering why shape did not do the rendering and I thought that this is may be because a shape can be used for doing something else than rendering. For example clipping or as igor mentioned defining the shape of the interaction
drawOnSpartaCanvas: aCanvas "Actually render receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil"
self assert: self shape path context isNotNil.
aCanvas clipPreserveBy: self shape during: [ aCanvas paintGroup: [ aCanvas setPaint: self shape fillPaint. aCanvas fillPreserve. aCanvas paintMode source. aCanvas setStrokePaint: self shape strokePaint. aCanvas stroke ] ]
unprotectedFullDrawOnSpartaCanvas: aCanvas "Draw the full structure on the given Canvas withing drawing bounds without caring about any errors and visibility. I am responsible for clipping and transforming canvas's path to local coordinates to allow simpler actual drawing in drawOnAthensCanvas: Additional checks should be implemented in fullDrawOnAthensCanvas: aCanvas is an instance of AthensCanvas. aCanvas must not be nil. @see BlElement>>#fullDrawOnAthensCanvas: @see BlElement>>#drawOnAthensCanvas:" self shape adaptTo: self. self shape path context: aCanvas.
aCanvas transform: self transformation during: [ self clippingStrategy clip: self on: aCanvas during: [ self drawOnSpartaCanvas: aCanvas ] childrenDuring: [ self drawChildrenOnSpartaCanvas: aCanvas ] ]
--
www.tudorgirba.com www.feenk.com
"To utilize feedback, you first have to acquire it."
-- Best regards, Igor Stasenko.
On Mon, Apr 4, 2016 at 11:45 PM, Igor Stasenko <siguctua@gmail.com> wrote:
On 4 April 2016 at 23:17, stepharo <stepharo@free.fr> wrote:
Doru
I have the impression that clipping and interaction can be defined by a "shape" then the drawing can be either drawn in a canvas or delegate to something else (for example for the cursor if there is an hardware rendering).
I like this discussion and the feedback of igor because he is kicking our asses much better than me in fact because he has real argument while I just got feelings :)
It is easy to kick asses.. much easier than doing things. And please, explain to guys, that i am not some kind of trolly crazy angry idiot, who sits in the corner making giggles and throwing paperballs into student(s), because its fun. It is because i also made similar mistakes, or have to fix other's mistakes at own time, and really don't want people repeating them over and over again, like ShadowCanvas, now its ShadowPaint.
yes, shadow should not be a paint. We discussed and decided to change that a few month ago just forgot to remove it. Cheers, Andrei
Stef
Le 4/4/16 19:10, Tudor Girba a écrit :
Hi,
Indeed, this was not clear. The original idea of the shape was to be primarily responsible for the clipping, but in the meantime the stroke and fill filtered in the implementation. Alex, Andrei and I will now change the âshapeâ to only be responsible for clipping and leave the drawing to the element. We will also propose a solution for dealing with drawing and interaction.
Please stay tuned and thanks for this discussion.
Cheers, Doru
On Apr 4, 2016, at 9:00 AM, stepharo <stepharo@free.fr> wrote:
I saw in bleedingEdge it is different and looks better. Now I was wondering why shape did not do the rendering and I thought that this is may be because a shape can be used for doing something else than rendering. For example clipping or as igor mentioned defining the shape of the interaction
drawOnSpartaCanvas: aCanvas "Actually render receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil"
self assert: self shape path context isNotNil.
aCanvas clipPreserveBy: self shape during: [ aCanvas paintGroup: [ aCanvas setPaint: self shape fillPaint. aCanvas fillPreserve. aCanvas paintMode source. aCanvas setStrokePaint: self shape strokePaint. aCanvas stroke ] ]
unprotectedFullDrawOnSpartaCanvas: aCanvas "Draw the full structure on the given Canvas withing drawing bounds without caring about any errors and visibility. I am responsible for clipping and transforming canvas's path to local coordinates to allow simpler actual drawing in drawOnAthensCanvas: Additional checks should be implemented in fullDrawOnAthensCanvas: aCanvas is an instance of AthensCanvas. aCanvas must not be nil. @see BlElement>>#fullDrawOnAthensCanvas: @see BlElement>>#drawOnAthensCanvas:" self shape adaptTo: self. self shape path context: aCanvas.
aCanvas transform: self transformation during: [ self clippingStrategy clip: self on: aCanvas during: [ self drawOnSpartaCanvas: aCanvas ] childrenDuring: [ self drawChildrenOnSpartaCanvas: aCanvas ] ]
--
www.tudorgirba.com www.feenk.com
"To utilize feedback, you first have to acquire it."
-- Best regards, Igor Stasenko.
On 5 April 2016 at 00:51, Andrei Chis <chisvasileandrei@gmail.com> wrote: btw, Andrei , if you looking how you can test if point contains shape or not, take a look at AthensCurveFlattener. It converts curved path, that containing Bezier curves (or not) into simple polygonal shape that consists only from a simple lines. Then there AthensPolygonTester, that has the piece you missing: - a small algorithm that can test if given point inside or outside that polygon. Please note that it is simpler even-odd rule algorithm. It not works correctly for all possible cases. There's another algorithm- winding number algorithm, that is much better, but i had to switch to other stuff before were able to get my hands to it. It is more reliable, since it can work for self-intersecting shapes. https://en.wikipedia.org/wiki/Point_in_polygon So, what you need is to wire these things down to Bloc.Then whenever you need to test whether point within shape or not, you can convert any path into polygon and perform the test. And of course, you can cache the results of conversion in order to avoid expensive computations every time you need to perform such tests. Once path is converted, the test is relatively cheap and costs something like O(n), where n is number of line segments. Or maybe, to simplify things, you could extend the shape protocol and introduce #containsPoint: or as variant #containsPoint:ifNotAvailable: so that if shape implements such feature - it can answer true or false, and if not - evaluating a block. So, you don't have to implement all tests for all kinds of shapes that invented or not yet invented in universe. -- Best regards, Igor Stasenko.
On 5 April 2016 at 01:12, Igor Stasenko <siguctua@gmail.com> wrote:
On 5 April 2016 at 00:51, Andrei Chis <chisvasileandrei@gmail.com> wrote:
btw, Andrei , if you looking how you can test if point contains shape or not, take a look at AthensCurveFlattener. It converts curved path, that containing Bezier curves (or not) into simple polygonal shape that consists only from a simple lines. Then there AthensPolygonTester, that has the piece you missing: - a small algorithm that can test if given point inside or outside that polygon.
Please note that it is simpler even-odd rule algorithm. It not works correctly for all possible cases.
There's another algorithm- winding number algorithm, that is much better, but i had to switch to other stuff before were able to get my hands to it. It is more reliable, since it can work for self-intersecting shapes.
https://en.wikipedia.org/wiki/Point_in_polygon
So, what you need is to wire these things down to Bloc.Then whenever you need to test whether point within shape or not, you can convert any path into polygon and perform the test. And of course, you can cache the results of conversion in order to avoid expensive computations every time you need to perform such tests. Once path is converted, the test is relatively cheap and costs something like O(n), where n is number of line segments.
Or maybe, to simplify things, you could extend the shape protocol and introduce #containsPoint: or as variant #containsPoint:ifNotAvailable: so that if shape implements such feature - it can answer true or false, and if not - evaluating a block. So, you don't have to implement all tests for all kinds of shapes that invented or not yet invented in universe.
Oh, forgot to add, you can look for example how i converting path in AthensBezier3Scene class.
-- Best regards, Igor Stasenko.
-- Best regards, Igor Stasenko.
As far as I know bloc already has perfect hit detection. In BlPath. What a dumb state holder! -- View this message in context: http://forum.world.st/bloc-I-do-not-understand-why-some-behavior-is-not-in-t... Sent from the Pharo Smalltalk Developers mailing list archive at Nabble.com.
On 5 April 2016 at 00:50, Anonymous <anonymousknight@hmamail.com> wrote:
As far as I know bloc already has perfect hit detection. In BlPath. What a dumb state holder!
Aha, i see - containsX: x Y: y
render the path to determine if it contains point or not. Thank you for pointing out! Now i have another reason for yet another rant! Seriously, brute force is synonym of dumb. If you cannot solve the problem by anything else than using brute force, then first thing you do, you leave an extensive comment "sorry guys, this piece stinks, but i was <...> unable to do any better". That at least won't aflame people when they will look at it :)
-- View this message in context: http://forum.world.st/bloc-I-do-not-understand-why-some-behavior-is-not-in-t... Sent from the Pharo Smalltalk Developers mailing list archive at Nabble.com.
-- Best regards, Igor Stasenko.
Hi Igor and others, (Without having seen any of the code, but from experience on other (Javascript) project, beziers might be useful. Maybe the mentioned approach for polygon approximation is already based on beziers. Forgive me for wasting your time in that case.) Beziers may be a good starting point for different kinds of tests/transformations/etc. Most shapes (should I say Paths here? ;-) can be specified as (cubic) beziers or approximated, allowing these tests/transforms on all shapes. See http://pomax.github.io/bezierinfo/ for the explanation and the Javascript-code. Nice read for anyone interested in some maths concerning beziers. Cheers, Erik -- View this message in context: http://forum.world.st/bloc-I-do-not-understand-why-some-behavior-is-not-in-t... Sent from the Pharo Smalltalk Developers mailing list archive at Nabble.com.
Seriously, brute force is synonym of dumb. If you cannot solve the problem by anything else than using brute force, then first thing you do, you leave an extensive comment "sorry guys, this piece stinks, but i was <...> unable to do any better".
That is enough, Igor. Calling other developers or their decisions "dumb" is unacceptable! It is far way beyond a red line and only shows impoliteness. You are not trying to constructively criticize but just trolling us. Here are some basic rules if you want to continue discussion: 1) Before "pushing" other people to spend time on you, try to spend your own. Read previous posts at least in the same thread before writing an email. Link to bloc build was in the second email but you asked for it 2 or 3 messages later. 2) Don't use "pfff...", "yadda yadda", " pony" or whatever other jargon in any thread related to bloc. It distracts and shows your disregard to the reader. 3) Don't judge others that they don't know or understand something. There are no stupid people around here. 4) Write short but informative emails. We have a lot of other stuff to do. Everything that you wrote can be expressed using much less amount of characters. 5) First ask why decision was made and only then describe cons and pros. There is obsolete code in multiple places left because it maybe forgotten or we realized that it was a mistake but accidentally committed. That shadow problem was a mistake and big thanks to Glenn who explained and fixed it in bloc a lot of month ago by using ShadowFilter which is in another repo for a moment. 6) When criticizing try to find and mention also positive desicions. It is called politeness. Thanks Alex On 5 April 2016 at 01:12, Igor Stasenko <siguctua@gmail.com> wrote:
On 5 April 2016 at 00:51, Andrei Chis <chisvasileandrei@gmail.com> wrote:
btw, Andrei , if you looking how you can test if point contains shape or not, take a look at AthensCurveFlattener. It converts curved path, that containing Bezier curves (or not) into simple polygonal shape that consists only from a simple lines. Then there AthensPolygonTester, that has the piece you missing: - a small algorithm that can test if given point inside or outside that polygon.
Please note that it is simpler even-odd rule algorithm. It not works correctly for all possible cases.
There's another algorithm- winding number algorithm, that is much better, but i had to switch to other stuff before were able to get my hands to it. It is more reliable, since it can work for self-intersecting shapes.
https://en.wikipedia.org/wiki/Point_in_polygon
So, what you need is to wire these things down to Bloc.Then whenever you need to test whether point within shape or not, you can convert any path into polygon and perform the test. And of course, you can cache the results of conversion in order to avoid expensive computations every time you need to perform such tests. Once path is converted, the test is relatively cheap and costs something like O(n), where n is number of line segments.
Or maybe, to simplify things, you could extend the shape protocol and introduce #containsPoint: or as variant #containsPoint:ifNotAvailable: so that if shape implements such feature - it can answer true or false, and if not - evaluating a block. So, you don't have to implement all tests for all kinds of shapes that invented or not yet invented in universe.
Oh, forgot to add, you can look for example how i converting path in AthensBezier3Scene class.
-- Best regards, Igor Stasenko.
-- Best regards, Igor Stasenko.
On 5 April 2016 at 02:34, Aliaksei Syrel <alex.syrel@gmail.com> wrote:
Seriously, brute force is synonym of dumb. If you cannot solve the problem by anything else than using brute force, then first thing you do, you leave an extensive comment "sorry guys, this piece stinks, but i was <...> unable to do any better".
That is enough, Igor. Calling other developers or their decisions "dumb" is unacceptable! It is far way beyond a red line and only shows impoliteness. You are not trying to constructively criticize but just trolling us.
Why i should be polite to bad code and bad practices? And have you thought what i should feel , when you using 'perfect' wording for something that not nearly close to perfect? That was insulting.
Here are some basic rules if you want to continue discussion:
1) Before "pushing" other people to spend time on you, try to spend your own. Read previous posts at least in the same thread before writing an email. Link to bloc build was in the second email but you asked for it 2 or 3 messages later.
2) Don't use "pfff...", "yadda yadda", " pony" or whatever other jargon in any thread related to bloc. It distracts and shows your disregard to the reader.
3) Don't judge others that they don't know or understand something. There
are no stupid people around here.
4) Write short but informative emails. We have a lot of other stuff to do. Everything that you wrote can be expressed using much less amount of characters.
If you don't have time to read mails, don't read them. Just ignore. Nobody forcing you to to read it. I had to write long, because i need to explain what i don't like and why. Or would it be better for you if i would review the code and just state: - i don't like it, fix it. Leaving you clueless what i don't like and how it should be fixed? Will such brevity help you to fix things? Apparently not. It is quite opposite, because them you can simply ignore it. And if i would do it like that, this is exactly will be disregard to participants of discussion.
5) First ask why decision was made and only then describe cons and pros. There is obsolete code in multiple places left because it maybe forgotten or we realized that it was a mistake but accidentally committed. That shadow problem was a mistake and big thanks to Glenn who explained and fixed it in bloc a lot of month ago by using ShadowFilter which is in another repo for a moment.
6) When criticizing try to find and mention also positive desicions. It is called politeness.
I just want it to see it fixed. Yes, #containsX:Y: works.. but that's far from 'perfect'. Because right now it using a private method of Cairo, bypassing layer of abstraction provided by Athens. So, you need to expose feature in proper way via Athens API.. and then it is perfect. And i didn't mentioned it, because as to me it is obvious. You asking me to be short, but then pointing that i don't mention positive decisions. You can't have both, choose one.
From your side, i don't like to see that on all that mails with many questions, all i got in response it: 'bullshit, it is perfect'. Because it feels like that.
If all that i wrote here is bullshit, then fine.. perfect. You don't have to do and worry about anything.
Thanks Alex
-- Best regards, Igor Stasenko.
About hit detection. Idea was to be completely general. And this is the only approach I found (still bad because uses native calls instead of athens-like solution) that works with text. Text is also a shape and it's very cool to be able to detect events inside characters. Imagine morph in form of huge arbitrary string. Current solution does not render, it only sets fill paint and path, then uses cairo, no need to invent wheel. Imagine that paint is something fancy, Cairo takes Cairo about special cases. Cheers Alex On Apr 5, 2016 12:17 AM, "Igor Stasenko" <siguctua@gmail.com> wrote:
On 5 April 2016 at 01:12, Igor Stasenko <siguctua@gmail.com> wrote:
On 5 April 2016 at 00:51, Andrei Chis <chisvasileandrei@gmail.com> wrote:
btw, Andrei , if you looking how you can test if point contains shape or not, take a look at AthensCurveFlattener. It converts curved path, that containing Bezier curves (or not) into simple polygonal shape that consists only from a simple lines. Then there AthensPolygonTester, that has the piece you missing: - a small algorithm that can test if given point inside or outside that polygon.
Please note that it is simpler even-odd rule algorithm. It not works correctly for all possible cases.
There's another algorithm- winding number algorithm, that is much better, but i had to switch to other stuff before were able to get my hands to it. It is more reliable, since it can work for self-intersecting shapes.
https://en.wikipedia.org/wiki/Point_in_polygon
So, what you need is to wire these things down to Bloc.Then whenever you need to test whether point within shape or not, you can convert any path into polygon and perform the test. And of course, you can cache the results of conversion in order to avoid expensive computations every time you need to perform such tests. Once path is converted, the test is relatively cheap and costs something like O(n), where n is number of line segments.
Or maybe, to simplify things, you could extend the shape protocol and introduce #containsPoint: or as variant #containsPoint:ifNotAvailable: so that if shape implements such feature - it can answer true or false, and if not - evaluating a block. So, you don't have to implement all tests for all kinds of shapes that invented or not yet invented in universe.
Oh, forgot to add, you can look for example how i converting path in AthensBezier3Scene class.
-- Best regards, Igor Stasenko.
-- Best regards, Igor Stasenko.
On 5 April 2016 at 02:37, Aliaksei Syrel <alex.syrel@gmail.com> wrote:
About hit detection. Idea was to be completely general. And this is the only approach I found (still bad because uses native calls instead of athens-like solution) that works with text. Text is also a shape and it's very cool to be able to detect events inside characters. Imagine morph in form of huge arbitrary string.
Yes, that would be cool to have.
Current solution does not render, it only sets fill paint and path, then uses cairo, no need to invent wheel. Imagine that paint is something fancy, Cairo takes Cairo about special cases.
This code was born before Cairo came to play. Mind you that Cairo is only one of the potential backends of Athens. It doesn't means that you cannot use feature and invent own wheel. Don't you think i am not hating to reproduce numerical algorithms by myself, if i would be able to use one that already available for use. But that was not the case. So, exposing feature via correct protocol is the way to go. Then when backend provides such feature, one can simply use backed.. and if not - then it will fallback to homemade numerical crunching. And especially that code smells and need a cleanup (not your code, but code in Athens). All i wanna see is exposing feature in nice and consisting way. We all want it to be there. Not just you and me, but whole community does. The rest is a poetry.
Cheers Alex On Apr 5, 2016 12:17 AM, "Igor Stasenko" <siguctua@gmail.com> wrote:
On 5 April 2016 at 01:12, Igor Stasenko <siguctua@gmail.com> wrote:
On 5 April 2016 at 00:51, Andrei Chis <chisvasileandrei@gmail.com> wrote:
btw, Andrei , if you looking how you can test if point contains shape or not, take a look at AthensCurveFlattener. It converts curved path, that containing Bezier curves (or not) into simple polygonal shape that consists only from a simple lines. Then there AthensPolygonTester, that has the piece you missing: - a small algorithm that can test if given point inside or outside that polygon.
Please note that it is simpler even-odd rule algorithm. It not works correctly for all possible cases.
There's another algorithm- winding number algorithm, that is much better, but i had to switch to other stuff before were able to get my hands to it. It is more reliable, since it can work for self-intersecting shapes.
https://en.wikipedia.org/wiki/Point_in_polygon
So, what you need is to wire these things down to Bloc.Then whenever you need to test whether point within shape or not, you can convert any path into polygon and perform the test. And of course, you can cache the results of conversion in order to avoid expensive computations every time you need to perform such tests. Once path is converted, the test is relatively cheap and costs something like O(n), where n is number of line segments.
Or maybe, to simplify things, you could extend the shape protocol and introduce #containsPoint: or as variant #containsPoint:ifNotAvailable: so that if shape implements such feature - it can answer true or false, and if not - evaluating a block. So, you don't have to implement all tests for all kinds of shapes that invented or not yet invented in universe.
Oh, forgot to add, you can look for example how i converting path in AthensBezier3Scene class.
-- Best regards, Igor Stasenko.
-- Best regards, Igor Stasenko.
-- Best regards, Igor Stasenko.
Hi Stef, Currently there was a distinction between Shape and Path which turn out to be confusing. So Shape will go away and there will be just a Path (line, circle, rectangle, etc). We started to refactor this during the PharoDays. And yes, Rectangle will be polymorphic with Path. BlElement does support clipping and click detection by paths but right now they were implicit and you needed to subclass BlElement to override them independently. We'll change this so one can set them independently in BlElement. Yes, BlElement had a default path for drawing but that will go away. By default BlElement will have an empty drawOn... method. So to create a custom widget you can subclass BlElement, override drawOn... and use the canvas to draw anything you wish. There will be a subclass of BlElement that will add more drawing oriented logic but BlElement will be independent of that. Yes, the API of the canvas is not perfect but we're improving. Cheers, Andrei On Mon, Apr 4, 2016 at 6:00 PM, stepharo <stepharo@free.fr> wrote:
I saw in bleedingEdge it is different and looks better. Now I was wondering why shape did not do the rendering and I thought that this is may be because a shape can be used for doing something else than rendering. For example clipping or as igor mentioned defining the shape of the interaction
drawOnSpartaCanvas: aCanvas "Actually render receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil"
self assert: self shape path context isNotNil.
aCanvas clipPreserveBy: self shape during: [ aCanvas paintGroup: [ aCanvas setPaint: self shape fillPaint. aCanvas fillPreserve. aCanvas paintMode source. aCanvas setStrokePaint: self shape strokePaint. aCanvas stroke ] ]
unprotectedFullDrawOnSpartaCanvas: aCanvas "Draw the full structure on the given Canvas withing drawing bounds without caring about any errors and visibility. I am responsible for clipping and transforming canvas's path to local coordinates to allow simpler actual drawing in drawOnAthensCanvas: Additional checks should be implemented in fullDrawOnAthensCanvas: aCanvas is an instance of AthensCanvas. aCanvas must not be nil. @see BlElement>>#fullDrawOnAthensCanvas: @see BlElement>>#drawOnAthensCanvas:" self shape adaptTo: self. self shape path context: aCanvas.
aCanvas transform: self transformation during: [ self clippingStrategy clip: self on: aCanvas during: [ self drawOnSpartaCanvas: aCanvas ] childrenDuring: [ self drawChildrenOnSpartaCanvas: aCanvas ] ]
ok let me know when you have a new "stable" version so that I try my example. Stef Le 4/4/16 23:21, Andrei Chis a écrit :
Hi Stef,
Currently there was a distinction between Shape and Path which turn out to be confusing. So Shape will go away and there will be just a Path (line, circle, rectangle, etc). We started to refactor this during the PharoDays. And yes, Rectangle will be polymorphic with Path.
BlElement does support clipping and click detection by paths but right now they were implicit and you needed to subclass BlElement to override them independently. We'll change this so one can set them independently in BlElement.
Yes, BlElement had a default path for drawing but that will go away. By default BlElement will have an empty drawOn... method. So to create a custom widget you can subclass BlElement, override drawOn... and use the canvas to draw anything you wish.
There will be a subclass of BlElement that will add more drawing oriented logic but BlElement will be independent of that.
Yes, the API of the canvas is not perfect but we're improving.
Cheers, Andrei
On Mon, Apr 4, 2016 at 6:00 PM, stepharo <stepharo@free.fr <mailto:stepharo@free.fr>> wrote:
I saw in bleedingEdge it is different and looks better. Now I was wondering why shape did not do the rendering and I thought that this is may be because a shape can be used for doing something else than rendering. For example clipping or as igor mentioned defining the shape of the interaction
drawOnSpartaCanvas: aCanvas "Actually render receiver on aCanvas in local bounds. Override to customize. aCanvas is an instance of AthensCanvas aCanvas must not be nil"
self assert: self shape path context isNotNil.
aCanvas clipPreserveBy: self shape during: [ aCanvas paintGroup: [ aCanvas setPaint: self shape fillPaint. aCanvas fillPreserve. aCanvas paintMode source. aCanvas setStrokePaint: self shape strokePaint. aCanvas stroke ] ]
unprotectedFullDrawOnSpartaCanvas: aCanvas "Draw the full structure on the given Canvas withing drawing bounds without caring about any errors and visibility. I am responsible for clipping and transforming canvas's path to local coordinates to allow simpler actual drawing in drawOnAthensCanvas: Additional checks should be implemented in fullDrawOnAthensCanvas: aCanvas is an instance of AthensCanvas. aCanvas must not be nil. @see BlElement>>#fullDrawOnAthensCanvas: @see BlElement>>#drawOnAthensCanvas:" self shape adaptTo: self. self shape path context: aCanvas.
aCanvas transform: self transformation during: [ self clippingStrategy clip: self on: aCanvas during: [ self drawOnSpartaCanvas: aCanvas ] childrenDuring: [ self drawChildrenOnSpartaCanvas: aCanvas ] ]
participants (13)
-
Aliaksei Syrel -
Andrei Chis -
Anonymous -
Ben Coman -
Cyril Ferlicot D. -
Cyril Ferlicot Delbecque -
Erik Stel -
Igor Stasenko -
Nicolai Hess -
phil@highoctane.be -
stepharo -
Thierry Goubier -
Tudor Girba