Open certik opened 1 month ago
Here's my initial thoughts on the current design and where I think it ought to go.
The current "API" does not really assume bitmap as the underlying representation, which is good. The current implementation really does kind of assume bitmap as the underlying representation, which will mean some significant refactoring, which I think to a degree will motivate some adjustments to the API.
Also, the current API is very procedural. Create a canvas, draw a thing on it, draw another thing on it, have it output to somewhere. I'm not suggesting that's necessarily the wrong approach, but I wonder if there is a more declarative style that could be explored for the API.
I certainly agree that we should have some sort of intermediate representation that at least resembles vector graphics. Now here comes the question in terms of design. How do the API, intermediate representation, canvas, and backends interact with eachother. Some options.
A canvas is an abstract type, that supports the user API, and backends are implementations of that type. The intermediate representation is then a shared implementation detail of different backends. This has the benefit that different implementations can be optimized quite easily, but at the cost of maintainability, because adding to the API requires that all backends be immediately updated to account for the new feature.
A canvas has a backend, that it interacts with immediately when calls to the API are made. This somewhat decouples the backends from the API, but likely couples them more directly to the intermediate representation.
A canvas just translates API calls into intermediate representation, which is later processed by a backend. This has the benefit of fully decoupling the backends from the API, and potentially decoupling (to a degree) from the intermediate representation. The question then becomes does the canvas make calls to the backend, or vice versa. This has the effect that now the user has full control over when things are actually sent to some output.
I don't have strong preferences for which option to choose, but wanted to give some food for thought.
It's a great start @AnonMiraj. I would like to add some practical comments to complement the design suggestions.
type(canvas)
implements pixels and could be a good starting point for PNG or BMP, but it's better to keep the primitives separated from the implementation (pixel-by-pixel write). So for example: type, abstract :: canvas
character(:), allocatable :: title
integer(coordinate) :: width, height
type(background) :: bg
type(line), allocatable :: lines(:)
type(circle), allocatable :: circles(:)
...
end type canvas
type, extends(canvas) :: bmp_canvas
type(point), allocatable :: pixels(:,:)
contains
procedure :: to_file
end type bmp_canvas
This way you keep the primitives separate from the implementation.
use iso_fortran_env, only: int16,
integer, parameter :: pixel = int16
integer, parameter :: rgb_level = int8
integer, parameter :: coord = real32
vec2
and vec3
are typically reserved for geometric entities. I suggest you create new modules for each of them, but separate the pixel representation from the geometric representation:
module fig_vec2
use fig_constants
type, public :: vec2
real(coord) :: x,y
end type vec2
...
end module fig_vec2
module fig_points
use fig_constants
type, public :: point
integer(pixel) :: x,y
end type point
...
It would be interesting to create a plotting library frontend. For that we should learn from Matplotlib, as well as the C++ version of it: https://github.com/alandefreitas/matplotplusplus. We need to figure out some good intermediate representation for vector objects that represent the plot.
For the backends some ideas are:
* the glfw library could be used (it seems it is in conda). * SVG (pure Fortran, just emit the xml as a string) * PNG (use a C library to actually save it, or stdlib) * PPM (use stdlib, pure Fortran)
The last two backends can use the algorithms from this library.
CC @everythingfunctional, @perazz.
Yeah, I agree, a plotting library is certainly going to be really cool. My current plan is to leave the first 6 weeks of my GSoC period mostly for creating the API for the library and the back-end, and then spend the last 6 weeks just working on the plotting side.
Here's my initial thoughts on the current design and where I think it ought to go.
The current "API" does not really assume bitmap as the underlying representation, which is good. The current implementation really does kind of assume bitmap as the underlying representation, which will mean some significant refactoring, which I think to a degree will motivate some adjustments to the API.
Initially, I wasn't planning to support vector graphics. That's why, for the current new design, there will need to be a lot of refactoring to support it.
Also, the current API is very procedural. Create a canvas, draw a thing on it, draw another thing on it, have it output to somewhere. I'm not suggesting that's necessarily the wrong approach, but I wonder if there is a more declarative style that could be explored for the API.
While I agree that a declarative approach is the right choice if the library were just for SVG or a plotting library, I'm not very keen on making this the case for FIG. It would be quite limiting for a general-use graphics library to be declarative. A procedural approach is going to give users more control and will be more fitting for my current vision of the library. It is possible in the future to use FIG to create a declarative API for graphics, but I don't think a declarative approach is the best option for the library right now—at least, that's my opinion. We may discuss this further in the next meeting.
However, I do plan to make the plotting library declarative. It is going to be more usable, flexible, and intuitive overall like this.
I certainly agree that we should have some sort of intermediate representation that at least resembles vector graphics. Now here comes the question in terms of design. How do the API, intermediate representation, canvas, and backends interact with eachother. Some options.
A canvas is an abstract type, that supports the user API, and backends are implementations of that type. The intermediate representation is then a shared implementation detail of different backends. This has the benefit that different implementations can be optimized quite easily, but at the cost of maintainability, because adding to the API requires that all backends be immediately updated to account for the new feature.
A canvas has a backend, that it interacts with immediately when calls to the API are made. This somewhat decouples the backends from the API, but likely couples them more directly to the intermediate representation.
A canvas just translates API calls into intermediate representation, which is later processed by a backend. This has the benefit of fully decoupling the backends from the API, and potentially decoupling (to a degree) from the intermediate representation. The question then becomes does the canvas make calls to the backend, or vice versa. This has the effect that now the user has full control over when things are actually sent to some output.
I don't have strong preferences for which option to choose, but wanted to give some food for thought.
I am more lenient towards an abstract canvas with different backends, but other options have a lot of merit too.
@perazz thanks for all the suggestions i will keep them in mind when i start to refactor the backend.
Also, the current API is very procedural. Create a canvas, draw a thing on it, draw another thing on it, have it output to somewhere. I'm not suggesting that's necessarily the wrong approach, but I wonder if there is a more declarative style that could be explored for the API.
In my (not yet very well thought) opinion, having the same canvas as an argument may indicate which operations belong to the same "object" (which may or may not translate to an derived type in Fortran).
As a (hopefully useful) basis for discussion, I have drawn a schematic for a possible architecture.
I do not claim that this is necessarily the best option.
The color blue means that I am really unsure about it.
Of course not everything can be done at once and it might be important to prioritize parts of the scheme.
It would be interesting to create a plotting library frontend. For that we should learn from Matplotlib, as well as the C++ version of it: https://github.com/alandefreitas/matplotplusplus. We need to figure out some good intermediate representation for vector objects that represent the plot.
For the backends some ideas are:
The last two backends can use the algorithms from this library.
CC @everythingfunctional, @perazz.