Jump to content

GSoC/2020/StatusReports/LeonardoEmanuelSegovia: Difference between revisions

From KDE Community Wiki
Lsegovia (talk | contribs)
Lsegovia (talk | contribs)
Fix broken URLs reported by Iván Yossi
 
(33 intermediate revisions by the same user not shown)
Line 1: Line 1:
== Dynamic Fill Layers in Krita using SeExpr ==
== Dynamic Fill Layers in Krita using SeExpr ==
[[File:PepperCarrot ep33 deevad.jpg|frame|center|💙 Energy #krita #seexpr (source: [https://framapiaf.org/@davidrevoy/104400422779912978 David Revoy on Framapiaf]) ]]


=== Abstract ===
=== Abstract ===
Line 26: Line 28:
* ARM support for AppImages (including this project’s deliverables as well)
* ARM support for AppImages (including this project’s deliverables as well)


=== Fill Layer generator ===
=== SeExpr-based Fill Layer generator ===
 
Enabling SeExpr as a Fill Layer generator involves two stages.
 
* Integration of SeExpr as a 3rdparty dependency
** Commits: [https://invent.kde.org/graphics/krita/-/merge_requests/380/diffs?commit_id=1c83393af727b941dcde248f29b4524bf5e1d21b 1], [https://invent.kde.org/graphics/krita/-/merge_requests/380/diffs?commit_id=1c83393af727b941dcde248f29b4524bf5e1d21b 2], [https://invent.kde.org/graphics/krita/-/merge_requests/380/diffs?commit_id=5ebfc3bd1f145dbbf4a0f6a6fd70c36d55fe87f7 3], [https://invent.kde.org/graphics/krita/-/merge_requests/380/diffs?commit_id=0ae44fc6f5eb017bd2c0a125b3f96c4ca718c1ed 4]
* Defining the new generator
* Loading, rendering, and saving the script with the layer
** [https://invent.kde.org/graphics/krita/-/merge_requests/380/diffs?commit_id=0638ce859445f65bb3b7eaceabac9e51a1d05bc8 This commit] implements both items.


WIP: https://phabricator.kde.org/T13337
The SeExpr generator is divided into three components:
 
* <code>SeExprExpressionContext</code> and <code>SeExprVariable</code> are the glue between SeExpr and Krita; the former contains the script’s execution context, the latter exposes the necessary variables for rendering the script (width, height, and pixel location in normalized coordinates).
* <code>KisSeExprGenerator</code> is the instance of <code>KisGenerator</code> tasked with rendering the script to the layer’s paint device. It sets up the <code>SeExprExpressionContext</code> with the given script and variables. Then, it iterates over the paint device, rendering the script on each pixel and converting the resulting color back to Krita’s color space.
* <code>KritaSeExprGenerator</code>, <code>WdgSeExpr</code> and <code>WdgSeExprSavePreset</code> comprise the UI of the SeExpr generator. These are described in more detail in the next section.
 
It must be noted that throughout the development of the SeExpr generator, extensive changes were made to Disney’s work itself. Of particular note are the following:
 
* Warnings and deprecations were solved across all operating systems I worked on.
** Commits: [https://invent.kde.org/lsegovia/seexpr/-/commit/1e497d030ac3816dca98b2aa4ef2efb65ca29d9f 1], [https://invent.kde.org/lsegovia/seexpr/-/commit/356c219272af3c5d99569df3a07d92c31c210fc1 2], [https://invent.kde.org/lsegovia/seexpr/-/commit/936d63854d75a7387caa5b574feb6d98c13e3fba 3], [https://invent.kde.org/lsegovia/seexpr/-/commit/f581821c9f7fd5e44cd5c2ca8508b393a8b677fc 4], [https://invent.kde.org/lsegovia/seexpr/-/commit/b32674aaa741fcebb6819fc82ea43e78d75a765f 5], [https://invent.kde.org/lsegovia/seexpr/-/commit/b3a158198f355be545cbdad8740e5b09a85cc9f9 6], [https://invent.kde.org/lsegovia/seexpr/-/commit/d09345361756447f9538724aa6962e4f910bd8d9 7], [https://invent.kde.org/lsegovia/seexpr/-/commit/e8a57e7774e15070fce5e7c8cd0a349bd29874a6 8], [https://invent.kde.org/lsegovia/seexpr/-/commit/58880c55c194857b4aac179abac1fbb17e433bba 9]
* The provided UI widgets were completely restyled for better accessibility.
** Commits: [https://invent.kde.org/lsegovia/seexpr/-/commit/996377ded2fef2955362f6d449d3d5902fa2e907 1], [https://invent.kde.org/lsegovia/seexpr/-/commit/eb9d1caba06ea3999f05a99c507de7d6b88333fb 2], [https://invent.kde.org/lsegovia/seexpr/-/commit/cb19738ab9f3f83bf9586cfbf70f14183e0310df 3], [https://invent.kde.org/lsegovia/seexpr/-/commit/f0f98ccd3ec1a5da3efeadf8fc6d282bcdec066e 4], [https://invent.kde.org/lsegovia/seexpr/-/commit/7bee2a0aa35c4ea84056b9eece6a3b99495f7c93 5], [https://invent.kde.org/lsegovia/seexpr/-/commit/7e6228f13f365273f9275cbbc654f4924d43ecff 6], [https://invent.kde.org/lsegovia/seexpr/-/commit/4c3d11a1a3e8192c13ff344516e1160f3771adc5 7], [https://invent.kde.org/lsegovia/seexpr/-/commit/237c4690b84eb4722ff112aab06ce4d958fc9ad3 8], [https://invent.kde.org/lsegovia/seexpr/-/commit/ad7dfd28306bf6c2b6c2a211f8d838a984e35d62 9]
* Unused or irrelevant items were hidden behind flags to further optimize compilation.
** Commits: [https://invent.kde.org/lsegovia/seexpr/-/commit/6bdc21a5ee2dfa23cda6a195af7855030a007357 1], [https://invent.kde.org/lsegovia/seexpr/-/commit/93e5b41372c718977a2a50746ab912703df081d1 2]
* The <code>ExprEditor</code> text widget [https://invent.kde.org/lsegovia/seexpr/-/commit/d9a7c387d1caeef675e1ff78cd8011003e703239 was decoupled] from <code>ExprControlCollection</code> (the manipulation widgets list). This enabled [https://invent.kde.org/graphics/krita/-/merge_requests/380/diffs?commit_id=f91e6e0df03a2237ec88d57800ebe246150197ba a complete migration] to Krita’s .ui widget system.
* <code>ExprTextEditor</code> [https://invent.kde.org/lsegovia/seexpr/-/commit/d3595251fa7df320265386ec63616f2312561337 now signals] when a change is made to the script text field.
* [https://invent.kde.org/lsegovia/seexpr/-/commit/1e008feb7e3bed158ef64d04ea842942a94bc3b4 A consistency change was made] in the naming of variables.
* By request from Agata Cacko, [https://invent.kde.org/lsegovia/seexpr/-/commit/4e36c6a71867698550421e242e8525c8760c7e57 variable insertion was modified] to insert them into a new line, and [https://invent.kde.org/lsegovia/seexpr/-/commit/d1465e5a83c6ba1af9222bef5f5f7e30c7fc97b5 comment were better differentiated from numbers].
 
The following bugs in SeExpr were fixed:
 
* As detailed on [https://www.amyspark.me/blog/2020/06/01/status-report-success.html these] [https://www.amyspark.me/blog/2020/06/04/status-update-linux.html blog posts], SeExpr implicitly assumed that it would be always executed in English-based locales. This was detected by Wolthera van Hövell.
** Commits: [https://invent.kde.org/lsegovia/seexpr/-/commit/6ffdfd08585aebff495d5580ba687b3cd9927267 1], [https://invent.kde.org/lsegovia/seexpr/-/commit/c01ffed707478d0ad2c690be969b30d9916217fb 2], [https://invent.kde.org/lsegovia/seexpr/-/commit/18e8cfe4f7d4ffada799590064345d83be5f23a2 3]
* The autocompletion support was unusable since it blocked numpad entry. This was fixed (commits: [https://invent.kde.org/lsegovia/seexpr/-/commit/354187fd4eac77bce833c3862cc6805e3e3f9cc0 1], [https://invent.kde.org/lsegovia/seexpr/-/commit/771f8b52315ff3eefa06dadeabc93792a08b3a7c 2]), along with an extra optimization to use native controls.
 
* A buffer underflow was fixed in <code>CCurveScene</code> (the UI widget for color curves) (commits: [https://invent.kde.org/lsegovia/seexpr/-/commit/bb7d81d1c379c9364bdcb778490ef08c6eeb9e4d 1])
* As part of the revision of the documentation, Wolthera van Hövell found a couple of bugs related to incorrect function documentation (commits: [https://invent.kde.org/lsegovia/seexpr/-/commit/b807bfebfec9edc4197ca3561f53625895edc96e 1])
* A crash when initializing vector variables outside the [0, 1] range (commits: [https://invent.kde.org/lsegovia/seexpr/-/commit/904c607d408cee1a9ea3eca02397b4aa62e15262 1], [https://invent.kde.org/lsegovia/seexpr/-/commit/48a1d837562ce5d243a3489519129e453e5a455c 2])
* Lack of promotion of vectors to colors after widget initialization (commits: [https://invent.kde.org/lsegovia/seexpr/-/commit/74737606c01c8a591659c1561b07b695c70f6997 1])
 
=== Creation and management of SeExpr scripts ===
 
<gallery mode="slideshow">
Krita-Artists-deevad-example-1.jpg|Example render by David Revoy.
Seexpr-management.jpg|SeExpr presets management UI.
Krita-Artists-deevad-example-2.jpg|Example render by David Revoy.
</gallery>
 
When I implemented the generator, the script was saved along with the layer. David Revoy proposed that scripts should be reusable, by making them bundleable as any other resource in Krita.
 
The initial implementation, shown in the screenshots below, is based on the existing Pattern chooser widget. The underlying <code>KoResource</code> specialization, which is called <code>KisSeExprScript</code> and located in <code>libs/flake/resources</code>, is an almost-straight port of Anna Medonosová’s Gamut Masks.
 
<ul>
<li style="display:inline-block;">[[File:SeExpr selection 1.png|thumb|none]]</li>
<li style="display:inline-block;">[[File:SeExpr selection 2.png|thumb|none]]</li>
</ul>
 
Data-wise, a SeExpr preset’s script is stored in a plaintext file named <code>script.se</code>, along with a thumbnail image in <code>preview.png</code>. These are bundled together in a ZIP of extension <code>.kse</code> and MIME type <code>application/x-krita-seexpr-script</code>.
 
In [https://phabricator.kde.org/T13337 T13337], I requested feedback from the developers on the UX for creating and editing these presets. Based on the existing workflow for managing brushes, I proposed the following mocks:
 
<ul>
<li style="display:inline-block;">[[File:SeExpr_Options_1.png|thumb|none]]</li>
<li style="display:inline-block;">[[File:SeExpr_save_1.png|thumb|none]]</li>
</ul>
 
Based on suggestions from Agata Cacko in both the Krita Artists feedback thread and the Phabricator task, the final form of the UX is as follows:
 
<ul>
<li style="display:inline-block;">[[File:SeExpr_selection_final_1.png|thumb|none]]</li>
<li style="display:inline-block;">[[File:SeExpr_Options_final_1.png|thumb|none]]</li>
<li style="display:inline-block;">[[File:SeExpr_error_reporting_final_1.png|thumb|none]]</li>
</ul>
 
By request from Agata, the manipulation widgets and the text editor were placed inside a <code>QSplitter</code>, to ensure users could freely choose how much space they want to assign each component.
 
By request from Wolthera, support was added for reporting validation errors. Since SeExpr was not designed with internationalization in mind, extensive refactoring was needed to extracts its strings [https://invent.kde.org/lsegovia/seexpr/-/commit/7a28649a47dbd78ce891955b14e7259f8b5f3f2a from the UI components] and [https://invent.kde.org/lsegovia/seexpr/-/commit/2dd6799933e86e7e25d1d9a8e5e5a3f7bed6d138 from the render library], [https://invent.kde.org/lsegovia/seexpr/-/commit/5e8be062ee48fae920ceefe4a3d5aded44d2b9c9 allow their future localization by the KDE team], and finally [https://invent.kde.org/lsegovia/seexpr/-/commit/75c41b41ea7f24d0710cf359c1fb5c2d3467fadc enable their usage in the widgets]. Additionally, variables exposed from Krita [https://invent.kde.org/graphics/krita/-/merge_requests/380/diffs?commit_id=97a446080c3c4de0e040466c366eec64fdab17cf were added to the autocompletion system].
 
<ul>
<li style="display:inline-block;">[[File:SeExpr_dirty_final_1.png|thumb|none]]</li>
<li style="display:inline-block;">[[File:SeExpr_save_final_1.png|thumb|none]]</li>
</ul>
 
While there were not many changes in the management UX, it was simplified into 4 actions. The unique contribution here is <kbd>Render Script to Thumbnail</kbd>; as its name says, it creates an instance of the SeExpr fill layer generator, points it to the thumbnail image’s <code>QPaintDevice</code>, and renders the given preset’s script into the thumbnail.


=== Integration tests ===
=== Integration tests ===
There are two testable points in the SeExpr generator:
* successful rendering from the given script
* loading and saving from a given <code>KoResource</code> instance
The first is already implemented in the SeExpr generator (commits: [https://invent.kde.org/graphics/krita/-/merge_requests/380/diffs?commit_id=ee3ace931b89aa5f000049c56978683a1367a753 1], [https://invent.kde.org/graphics/krita/-/merge_requests/380/diffs?commit_id=660b07167bb3d8c1e250eae13ae4783e9dfa0c59 2]). The second is NYI.
The first covers the SeExpr generator itself (commits: [https://invent.kde.org/graphics/krita/-/merge_requests/380/diffs?commit_id=ee3ace931b89aa5f000049c56978683a1367a753 1], [https://invent.kde.org/graphics/krita/-/merge_requests/380/diffs?commit_id=660b07167bb3d8c1e250eae13ae4783e9dfa0c59 2], [https://invent.kde.org/graphics/krita/-/merge_requests/380/diffs?commit_id=2c17dfc4116ed7331e97c350aa3051c53d2492de 3]).
The second covers serialization and deserialization from a known working <code>KoResource</code> (commits: [https://invent.kde.org/graphics/krita/-/merge_requests/380/diffs?commit_id=eab1ce4d4f4f040d62418197249c3bef049aff31 1]).


=== User documentation ===
=== User documentation ===


=== Multithreaded Fill Layers ===
<gallery mode="slideshow">
SeExpr_manual_1.jpg|SeExpr Fill Layer entry.
SeExpr_manual_2.jpg|SeExpr introductory tutorial.
SeExpr_manual_3.jpg|SeExpr quick reference manual.
SeExpr_manual_4.jpg|SeExpr resource entry.
</gallery>
 
Up to now, there was no complete reference for creating SeExpr scripts.
To build a complete reference for SeExpr scripts, my work created the following
documental resources in the Krita user manual:
 
* an entry to the Fill Layers section
* a specific page for SeExpr scripts in Resource Management
* a tutorial for creating, editing and bundling script presets
* a Quick Reference page detailing the expression language
 
The latter is one of the most important productions, as it involved porting
the existing User Documentation of Disney's (which is only available in
precompiled Doxygen form) to reStructuredText, while adapting it to suit
the script specification in Krita.
 
A fifth resource, this one provided with the Krita binaries, is a resource bundle comprising all examples in the Krita Artists thread, [https://krita-artists.org/t/procedural-texture-generator-example-and-wishes/7638 “Procedural texture generator (example and wishes)”]. The authors of the examples are Wolthera van Hövell and David Revoy; the latter employed an alpha version of this tool [https://framapiaf.org/@davidrevoy/104400422779912978 in the episode 33 of his webcomic, Pepper&amp;Carrot]. (commits: [https://invent.kde.org/graphics/krita/-/commit/2fb0bf9f91ccb864af0d91fecc2b955681832399 1], [https://invent.kde.org/graphics/krita/-/commit/2403de9640c6559d28fe54b5471a38b23aebc42c 2], [https://invent.kde.org/graphics/krita/-/commit/dfeec9fad6cb8743507fe77666fec01176559df5 3], [https://invent.kde.org/graphics/krita/-/commit/3205736d5fcca78c6ccc97f0527e2a4bb7199c3f 4])
 
=== Multithread support for Fill Layers ===
 
This is a '''stretch goal'''.
 
Originally, Fill Layers were rendered in a single, background thread. Although feasible for simple generators, it wastes SeExpr’s potential for parallelization. To maximize the usability and performance of this kind of layers, Dmitry Kazakov suggested that Fill Layers themselves should be converted to [https://docs.krita.org/en/untranslatable_pages/strokes_documentation.html a Stroke]. Strokes are individual, self-contained sets of jobs, each of which may be performed in parallel.
 
To achieve this, the canvas is tiled according to the optimal patch size at the moment; for each tile, a job structure is generated that will run the generator over the specified tile. These structures are then submitted to a stroke that runs them in the background.
 
This feature requires that the generator perform its work on a self-contained basis, i.e. the tile. For those that are unable to do so (e.g. Multipattern), this feature is disabled at compile time.
 
=== Live Preview for Fill Layers ===
 
(A demo video is available [https://diode.zone/videos/watch/b441f360-0b94-470a-8365-5a5f44b3a617?loop=1&muted=1 at diode.zone].)
 
This is a '''stretch goal'''.
 
Before this project, Fill Layers (unlike Filter Layers) were only able to be previewed after being added to the canvas. However, a direct update step was not possible; the multithreaded rendering process implemented above makes use of [https://docs.krita.org/en/untranslatable_pages/strokes_documentation.html Krita’s strokes system], and two strokes (layer addition, and layer update) cannot run concurrently. What’s more, all functions that update the graphical state of the layer are booby-trapped: any call to them will in turn trigger an update, the initialization of which breaks the internal rendering state.
 
In order to enable this functionality, I completely reworked Fill Layers’s update workflow:
 
# I ported the rendering strategy to a runnable-based one, which allowed me to move the actual work to self-contained lambda functions (commits: [https://invent.kde.org/graphics/krita/-/commit/85092cde7caecb37d80ed9907413461cc1fe9082 1], [https://invent.kde.org/graphics/krita/-/commit/a030d3fcad10e00bfd9794100ee4ced368ac6422 2])
# I defanged all functions dealing with internal state, which allows them to be called without triggering yet another update (commits: [https://invent.kde.org/graphics/krita/-/commit/fb9bb41a3604aff5b9b6df3a30f8a98da6f651e9 1], [https://invent.kde.org/graphics/krita/-/commit/da0daa7caad4da880d2f09817bc513236e9abb06 2], [https://invent.kde.org/graphics/krita/-/commit/8c3a4c8dc9268bf434007e4ce07b2a8821cec405 3], [https://invent.kde.org/graphics/krita/-/commit/7c4230cf1e368d1fb03215700af3b0834e06c632 4])
# I decoupled the job generation from the canvas update, allowing them to be inserted into the stroke that adds the layer. Additionally, this step was compressed to trigger only once every 100ms, saving a lot of CPU in unnecessary render jobs (commits: [https://invent.kde.org/graphics/krita/-/commit/669946d7260ace7af68ea156b4b81171cf213e43 1], [https://invent.kde.org/graphics/krita/-/commit/cd2cb8baa2af7f776b759a58fda992bde18d3042 2], [https://invent.kde.org/graphics/krita/commit/e524cf5bb9a5ad1cd9bf32794a4c6cc7a7f53b4f 3])
# Finally, I ensured that Fill Layers were completely re-rendered if the image bounds change, as some generators (like SeExpr) operate over the whole image (commits: [https://invent.kde.org/graphics/krita/-/commit/3f3062e757177112645967a9955f5bcbb3eaba59 1], [https://invent.kde.org/graphics/krita/-/commit/c3ce9a40d58d426188271b76a971c868153e28a9 2])
 
In addition to this goal, I completed the move of all documentation to
the User Manual (commits: [https://invent.kde.org/documentation/docs-krita-org/-/commit/0d2299b8a5e8916fa588f6566de813f18477beba 1], [https://invent.kde.org/documentation/docs-krita-org/-/commit/81dd4f835cc87851b879c8c3cbe5e71f8a8b9b54 2]).
 
=== ARM support for AppImages ===
 
This is a '''stretch goal''', and is explained in more detail on [https://www.amyspark.me/blog/posts/2020/07/15/compiling-krita-for-arm.html its blog post].
 
<gallery mode="slideshow">
SeExpr_RPi_AppImage.jpg|The SeExpr AppImage running on a Raspberry Pi 3B+.
SeExpr_Potato_AppImage.jpg|The SeExpr AppImage running on Libre Computer's Le Potato with 2GB RAM.
</gallery>
 
It was asked on the #krita IRC channel if it was possible to run Krita on ARM-based computers, specifically the Raspberry Pi 3B+. To the best of my knowledge, it has not been tried before.
 
In the blog post linked above, I prove fully-featured AppImages can be built for ARM-based platforms. Although the original target was 32-bit ARM (also known as <code>armhf</code> and <code>armv7l</code>), my main testing board is a 64-bit, 2GB Le Potato, which forced me to test this process for the <code>aarch64</code> architecture as well.
 
The build process was done on a 16-thread Ryzen 7 under QEMU.
 
The key results were as follows:
 
* The KDE Sysadmin’s Docker image works almost out-of-the-box.
** The base image must be changed to one that has QEMU, for instance <code>multiarch/ubuntu-debootstrap</code>.
** If this path is followed, the <code>universe</code> repository must be enabled manually.
** <code>xkbcommon-dev</code> is a key dependency to build Qt’s X11Extras module. It’s missing in <code>aarch64</code> because <code>libegl1-mesa-dev</code> does not bring it in, unless in every other platform.
** There are no official binaries for CMake, patchelf, and linuxdeployqt, so they must be built from scratch and bundled into the image.
* The build process '''must''' be multithreaded as much as possible.
** The 3rdparty dependencies <code>ext_expat</code>, <code>ext_python</code>, <code>ext_sip</code>, and <code>ext_qt</code> must be forcibly parallelized using the <code>-j${SUBMAKE_JOBS}</code> flag.
* These dependencies need fixes or be disabled altogether:
** OpenColorIO (<code>ext_ocio</code>) needs [https://github.com/AcademySoftwareFoundation/OpenColorIO/pull/970.patch this patch] applied
** libx265 inside <code>ext_heif</code> needs to be upgraded to 3.4 to allow <code>aarch64</code> builds. It works for <code>armhf</code> with <code>-DENABLE_ASSEMBLY=false</code>
** Python will hang when running its tests, the <code>BUILD_COMMAND</code> must be changed to <code>make build_all</code>.
** GSL needs the shared library in Unix-based systems, not only Android
** Vc does not work altogether
* The build scripts assume across the whole process that the platform is <code>x86_64</code>. This must be changed taking into account that <code>arm64</code> is called <code>aarch64</code> by AppImageTool.


=== AppImages support in ARM ===
=== Status ===


== Status ==
* SeExpr-based Fill Layer generator: '''merged'''
* Creation and management of SeExpr scripts: '''merged'''
* Integration tests: '''merged'''
* User documentation: '''merged'''
* Feature support status review: '''merged'''
* Multithread support for Fill Layers: '''merged'''
* Live Preview for Fill Layers: '''merged'''
* ARM support for AppImages: '''merged'''


* SeExpr-based Fill Layer generator: feature complete, '''awaiting review'''
=== Communications ===
* Creation and management of SeExpr scripts: feature complete, '''awaiting review'''
* [https://summerofcode.withgoogle.com/projects/#6114015970852864 Official entry for this project at Google Summer of Code]
* Integration tests: '''WIP''', missing tests for the Resources system
* Blog posts ([https://www.amyspark.me/blog/category/gsoc/ category: GSoC]):
* User documentation: '''WIP''', not yet uploaded
** May 6th: [https://www.amyspark.me/blog/posts/2020/05/06/hello-planet.html "Hello Planet”]
* Feature support status review: '''blog post published''', no fixes proposed
** May 9th–May 13th: “Developing Krita in Visual Studio Code” ([https://www.amyspark.me/blog/posts/2020/05/11/gsoc-setup-linux.html part 1], [https://www.amyspark.me/blog/posts/2020/05/12/gsoc-setup-macos.html part 2], [https://www.amyspark.me/blog/posts/2020/05/13/gsoc-setup-windows.html part 3])
* Multithread support for Fill Layers: feature complete, '''awaiting review'''
** May 19th: [https://www.amyspark.me/blog/posts/2020/05/19/what-is-seexpr-about.html “What is SeExpr about?”]
* ARM support for AppImages: '''blog post published''', no fixes proposed
** May 28th: [https://www.amyspark.me/blog/posts/2020/05/28/status-report-community-bonding.html “Status report: Community Bonding”]
** June 1st: [https://www.amyspark.me/blog/posts/2020/06/01/status-report-success.html “Status report: Week 1”]
** June 4th: [https://www.amyspark.me/blog/posts/2020/06/04/status-update-linux.html “Status update: Linux”]
** June 8th: [https://www.amyspark.me/blog/posts/2020/06/09/first-alpha-release.html “First alpha release of my project: looking for feedback!”]
** June 22th: [https://www.amyspark.me/blog/posts/2020/06/22/second-alpha-release.html “Second alpha release of my project”]
** July 3rd: [https://www.amyspark.me/blog/posts/2020/07/03/third-alpha-release.html “Third alpha release of my project”]
** July 15th: [https://www.amyspark.me/blog/posts/2020/07/15/compiling-krita-for-arm.html “Compiling Krita for ARM: An AppImage Tale”]
** July 27th: [https://www.amyspark.me/blog/posts/2020/07/27/status-update-merged.html “Status update: merged!”]
** August 8th: [https://www.amyspark.me/blog/posts/2020/08/11/status-report-seexpr.html “SeExpr status update!”]
* Krita Artists threads:
** [https://krita-artists.org/t/first-alpha-of-my-gsoc-project-procedural-texture-generator/8127 “First alpha of my GSoC project: procedural texture generator”]
** [https://krita-artists.org/t/procedural-texture-generator-example-and-wishes/7638 “Procedural texture generator (example and wishes)”]
* Project report at [https://community.kde.org/GSoC/2020/StatusReports/LeonardoEmanuelSegovia the KDE Community Wiki]
* Portfolio entry [https://www.amyspark.me/portfolio/gsoc-2020/ at my blog]


== Deliverables ==
=== Deliverables ===


* Phabricator task: [https://phabricator.kde.org/T13097 T13097], see in particular subtask [https://phabricator.kde.org/T13337 T13337] for the preset management UI
* Phabricator task: [https://phabricator.kde.org/T13097 T13097], see in particular subtask [https://phabricator.kde.org/T13337 T13337] for the preset management UI
Line 55: Line 248:
* Merge requests for the SeExpr generator: [https://invent.kde.org/graphics/krita/-/merge_requests/380 !380], [https://invent.kde.org/graphics/krita/-/merge_requests/380 !411]
* Merge requests for the SeExpr generator: [https://invent.kde.org/graphics/krita/-/merge_requests/380 !380], [https://invent.kde.org/graphics/krita/-/merge_requests/380 !411]
* Merge request for multithreading Fill Layers: [https://invent.kde.org/graphics/krita/-/merge_requests/412 !412]
* Merge request for multithreading Fill Layers: [https://invent.kde.org/graphics/krita/-/merge_requests/412 !412]
* Merge requests for Fill Layers preview: [https://invent.kde.org/graphics/krita/-/merge_requests/467 !467], [https://invent.kde.org/graphics/krita/-/merge_requests/472 !472]
* Merge request for the Krita manual: [https://invent.kde.org/documentation/docs-krita-org/-/merge_requests/145 !145], [https://invent.kde.org/documentation/docs-krita-org/-/merge_requests/151 !151]
* Feature support list is available as a [https://docs.google.com/spreadsheets/d/1Z4bOJuF-NnIqn6SZwQkDxDa5xMkLQvFetNLiLhlfLjo/edit?usp=sharing Google Docs file]
* Feature support list is available as a [https://docs.google.com/spreadsheets/d/1Z4bOJuF-NnIqn6SZwQkDxDa5xMkLQvFetNLiLhlfLjo/edit?usp=sharing Google Docs file]
* Blog posts are available [https://www.amyspark.me/blog/category/gsoc/ at my site]

Latest revision as of 12:25, 9 September 2020

Dynamic Fill Layers in Krita using SeExpr

💙 Energy #krita #seexpr (source: David Revoy on Framapiaf)

Abstract

Layers are one of the core concepts of digital painting. They allow artists to control different parts of their artwork at once, for instance, color, lighting, lineart, as well as texture. A key feature of them is their ability to be resized, composited, renamed, grouped or deleted independently of the rest of the document.

Patterns and textures are also essential components of an artist’s toolbox, allowing them to represent the intricacies of a physical material. They come in two forms: bitmap textures, which are images contained in e.g. PNG or OpenEXR files, or procedural textures, which are generated on the fly using their mathematical representation.

KDE’s Krita painting suite supports using patterns and textures through two types of layers, File or Fill Layers. However, neither of them let artists create dynamically generated content: File Layers are inherently static, and Fill Layers support only color fills (like Paint Layers) or basic pattern rendering.

The goal of this project is to let artists create dynamic content through a new, scriptable Fill Layer. To this effect, I integrated Disney Animation’s SeExpr expression language into Krita.

Components

This project is divided into the following objectives or goals:

  • SeExpr-based Fill Layer generator
  • Creation and management of SeExpr scripts
  • Integration tests
  • User documentation

Side goals included:

  • Feature support status across all our platforms
  • Multithread support for Fill Layers
  • ARM support for AppImages (including this project’s deliverables as well)

SeExpr-based Fill Layer generator

Enabling SeExpr as a Fill Layer generator involves two stages.

  • Integration of SeExpr as a 3rdparty dependency
  • Defining the new generator
  • Loading, rendering, and saving the script with the layer

The SeExpr generator is divided into three components:

  • SeExprExpressionContext and SeExprVariable are the glue between SeExpr and Krita; the former contains the script’s execution context, the latter exposes the necessary variables for rendering the script (width, height, and pixel location in normalized coordinates).
  • KisSeExprGenerator is the instance of KisGenerator tasked with rendering the script to the layer’s paint device. It sets up the SeExprExpressionContext with the given script and variables. Then, it iterates over the paint device, rendering the script on each pixel and converting the resulting color back to Krita’s color space.
  • KritaSeExprGenerator, WdgSeExpr and WdgSeExprSavePreset comprise the UI of the SeExpr generator. These are described in more detail in the next section.

It must be noted that throughout the development of the SeExpr generator, extensive changes were made to Disney’s work itself. Of particular note are the following:

The following bugs in SeExpr were fixed:

  • As detailed on these blog posts, SeExpr implicitly assumed that it would be always executed in English-based locales. This was detected by Wolthera van Hövell.
    • Commits: 1, 2, 3
  • The autocompletion support was unusable since it blocked numpad entry. This was fixed (commits: 1, 2), along with an extra optimization to use native controls.
  • A buffer underflow was fixed in CCurveScene (the UI widget for color curves) (commits: 1)
  • As part of the revision of the documentation, Wolthera van Hövell found a couple of bugs related to incorrect function documentation (commits: 1)
  • A crash when initializing vector variables outside the [0, 1] range (commits: 1, 2)
  • Lack of promotion of vectors to colors after widget initialization (commits: 1)

Creation and management of SeExpr scripts

When I implemented the generator, the script was saved along with the layer. David Revoy proposed that scripts should be reusable, by making them bundleable as any other resource in Krita.

The initial implementation, shown in the screenshots below, is based on the existing Pattern chooser widget. The underlying KoResource specialization, which is called KisSeExprScript and located in libs/flake/resources, is an almost-straight port of Anna Medonosová’s Gamut Masks.

Data-wise, a SeExpr preset’s script is stored in a plaintext file named script.se, along with a thumbnail image in preview.png. These are bundled together in a ZIP of extension .kse and MIME type application/x-krita-seexpr-script.

In T13337, I requested feedback from the developers on the UX for creating and editing these presets. Based on the existing workflow for managing brushes, I proposed the following mocks:

Based on suggestions from Agata Cacko in both the Krita Artists feedback thread and the Phabricator task, the final form of the UX is as follows:

By request from Agata, the manipulation widgets and the text editor were placed inside a QSplitter, to ensure users could freely choose how much space they want to assign each component.

By request from Wolthera, support was added for reporting validation errors. Since SeExpr was not designed with internationalization in mind, extensive refactoring was needed to extracts its strings from the UI components and from the render library, allow their future localization by the KDE team, and finally enable their usage in the widgets. Additionally, variables exposed from Krita were added to the autocompletion system.

While there were not many changes in the management UX, it was simplified into 4 actions. The unique contribution here is Render Script to Thumbnail; as its name says, it creates an instance of the SeExpr fill layer generator, points it to the thumbnail image’s QPaintDevice, and renders the given preset’s script into the thumbnail.

Integration tests

There are two testable points in the SeExpr generator:

  • successful rendering from the given script
  • loading and saving from a given KoResource instance

The first is already implemented in the SeExpr generator (commits: 1, 2). The second is NYI. The first covers the SeExpr generator itself (commits: 1, 2, 3). The second covers serialization and deserialization from a known working KoResource (commits: 1).

User documentation

Up to now, there was no complete reference for creating SeExpr scripts. To build a complete reference for SeExpr scripts, my work created the following documental resources in the Krita user manual:

  • an entry to the Fill Layers section
  • a specific page for SeExpr scripts in Resource Management
  • a tutorial for creating, editing and bundling script presets
  • a Quick Reference page detailing the expression language

The latter is one of the most important productions, as it involved porting the existing User Documentation of Disney's (which is only available in precompiled Doxygen form) to reStructuredText, while adapting it to suit the script specification in Krita.

A fifth resource, this one provided with the Krita binaries, is a resource bundle comprising all examples in the Krita Artists thread, “Procedural texture generator (example and wishes)”. The authors of the examples are Wolthera van Hövell and David Revoy; the latter employed an alpha version of this tool in the episode 33 of his webcomic, Pepper&Carrot. (commits: 1, 2, 3, 4)

Multithread support for Fill Layers

This is a stretch goal.

Originally, Fill Layers were rendered in a single, background thread. Although feasible for simple generators, it wastes SeExpr’s potential for parallelization. To maximize the usability and performance of this kind of layers, Dmitry Kazakov suggested that Fill Layers themselves should be converted to a Stroke. Strokes are individual, self-contained sets of jobs, each of which may be performed in parallel.

To achieve this, the canvas is tiled according to the optimal patch size at the moment; for each tile, a job structure is generated that will run the generator over the specified tile. These structures are then submitted to a stroke that runs them in the background.

This feature requires that the generator perform its work on a self-contained basis, i.e. the tile. For those that are unable to do so (e.g. Multipattern), this feature is disabled at compile time.

Live Preview for Fill Layers

(A demo video is available at diode.zone.)

This is a stretch goal.

Before this project, Fill Layers (unlike Filter Layers) were only able to be previewed after being added to the canvas. However, a direct update step was not possible; the multithreaded rendering process implemented above makes use of Krita’s strokes system, and two strokes (layer addition, and layer update) cannot run concurrently. What’s more, all functions that update the graphical state of the layer are booby-trapped: any call to them will in turn trigger an update, the initialization of which breaks the internal rendering state.

In order to enable this functionality, I completely reworked Fill Layers’s update workflow:

  1. I ported the rendering strategy to a runnable-based one, which allowed me to move the actual work to self-contained lambda functions (commits: 1, 2)
  2. I defanged all functions dealing with internal state, which allows them to be called without triggering yet another update (commits: 1, 2, 3, 4)
  3. I decoupled the job generation from the canvas update, allowing them to be inserted into the stroke that adds the layer. Additionally, this step was compressed to trigger only once every 100ms, saving a lot of CPU in unnecessary render jobs (commits: 1, 2, 3)
  4. Finally, I ensured that Fill Layers were completely re-rendered if the image bounds change, as some generators (like SeExpr) operate over the whole image (commits: 1, 2)

In addition to this goal, I completed the move of all documentation to the User Manual (commits: 1, 2).

ARM support for AppImages

This is a stretch goal, and is explained in more detail on its blog post.

It was asked on the #krita IRC channel if it was possible to run Krita on ARM-based computers, specifically the Raspberry Pi 3B+. To the best of my knowledge, it has not been tried before.

In the blog post linked above, I prove fully-featured AppImages can be built for ARM-based platforms. Although the original target was 32-bit ARM (also known as armhf and armv7l), my main testing board is a 64-bit, 2GB Le Potato, which forced me to test this process for the aarch64 architecture as well.

The build process was done on a 16-thread Ryzen 7 under QEMU.

The key results were as follows:

  • The KDE Sysadmin’s Docker image works almost out-of-the-box.
    • The base image must be changed to one that has QEMU, for instance multiarch/ubuntu-debootstrap.
    • If this path is followed, the universe repository must be enabled manually.
    • xkbcommon-dev is a key dependency to build Qt’s X11Extras module. It’s missing in aarch64 because libegl1-mesa-dev does not bring it in, unless in every other platform.
    • There are no official binaries for CMake, patchelf, and linuxdeployqt, so they must be built from scratch and bundled into the image.
  • The build process must be multithreaded as much as possible.
    • The 3rdparty dependencies ext_expat, ext_python, ext_sip, and ext_qt must be forcibly parallelized using the -j${SUBMAKE_JOBS} flag.
  • These dependencies need fixes or be disabled altogether:
    • OpenColorIO (ext_ocio) needs this patch applied
    • libx265 inside ext_heif needs to be upgraded to 3.4 to allow aarch64 builds. It works for armhf with -DENABLE_ASSEMBLY=false
    • Python will hang when running its tests, the BUILD_COMMAND must be changed to make build_all.
    • GSL needs the shared library in Unix-based systems, not only Android
    • Vc does not work altogether
  • The build scripts assume across the whole process that the platform is x86_64. This must be changed taking into account that arm64 is called aarch64 by AppImageTool.

Status

  • SeExpr-based Fill Layer generator: merged
  • Creation and management of SeExpr scripts: merged
  • Integration tests: merged
  • User documentation: merged
  • Feature support status review: merged
  • Multithread support for Fill Layers: merged
  • Live Preview for Fill Layers: merged
  • ARM support for AppImages: merged

Communications

Deliverables