ABU 21-Aug-2024 - Blend2d 1.3.1 adapted for Tcl9 compatibility
TclTk binding for Blend2d has been released. Download blend2d-1.3.1
For starters, take a look at Blend2d Gallery.
The package contains prebuilt binaries for Win-x64, Linux-x64, and MacOS. Within the package, you can find a reference manual and a lot of demos.
Tiger demo | Electric sphere demo |
---|---|
Blend2d 1.3.1 - 2024-08-21
Blend2d 1.3 - 2024-07-11
In general if BL::spline has no options, then the minus sign of the first point, is erroneously interpreted as the start of an option "-", and then the error .. FIXED
Blend2d 1.2 - 2024-03-12
Blend2d 1.1.1 - 2023-10-31
Blend2d 1.0.1 - 2023-06-12
Blend2d 1.0 - 2023-04-15
Blend2d 1.0b2 - 2020-09-03
Blend2d 1.0b1 - 2020-08-12
TclBlend2d is a Tcl package for working with the Blend2d graphics engine.
Blend2d is an open source, high quality, high-performance vector graphics engine
TclBlend2d is a multiplatform binary package including Blend2d library for Windows and Linux; MacOS support is planned.
Images created with TclBlend2d can be saved as BMP files, or exchanged with the tk-photo images.
TclBlend2s provides also a new type of tk-image (named "blend2d") you can embed in your widgets much like a tk-photo image.
You can draw on a "blend2d" tk-image and instantly see the changes in the widgets that have this image attached.
TclBlend2d closely matches the Blend2d C++ API with the exception of cases where a more Tcl-ish way is more appropriate.
The main concept of TclBlend2d is the Surface.
A Surface comes with an internal pixmap (32bit depth, with alpha support) and holds all of the graphics state parameters that describe how drawing is to be done. This includes parameters like the current line width, the current color (or gradient), a 2D transformation matrix and many other things.
It allows the actual drawing function to take fewer arguments to simplify the interface.
A Surface implements an immediate-mode rendering; there's no concept of 'scene' or display-list like in the tk-canvas widget; if you want to delete or move a part of the pixmap, you should erase everything and restart drawing from the beginning.
The whole graphic-state can be saved on an internal Surface stack. Any subsequent changes to the graphics state can then be undone quickly by simply restoring the previous graphics-state.
TclBlend2d provides support for simple and complex geometrical entities.
Among the simple geometrical entities, you can find lines, arcs, (rounded) rectangles and many others.
A Path is a complex geometrical entity made of lines and curves (quadratics and cubics Bezier's curves).
A Surface provides just two methods for drawing the geometrical entities: fill and stroke. A geometrical entity can be stroked or filled with a style, i.e a solid color, a gradient, or a pattern.
When you stroke/fill a geometrical entity, you are not limited to use an uniform color; when you do a stroke/fill, you apply a style. A style can be
Styles may have their own alpha-transparency, and when you do a fill/stroke, pixels are blended with the pixels already stored in the internal pixmap.
You can set a composition op to control how the new colors are blended with the destination.
TclBlend2d provides support for controlling how a (complex) stroke is rendered: caps, joins, miter, dashes, ...
By default the coordinates you specify (user-coords) coincide with the coords of the internal pixmap (pixmap-coords), being (0.0 0.0) the top-left corner.
You can set and combine a transformation matrix (translation, rotation,scale,...), then all the following coordinates you specify with the fill/stroke methods are accordingly multiplied.
These transformations are part of the whole Surface's graphics-state and then they can be pushed on the Surface's stack.
TclBlend2d provides support for simple text layout. It can also parse and extract glyphs from the most common font-files.
Glyphs can also be transformed in Path (i.e. set of curves) and then analytically manipulated in terms of contours and single curves.
TClBlend2d has builtin support for reading BMP, PNG, QOI and JPG files.
Currently, support for writing is limited to BMP, PNG, QOI but you can save the generated image in a tk-photo and then save it in any other format ...
Note that you can import/save just a part of an image (a tk-photo or another Surface), and when you import an image in a Surface, the image is transformed based on the current transformation matrix, i.e. it is properly scaled/rotated.
This concludes this short introduction to the basic features and concepts for working with TclBlend2d.
For further details, read the Getting-started section, look at the demos included with the package, an read the reference manual...
How to create and view an image generated with blend2d ?
Let's create a very simple image with Blend2d
package require Blend2d BL::Surface create mySfc mySfc clear mySfc fill [BL::circle {200 200} 50] -style [BL::color orange] ... you got the idea ...
then we could:
mySfc save "./image01.bmp"
image create photo photo1 mySfc writeToTkphoto photo1
... and then embed photo1 in a widget, or save it in a file ...
But we could also create a totally new kind of image that can be embedded in a widget and manipulated in real-time...
You already know how to display a tk-photo in a widget like a canvas or a label. tclBlend2d provides a new type of image named "blend2d".
image create blend2d mySrf1
"mySrf1" is both a new image and a BL::Surface object (i.e. a command). So, you can embed this image in a widget
label .a -image mySrf1 pack .a
and then draw on "mySrf1" ...
mySrf1 clean mySrf1 fill [BL::circle {100 100} 80] -style [BL::color red] ...`
You can continue to draw in mySrf1, and instantly see the results in the label-widget.
tclBlend2d 1.3 tclBlend2d Tcl meets Blend2d
Tcl meets Blend2d
package require Tcl 8.6
package require Blend2d ?1.3?
Package Blend2d integrates the Blend2d vector engine in Tcl/Tk. Blend2d is an open source, high-quality, high-performance vector graphics engine. Blend2d is a binary package, distributed in a multi-platform bundle, i.e. it can be used on
Just an example to get the flavor of how to use Blend2d:
# draw a circle ... package require Blend2d set sfc [BL::Surface new] $sfc clear $sfc configure -fill.style [BL::color orange] $sfc fill [BL::circle {150 150} 100] $sfc save "./image01.bmp" $sfc destroy
You can run Blend2d from a tclsh interpreter, without loading Tk. The following command
package require tclBlend2d
can be used in a tclsh interpreter to load the package without requiring Tk support. You will be still able to generate and save images, but of course, some subcommands related to Tk won't be available. The command
package require tkBlend2d
loads the full package (and requires Tk). Note that
package require Blend2d
is equivalent to
package require tkBlend2d
The main concept of Tcl-Blend2d is the Surface. A Surface comes with an internal framebuffer (32bit depth, with alpha support) and holds all of the graphics state parameters that describe how drawing is to be done. This includes parameters like the current line width, the current color (or gradient), a 2D transformation matrix and many other things. A Surface can be created with the following commands
Note: the full stack of options is not duplicated; only the current options are duplicated.
A drawing state consists of
Surface options are:
WARNING: When user sets a new -format, the previous content of the framebuffer is lost, and the new framebuffer is uninitialized (it contains garbage). It is user's responsibility to clean it or to properly restore the previous contents. Default is {400 400 PRGB32}
Meta matrix is a core transformation matrix that is normally not changed by transformations applied to the context. Instead, it acts as a secondary matrix used to create the final transformation matrix from meta and user matrices. Meta matrix can be used to scale the whole context for HI-DPI rendering or to change the orientation of the image being rendered, however, the number of use-cases is unlimited. To change the meta-matrix you must first change user-matrix and then call the userToMeta method, which would update meta-matrix and clear user-matrix.
See the "Setting a style" section below.
There are 3 types of styles you can set for strokes and fills: SOLID, GRADIENT, PATTERN
RR, GG, BB are integers 0..255 (best expressed as 0x00..0xFF), alpha is an optional parameter ranging from 0.0 (transparent) to 1.0 (opaque). Default alpha is 1.0
colorName is a color name (e.g "lightblue") or a numeric-color like #rrggbb, alpha is an optional parameter ranging from 0.0 (transparent) to 1.0 (opaque). Default alpha is 1.0
See the "HSB/HSL color models" section at the end for more details.
# define an oblique LINEAR gradient set gr1 [BL::gradient LINEAR {0 0 400 400} \ [list 0.0 [BL::color lightblue] 0.8 [BL::color blue] 1.0 [BL::rgb 0 0 0 0.1]] \ ] $sfc fill [BL::circle {200 200} 100] -style $gr1
Valid options are:
Blend2D provides both simple geometric types ( line, rectangle, circle ....) and complex geometric types (Path). The main difference between simple and complex geometry types derives from their implementation. Although all the geometric types could be implemented as oo-classes, this will tend to develop programs difficult to maintain, since in Tcl oo-objects should be explicitly destroyed. Therefore most of the following commands for building geometric types don't return oo-objects but simple tcl-lists/dictionaries, that are automatically disposed when they go out of scope. Currently, just two complex geometry-types (BL::Path and BL::Svgdoc) are implemented as oo-class, (and then it's programmers's responsibility to explicitly destroy it). A simple example for drawing a simple geometry is
$sfcName fill [BL::box {0 0} {120 175.8}]
The supported simple-geometries are:
It's possible to align the text differently, using the -anchor option, whose possible values are: alignment on the text baseline
Option -alpha is 0.5 for the centripetal splines (default), 0.0 for the uniform splines, 1.0 for the chordal splines. If mode is not specified, then this is a canonical representation and it requires at least 4 points. If mode is extend, then the control points are implicitly extended by adding a first and last point. In this way, the spline goes through all its explicit control points. Even if mode is close, then some control points are implicitly added, so that the spline becomes a closed curve. Note that if mode is close, then the last explicit control point should not be equal to the first explicit control point.
... $sfc configure -stroke.style [BL::color yellow] # a minimal canonical spline (4 points): the first and the last point are not drawn $sfc stroke [BL::spline {0 0} {100 100} {300 100} {200 200}] # an extended spline (3 points): all the 3 control points are interpolated $sfc stroke [BL::spline extend {100 100} {300 100} {200 200}] # a minimal close spline (3 points) $sfc stroke [BL::spline close {100 100} {300 100} {200 200}]
Note: default splines (those with -alpha 0.5) are C1 cubic Catmull-Rom centripetal splines. Note that all these commands defining simple geometry types start with a lowercase letter. These commands do not create oo-objects; they simply return a specially crafted list that should be passed to the fill/stroke methods. These objects (lists/dictionaries!) don't require an explicit "destroy" method. Other than simple geometries there are complex geometries like BL::Path and BL::Svgdoc and they will be described in the next sections.
This method also accepts the option -transformation.
This method also accepts the option -transformation as for the stroke method.
This method returns the trasformation matrix applied over the current coordinate-system for translating&scaling the SVG-image. Extra options can be specified for automatic scaling or aligning other notable control points....
Note that if -autoresize is activated, and the surface's anchorpoint is not within the surface, then nothing is drawn, because with -autoresize ALL the svgName should be contained within the visible section of the surface. In this case the return transformation matrix is {}.
set M1 [$sfc cget -matrix] set M2 [Mtx::rotation 30 degrees] $sfc applyTransform $M2 set M3 [$sfc cget -matrix] # --> M3 == M2*M1
MetaxMtx <-- UserMtx * MetaMtx UserMtx <-- IdentityMtx
A Path is a complex shape made of b-curves (Bezier curves, including straight lines). The following commands can be used for creating and manipulating a Path:
Valid options are:
Hint: Use CCW for adding holes in a path
# starting from Blend2d 1.0, the "add" method also accepts a "BL::text" as a geometry. # All the glyphs are converted and added to a BLPath using a simple layout algorithm set fontFace [BL::FontFace new "./Arial.ttf"] set fontName [BL::Font new $fontFace 12.0] set blPath [BL::Path new] $blPath add [BL::text {100 100} $font "ABC .. Z"] # then you can get and manipulate its SVG representation set SVG [$blPath view] ...
# build path0 as a simple triangle set path0 [BL::Path new] $path0 add [BL::polygon {100 100} {150 200} {200 200}] # then derive a new path ... as the previous path but with a thick contour and rounded corners ..' set path1 [$path0 newStrokedPath -width 20 -join ROUND] ... remember to destroy path0 and path1
set blPath [BL::Path new] # the following SVG-path is presented in this way just for readability .. $blPath addSVGpath " M 100 100 q -100 0 -200 -100 l 10.0 20.1 30 40 50 -5 h 1.5e+3 Z" # but it can also be specified in a compact form $blPath addSVGpath "M100+100q-100+0-200-100l10.0,20.1,30,40,50-4H2E+3h1.5e+3Z"
if matrix is a 3x3 matrix (i.e a list of 9 numbers), then a planar-perspective-transformation is applied. **NOTE** This perspective transformation is not completely correct from a mathematical point of view, in the sense that the curves that form a BL::Path are not totally correctly deformed; only the control points of these curves are deformed. In practice, all the straight segments are correctly deformed, and the error in the curves is perceptible only in the case of accentuated perspective deformations.
Note that bbox does not consider the line-width, offset, caps (these parameters are defined when stroking/filling the path). If path is empty returns {0.0 0.0 0.0 0.0}
Within a Path, a contour is a sequence of connected b-curves. A non-empty Path may contain one or more contours; contours can be open or closed.
Length is calculated using numerical approximation. (see above tolerance).
If contour-index i is out of range, result is {}. If * is specified, it returns a list with the curveOP evaluation at rLen for each contour. Note that if the coefficient T corresponds to a junction-point of two b-curves, the result for the tangent, normal, tangentAt, normalAt, curvature may be undefined (i.e {})
If the countour is closed, the last point ((N+1)-th point) coincides with the first one.
The following methods require to specify two indices, i and j; i is the contour-index, j is the b-curve-index. As usual, i and j can be '*', meaning 'all contours' and 'all b-curves'.
If * is specified for the contour-index, the evaluation is performed on each contour, returning a list of values; similarly if the curve-index is *. If * is specified for both the contour-index and the curve-index, this command returns a list of lists, i.e. for every contour returns a list with the lengths of each of its b-curves.
A Svgdoc is an internal representation of the contents of an SVG file. There are no methods for manipulating a SvgDoc, other than for creating (loading an SVG-file), painting on a Surface (see above the method paint) and of course unloading/destroying. A Svgdoc is built by using a third-party library (https://github.com/Wiladams/svgandme ) providing a "[..] fairly complete library, supporting most of the SVG features found in typical usage today." The following commands can be used for creating and manipulating a Svgdoc:
In case a given SVG-file makes use of external fonts, these fonts are searched among the loaded fonts, and if not found, a default-loaded font is selected. You can use these two command for controlling the available loaded fonts.
NOTE* An error is raised if one font-file cannot be loaded, but no details are returned ... THIS SHOULD BE FIXED ...
Note: Currently text support is still basic and subject to changes. Before drawing some text, you need to load some fonts from an external font-file.
If fontfile is a font collection, you can specify which fontface to load. Default value for faceIdx is 0 (i.e. the first fontface). if faceIdx is greater than the number of the available fontfaces, the last fontface is loaded, and it can be inspected with the details method.
These are the currently listed properties; more properties may be added in future Blend2d releases.
# load the last fontface from a fontfile-collection # ("AmericanTypewriter.ttc" can be found in the tclBlend2d-devkit distribution ) # Note that I want to load the last fontface, so I specify a large 'faceIdx' # surely greater than the available fontface (.. there're 6 fontfaces in this collection ..) set fface [BL::FontFace new "./AmericanTypewriter.ttc" 999] # pretty print details dict for {key value} [$fface details] { puts "[format "%25s %s" $key $value]" } # .... # other ops ... # $fface destroy
This produces the following output :
faceIndex 5 glyphCount 916 fullName American Typewriter Condensed Light familyName American Typewriter subfamilyName Condensed Light postScriptName AmericanTypewriter-CondensedLight unitsPerEm 1000 weight 300 style 0 stretch 3 hasCharToGlyphMapping 1
Once a BL::FontFace has been loaded, and before drawing some text or extracting some glyphs, you should create a BL::Font object based on an instance of BL::FontFace
Note that although any text and glyph can be arbitrarily scaled with the usual 2D transformations, fontsize can be used to select some special glyphs that some fonts may make available for working with very small font sizes.
set fontFace [BL::FontFace new "./Arial.ttf"] set fontName [BL::Font $fontFace 12.0] set sfc [BL::Surface new] $sfc fill [BL::text {100 100} $fontName "Hello World"] -style [BL::color orange]
but it can also used for extracting single glyphs from it.
Note: this method creates a new BL::Pathinstance, and it is user's responsibility to destroy it explicitly. Before drawing some text (or a single glyph) you should load a fontfile, then setup a BL::Font with a given size
set aFontFace [BL::FontFace new _fontfile_] set aFont [BL::Font new $aFontFace _size_]
note that both BL:FontFace and BL::Font create new objects, and therefore it's programmer's responsibility to delete them (e.g call "$aFontFace destroy" ) The easiest way to draw a text on a Surface is to use the special 'geometry' BL::text with the fill/stroke methods
surfaceName fill [BL::text {10 20} $aFont "Hello World!!"]
Of course you can set the drawing-properties of the Surface as usual (color, gradient, line width, matrix transformation ....) Alternatively, you can extract a single glyph from a font, store it as a BL::Path, and then manipulate it as usual
set aGlyph [$font glyph 44] ;# extract glyph n.44 $sfc stroke $aGlyph
Note that the glyph method returns a new BL::Path object,and therefore it is programmer's responsibility to free the resources (e.g. "$aGlyph destroy" )
TclBlend2d provides two basic ways to work with filters. You can apply a filter to a rectangular region of a Surface (currently only blur and bw filters), or you can set a filter to a script so that it will be applied to all the graphical primitives that will be rendered by this script.
Valid options are:
If -rect is not specified, the filter will be applied to the whole surface.
Valid options are:
If -rect is not specified, the filter will be applied to the whole surface.
Parameters are:
Note that if this script changes the state of the (redirected) sfcName, these changes will be also visible in the original sfcName. Warning: take care of not "popping" the initial stack level of sfcName. Method push and pop are allowed within script as long as they are properly paired.
$sfc reset $sfc clear -style [BL::color white] # # --- a shadowed blue/white/red disc # set center {100 150} $sfc filter shadow -radius 20 -dxy {5 9} { foreach circleRadius {90 60 30} color {lightblue white red} { $sfc fill [BL::circle $center $circleRadius] -style [BL::color $color] } } # # --- three shadowed discs # set center {300 150} foreach circleRadius {90 60 30} color {lightblue white red} { $sfc filter shadow -radius 20 -dxy {5 9} { $sfc fill [BL::circle $center $circleRadius] -style [BL::color $color] } }
Blend2d provides commands for loading graphics files in a Surface, as well for saving the Surface's internal framebuffer in a graphic file. Blend2d provides commands for copying (part of) the internal framebuffer among different Surfaces. If the Tk support is loaded, that is if you loaded the Blend2d or tkBlend2d packages, you can also exchange parts of the Surfaces framebuffer with tk photo images.
WARNING: the internal framebuffer is resized.
NOTE: Currently only BMP, PNG and QOI encoders are available
The following options may be specified:
Note that if there's a matrix-trasformation (rotation, scaling, ..) on the current surface, this transformation will be applied to all points of the destination sub-region (i.e. the -from rectangle will be rotated, scaled, ...)
The default -compop mode is SRC_OVER.
These commands require the Blend2d or tkBlend2d package. These commands are not available if you loaded the tclBlend2d package; NOTE: points and rectangles below are specified in pixel-coords. This means that pixels-coords are independent of the current transformation matrix; no rotation or scaling is applied.
The following options may be specified:
tkphoto will be expanded to include the the source area, unless the user has specified an explicit image size with the -width and/or -height widget configuration options (see photo(n)); in that case the source area is silently clipped to the image boundaries. The following options may be specified:
These commands require the "Blend2d" or "tkBlend2d" package. These commands are not available if you loaded the "tclBlend2d" package;
Options are the same options used for the "BL::Surface create .." command. The image can then be embedded in a widget (like a "label" or a "canvas"); every command like fill or stroke issued to the image name, will immediately change the displayed image. Both "image delete sfcName" and "sfcName destroy" can be used to delete the image AND the related surface-object.
For each supported graphic file formats, returns a detailed list made of 5 elements: id, vendor, mimeType, extensions, features.
Blend2d provides some small helpers for working with transformation-matrix and colors
An affine matrix is a 3x3 matrix whose last column is fixed 0 0 1
a b 0 c d 0 e f 1
Given this rule, it is convenient to express such matrices as a list of 6 numbers { a b c d e f } instead of 9 numbers. Working with these matrices can be simplified by using the Mtx package included in Blend2D. In the following paragraphs "M" stands for a matrix (a list of 6 numbers), "P" stands for a 2D point (a list of 2 numbers). The following commands are supported
A 3x3 matrix is an extension of an affine matrix and it could be used for applying a Planar Perspective Transformation to a BL::Path (see method "$pathObj apply ..."). This matrix is expressed as a list of 9 numbers, and as a convenience it can be computed with Mtx::quadtoquad
Note that this command may raise an error if quadrilaters are degenere (e.g. non-convex quadrilaters) or more generally if ther's no transformation from quad1 to quad2.
Blend2d internally works with colors expressed in terms of red,green,blue and alpha channels, but in some cases it is more natural to express color following the HSB or HSL color model, where:
The following commands are available for converting between between ARGB and HSB or HSL color models, as well for interpolating color in the HSB or HSL color-space.
graphics
vector graphics
Copyright (c) 2021..2024 - tclBlend2d - A.Buratti Copyright (c) 2017..2024 - Blend2d - Blend2D Team Copyright (c) 2024 - SvgAndMe - William Adams
ABU 12-Jul-2020
TclTk binding for Blend2d is almost ready .. Just some fixing for the last Blend2d features (multithread rendering) and for a more tcl-ish API.
In the meantime, you can play with the new pixmix 2.x demos. pixmix includes a preliminary tcl-Blend2d engine and, although Blend2d is a vector graphics engine, it provides amazing performances even for working with large (fullscreen) bitmaps.
ABU 18-Oct-2019
Blend2D is a high-performance 2D vector graphics engine. See https://blend2d.com
Credits to https://ciechanow.ski/alpha-compositing/
TclTk binding is still at alpha stage, performances are amazing.
Here is a demo screenshot. Rotation/zoom at full-screen resolution in real-time (no flickering)
More to come..
arjen - 2020-07-17 09:43:26
Very nice - I followed the instructions and it works as described :). Haven't tried anything complicated yet, but this is encouraging.
ABU Thanks. I'm going to write a better reference manual and a sort of tutorial. Let me know what sounds complicated..
arjen - 2020-07-20 06:40:52
Wrt "complicated": I merely meant that I varied on some of the commands in the documentation and this page to see how things work. And I had a look at the demos. Especially rotating the tiger image was impressive.
EB - 2023-04-21 06:59:52
The result is pretty impressive. I had a look at the previous version and didn't find it so fast, but I will retry. I'm looking for a library for tkpath backend, the GDI+ one being a bit slow. Another alternative backend for tkpath could be skia , whose API fits very well. Another use for such library could be to create ttk element. The image element is pretty slow and such library would open a lot of possibilities.
Using Blend2d as a backend for tkpath could be a good idea. Blend2d is platform agnostic, does not require any external lib, nor GPU support, so there's no need for any diff for Windows/MacOS/Linux ...
This project of mine, TclBlend2d, had a different inspiration and goal: provide a Processing-like library for Tcl .I mean someting like processing.org, or p5js.org. I must say that I'm quite satisfied; I can code in Tcl (almost) everything I can code in Processing (except OpenGL shaders, ... but that's a totally different topic .. ) with astonishing performances.
NR - 2023-09-28 12:05:26
I find the thickness of my lines different, my first line seems much thicker than my following lines, even though the thickness is set to the same value. There is an adjustment to be done, or a misunderstanding on my side. Your demos are just amazing and beautiful and mine is not very nice...
package require Blend2d 1.0.1 set sfc [image create blend2d -format {500 400}] $sfc configure -fill.style [BL::color white] $sfc clear set xmin 51.8 set ymin 362.0 set xmax 480.5 set ymax 20.0 $sfc stroke [BL::line [list $xmin $ymin] [list $xmax $ymin]] -width 1 -style [BL::color #6E7079] $sfc stroke [BL::line [list $xmin $ymin] [list $xmin [expr {$ymin + 5}]]] -width 1 -style [BL::color #6E7079] set distance [expr {($ymax - $ymin) / 5.0}] set start $ymin for {set i 0} {$i < 5} {incr i} { set start [expr {$start + $distance}] $sfc stroke [BL::line [list $xmin $start] [list $xmax $start]] -width 1 -style [BL::color #E0E6F1] } label .x -image $sfc -width 500 -height 400 pack .x -fill both -expand 1 #
ABU Try to look at those lines with a screen-magnifier (e.g. Meeazure for Windows). If you look at those horizontal lines having width (thickness) 1 pixel, you can see they are differently spread over two pixels. This is because a y-coord like 51.8 means that color is blended over pixels 51 and 52. If the decimal part were different (e.g, y=51.6), the true color of these pixels will be different again....
If you want horizontal lines exactly 1 pixel thick, you should put them *at the center of the pixel*, i.e. at y=51.5. Note the if you put the line at y=51.0 or at y=52.0, then the line is spread over two pixels again. Alternatively, you could set -stroke.width less than 1.0 . eg. 0.33. In this case you will observe lines spread exactly over1 pixel (less than one pixel is hard ...), but with a reduced color intensity (that's because 'theoretically' the line occupies just 1/3 of the pixel ..)
Color theory is complex, and I don't know the details about how the colors are blended, but this is how anti-aliasing is implemented in Blend2d.
NR - 2023-09-29 08:19:33
Thanks , it works. I still have a few questions.
ABU Currently support for font is incomplete and limited; text metrics will be available with the next release (I think within a couple of weeks at most)
ABU No. Blend2d is an API for immediate-mode graphics (see also tclcairo) . Blend2d has no concept of scene (or display list like in 'Tk-canvas or in zinc). Tk-canvas, or zinc are APIs for retained-mode graphics. If you want to delete a circle in immediate-mode graphics, you should clean all the Surface and then redraw everything but that circle. Blend2d is very similar to https://processing.org or https://p5js.org , Even without a scene, these languages can generate amazing images and animations that are impossible to generate with an API working in retained-mode.
NR - 2023-10-04 09:00:02
Okay, I thought it was possible, via these 2 blend2D functions BLContext::save() & BLContext::restore() .
ABU It depends on what you mean with 'the State of a Surface' . In your original question, you talked about saving the objects/primitives drawn on on Surface and then be able to change some parts of what you have drawn. This is perfectly doable with a retained-mode API such as Tk-canvas, but it's impossible to do with an immediate-mode API. Simply, after you paint some circles or lines.. all you have is a painted bitmap, no concept of circles, lines, and so on is 'retained'. It's up to you to repaint these (changed) graphic primitives. Regarding the cited BLContext::save restore methods, they are still present in tcl-Blend2d (renamed as push/pop), but they are just to save the current 'state of a Context', i.e the current stroke-width, the current stroke-style (color or gradient or color) as well as the fill-style, the current transformation matrix and so on. The 'state' is just a small set of drawing attributes, it's not a 'scene' or a 'display-list'.
On the tclBlend2d side, I suppose this won't be a problem, but on the Tcl side, looping through my list of coordinates each time is a performance issue.
ABU I hope you can take some inspiration from Blend2d Gallery, but in general, if you need to paint and update a complex, huge, mutable scene, you need a different tool, a tool like tk-canvas, able to retain, mark and manipulate a display-list. Anyway, if you can elaborate on what you want to draw, I could give you more specific tips
NR - 2023-10-04 14:12:58
Thank you for this clarification. My goal is to draw a surface, in this surface I would like to draw a rectangle which are the edges of my surface. In this rectangle, I would like to draw circles (hundreds to thousands of circles). Depending on the movement of my mouse and its coordinates, each time I find a circle, I'd like to enlarge the diameter of that circle, and vice versa, when I leave the previously enlarged circle, I'd like it to return to its initial state.
I'm trying to get some inspiration from HTML canvas and see if tclBlend2d can do the same thing (I think it can, I hope so!), but it's the Tcl side that gives me trouble...
ABU Well, with something like Tk-canvas this can be done with few lines of code (about..20 lines), but I imagine you plan to add more requirements Tk-canvas cannot support. Using Blend2d, you should store your huge list of circles and then scan the whole list every time the mouse moves. (..you can also think to implement a quad-tree subdivision of the 2D-plane, but this is stuff for C, not for Tcl) and "worse", when you hit the circle to modify, you should update your list of circles, and then redraw everything from scratch. I predict bad performances with 1000 circles. I can be wrong, but I've never seen anything like it done in Blend2d or friends ... I suggest to play with p5js.org; apart from the different language (Javascript vs Tcl), they are very similar in approach. If you like p5js approach, and you love Tcl, probably you will reconsider Blend2d (...warning ... , Blend2d just covers the core features of p5js; p5js provides a huge number of extended libraries)
NR - 2023-10-05 08:42:31
Thank you Aldo , for the advice , in the end your library does not correspond to my needs. I thought I could replace that ugly old Tk canvas... It's a pity.
RZ You could try tkpath from androwish.org or tko::path from https://chiselapp.com/user/rene/repository/tko
NR - 2023-12-27 12:21:22
My intention is to make this demo , but I find that the brightness is not perfect. Can you give me some advice? The author uses HSL here I use HSB, I don't know if that's the problem.
ABU HSB (aka HSV) and HSL are different color models (see https://en.wikipedia.org/wiki/HSL_and_HSV ).
Anyway you can use this conversion utility (it will be included in the next Blend2d release)
proc HSL {h s l {alpha 1.0}} { set v [expr {$l+$s*min($l,1-$l)}] set sat [expr {$v==0 ? 0.0 : 2*(1-$l/$v)}] HSB $h $sat $v $alpha }
# Ported from Rainbow Rain animation: # https://onaircode.com/awesome-html5-canvas-examples-source-code/ # https://codepen.io/towc/pen/VYbYvQ package require Blend2d proc HSL {h s l {alpha 1.0}} { # ABU conversion utility : set v [expr {$l+$s*min($l,1-$l)}] set sat [expr {$v==0 ? 0.0 : 2*(1-$l/$v)}] return [HSB $h $sat $v $alpha] } proc anim {} { global sfc w h dots dotsVel accelleration repaintColor global total occupation size portion $sfc clear -style $repaintColor for {set i 0} {$i < $total} {incr i} { set currentY [expr {$dots($i) - 1}] set dots($i) [expr {$dots($i) + $dotsVel($i) + $accelleration}] set hsl [HSL [expr {$portion * $i}] 0.8 0.5] ; # conversion utility $sfc fill [BL::rect [expr {$occupation * $i}] $currentY $size [expr {$dotsVel($i) + 1}]] -style $hsl if {($dots($i) > $h) && (rand() < 0.01)} { set dots($i) 0 set dotsVel($i) 2 } } update after idle anim } set w 800 set h 400 set total $w set accelleration 0.05 set size [expr {$w / $total}] set occupation [expr {$w / double($total)}] set repaintColor [BL::rgb 0 0 0 0.04] set portion [expr {360 / double($total)}] for {set i 0} {$i < $total} {incr i} { set dots($i) $h set dotsVel($i) 10 } wm title . "TclTk binding for Blend2d - Rainbow Rain animation" set sfc [image create blend2d -format [list $w $h]] label .x -image $sfc -borderwidth 0 ; pack .x anim
NR - 2023-12-27 18:14:01
Thanks, it's much better, I've modified my code to include your utility.
NR - 2023-12-28 19:29:15
Small contribution according to this demo .
set thisDir [file normalize [file dirname [info script]]] set auto_path [linsert $auto_path 0 [file dirname $thisDir]] # Ported from Fireworks animation: # https://onaircode.com/awesome-html5-canvas-examples-source-code/ # https://codepen.io/programking/pen/AJgeEd?editors=1111 package require Blend2d proc random {min max} { return [expr {rand() * ($max - $min) + $min}] } proc calculateDistance {p1x p1y p2x p2y} { set xDistance [expr {$p1x - $p2x}] set yDistance [expr {$p1y - $p2y}] return [expr {pow($xDistance, 2) + pow($yDistance, 2)}] } proc setParticle {value} { global PARTICLECOUNT set PARTICLECOUNT [expr {int($value)}] } proc createParticles {x y} { global PARTICLES PARTICLECOUNT for {set i 0} {$i < $PARTICLECOUNT} {incr i} { lappend PARTICLES [Particle new $x $y] } } proc HSL {h s l {alpha 1.0}} { set v [expr {$l+$s*min($l,1-$l)}] set sat [expr {$v==0 ? 0.0 : 2*(1-$l/$v)}] return [HSB $h $sat $v $alpha] } proc loop {} { global HUE SFC REPAINTCOLOR FIREWORKS PARTICLES global TIMERTICK TIMERTOTAL MOUSEDOWN CW CH MX MY global LIMITERTICK LIMITERTOTAL PARTICLECOUNT FONT set HUE [expr {$HUE + 0.5}] $SFC clear -style $REPAINTCOLOR $SFC fill [BL::text {0 21} $FONT "Particle count : $PARTICLECOUNT"] -fill.style [HSL $HUE 1 0.5 0.8] $SFC fill [BL::text {835 21} $FONT "Click on the surface..."] -fill.style [HSL $HUE 1 0.5 0.8] for {set i [expr {[llength $FIREWORKS] - 1}]} {$i >= 0} {incr i -1} { set obj [lindex $FIREWORKS $i] $obj draw $obj update $i } for {set i [expr {[llength $PARTICLES] - 1}]} {$i >= 0} {incr i -1} { set obj [lindex $PARTICLES $i] $obj draw $obj update $i } if {$TIMERTICK >= $TIMERTOTAL} { if {!$MOUSEDOWN} { lappend FIREWORKS [Firework new [expr {$CW / 2.}] $CH [random 0 $CW] [random 0 [expr {$CW / 2.}]]] set TIMERTICK 0 } } else { incr TIMERTICK } if {$LIMITERTICK >= $LIMITERTOTAL} { if {$MOUSEDOWN} { lappend FIREWORKS [Firework new [expr {$CW / 2.}] $CH $MX $MY] set LIMITERTICK 0 } } else { incr LIMITERTICK } update after idle loop } oo::class create Firework { variable _x variable _y variable _sx variable _sy variable _tx variable _ty variable _distanceToTarget variable _distanceTraveled variable _coordinates variable _coordinateCount variable _angle variable _speed variable _acceleration variable _brightness variable _targetRadius constructor {sx sy tx ty} { set _x $sx set _y $sy set _sx $sx set _sy $sy set _tx $tx set _ty $ty set _distanceToTarget [calculateDistance $sx $sy $tx $ty] set _distanceTraveled 0 set _coordinates {} set _coordinateCount 3 for {set i 0} {$i < $_coordinateCount} {incr i} { lappend _coordinates [list $_x $_y] } set _angle [expr {atan2($ty - $sy, $tx - $sx)}] set _speed 2 set _acceleration 1.05 set _brightness [random 50 70] set _targetRadius 1 } method update {index} { global FIREWORKS set _coordinates [lrange $_coordinates 0 end-1] set _coordinates [linsert $_coordinates 0 [list $_x $_y]] if {$_targetRadius < 8} { set _targetRadius [expr {$_targetRadius + 0.3}] } else { set _targetRadius 1 } set _speed [expr {$_speed * $_acceleration}] set vx [expr {cos($_angle) * $_speed}] set vy [expr {sin($_angle) * $_speed}] set _distanceTraveled [calculateDistance $_sx $_sy [expr {$_x + $vx}] [expr {$_y + $vy}]] if {$_distanceTraveled >= $_distanceToTarget} { createParticles $_tx $_ty set FIREWORKS [lreplace $FIREWORKS $index $index] } else { set _x [expr {$_x + $vx}] set _y [expr {$_y + $vy}] } } method draw {} { global SFC HUE FONT PARTICLECOUNT set hsl [HSL $HUE 1 $_brightness] set path [BL::Path new] $path moveTo [list [lindex $_coordinates end 0] [lindex $_coordinates end 1]] $path lineTo [list $_x $_y] $path close $path arcTo [list $_tx $_ty] [list $_targetRadius $_targetRadius] 0 [expr {3.14 * 2}] $SFC stroke $path -style $hsl $path destroy } } oo::class create Particle { variable _x variable _y variable _coordinates variable _coordinateCount variable _angle variable _speed variable _friction variable _gravity variable _hue variable _brightness variable _alpha variable _decay constructor {x y} { global HUE set _x $x set _y $y set _coordinates {} set _coordinateCount 5 for {set i 0} {$i < $_coordinateCount} {incr i} { lappend _coordinates [list $_x $_y] } set _angle [random 0 [expr {3.14 * 2}]] set _speed [random 1 10] set _friction 0.95 set _gravity 1 set _hue [random [expr {$HUE - 20}] [expr {$HUE + 20}]] set _brightness [random 50 80] set _alpha 1 set _decay [random 0.015 0.03] } method update {index} { global PARTICLES set _coordinates [lrange $_coordinates 0 end-1] set _coordinates [linsert $_coordinates 0 [list $_x $_y]] set _speed [expr {$_speed * $_friction}] set _x [expr {$_x + cos($_angle) * $_speed}] set _y [expr {$_y + sin($_angle) * $_speed + $_gravity}] set _alpha [expr {$_alpha - $_decay}] if {$_alpha <= $_decay} { set PARTICLES [lreplace $PARTICLES $index $index] } } method draw {} { global SFC HUE set path [BL::Path new] $path moveTo [list [lindex $_coordinates end 0] [lindex $_coordinates end 1]] $path lineTo [list $_x $_y] $SFC stroke $path -style [HSL $HUE 1 $_brightness $_alpha] $path destroy } } set CW 1000 set CH 600 set PARTICLES {} set FIREWORKS {} set HUE 120 set LIMITERTOTAL 5 set LIMITERTICK 0 set TIMERTOTAL 80 set TIMERTICK 0 set MOUSEDOWN false set REPAINTCOLOR [BL::rgb 0 0 0 0.5] set MX 0 set MY 0 set PARTICLECOUNT 30 wm title . "TclTk binding for Blend2d - Fireworks" wm resizable . 0 0 set SFC [image create blend2d -format [list $CW $CH]] label .x -image $SFC -borderwidth 0 ttk::scale .scale -orient h -from 30 -to 600 -variable PARTICLECOUNT -command setParticle pack .x pack .scale -expand 1 -fill both set fontface [BL::FontFace new "$thisDir/Blacksword.otf"] set FONT [BL::Font new $fontface 20.0] bind .x <Motion> {set MX %x ; set MY %y} bind .x <ButtonPress> {set MOUSEDOWN true} bind .x <ButtonRelease> {set MOUSEDOWN false} loop
arjen - 2024-07-16 07:26:49
I have had a look at the documentation - quite useful :) - but I have a few questions still:
Let's forget for a moment these filters. Currently tclBlend2d has 2 special effects that can be applied to a BL::Surface (or to a rectangular region of the surface): $sfc blur .... $sfc bw .... These methods are applied to what is already rendered on the Surface. Now, filters .... Filters are more powerful (.. I know, they would required a better documentation ...) Filters are for applying some special effect to what are you going to draw. Currently tclBlend2d has 3 types of filters: blur, bw and shadow Within the demo directory you can find many small programs you can play: demo/sample127-spheres.tcl - draw some random circles with a shadow. demo/sample121-blur.tcl - draw some 'waves' with a different blur factor. .. and many others. I suggest to take some of these demos and replace $sfc filter xxx ... { ... some graphical ops on $sfc } with "ignore" $sfc filter "ignore" ... { ... some graphical ops on $sfc } in this way all the graphical ops (stroke, fill, copy ...) are rendered in the normal way. Note that, by design, filters are not applied to a single graphics object; they are applied to a tcl-script, or better, to all the graphic-objects that will be drawn by that script.
Clipping is not supported ... well, there're two "experimental" methods named "setclip" and "resetclip", but they'are intentionally undocumented/unsupported because they work only on a rectangular region, and I'm waiting for a better implementation from the Blend2d-core library. About the drawing order, this is the plain "painter algorhytm". Let me know if you have a particular problem of drawing order; there're many tricks ..
arjen - 2024-08-05 14:40:37
Quick question: is it possible to set individual pixels o shoul dyou use short line segments for that? Use case: things like Fern Fractal
ABU Quick answer: no. Blend2d (core library) does not expose methods for single pixel manipulation (and if there were, they would be extremely inefficient to be called by Tcl). I suggest to use small circles or small rectangles
e.g. $surface fill [BL::rect $x $x 1 1]
arjen - 2024-08-06 14:13:49
Clear enough - worth experimenting with :).