Bindings to Google's Skia library through the C API of https://github.com/mono/skia
Working in progress!!
- If you already have
libSkiaSharp.sobuilt, docabal run skia:exe:demo-raster-example. - If you have
nix, donix developbuild and installlibSkiaSharp.sointo the nix shell environment, then docabal run skia:exe:demo-raster-example.
Running the demo should generate this PNG picture file:
https://skia.org/: "Skia is an open source 2D graphics library which provides common APIs that work across a variety of hardware and software platforms. It serves as the graphics engine for Google Chrome and ChromeOS, Android, Flutter, and many other products."
Skia is written in C++. However, Haskell's FFI
does not mix well with C++
apparently.
To workaround this, a C API of Skia can be coded, then a Haskell skia binding
library can be bound to that C API. Google has a C API of
Skia
but it seems to be not
well-maintained. Fortunately,
Microsoft provides SkiaSharp, a binding for
Skia for .NET platforms. Microsoft's SkiaSharp project forks Google's Skia and
implement their own C API of Skia here. This
library is a Haskell binding library to that C API and it links to
libSkiaSharp.so.
You have to build and install Mono's Skia library yourself. See https://github.com/mono/SkiaSharp/wiki/Building-SkiaSharp.
This project provides mono-skia-package.nix to build and install Mono's Skia
library for developers to work on the Haskell library directly, though note that
this is a crude solution.
This project uses a Python script to parse Mono's Skia C API headers and
auto-generate almost all bindings. These generated Haskell bindings live in
src/Skia/Bindings/AutoGenerated.hsc. However, some C function bindings
require manual implementation, and they are handled in
src/Skia/Bindings/Manual.hsc.
- Fix the binding generator's word breaker implementation. The binding
generator's word breaker is an algorithm to break mangled C names like
gr_backendrendertarget_get_gl_framebufferinfointogr/backend/render/target/get/gl/framebuffer/infofor use in generating nice camel case Haskell names likegrBackendRenderTargetGetGlFramebufferInfo. However, the current implementation does not work perfectly. - Auto-generate documentations.
- Create a high-level API like https://learn.microsoft.com/en-us/dotnet/api/skiasharp?view=skiasharp-2.88 + documentation.
-
Technically it should be possible to create our very own
cbits/by copying all mono-skia C-API header/source files. However, some public Skia headers include internal private headers...- Here is a random example of a public Skia header including a private header: https://issues.skia.org/issues/40045022/blocking
include/c/sk_document.hincludes some private headers.
-
About module namespacing:
- All modules in this library are prefixed with
Skia.. Some may preferGraphics.Skia, but it is less lean. - If there exists a different Haskell Skia binding library that also uses
the prefix
Skia., and for some reason someone's application depends on that library and this one; a solution can be found here: https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/package_qualified_imports.html.
- All modules in this library are prefixed with
-
Here is a list of odd facts about SkiaSharp/Mono Skia's C API found when this library was being developed:
-
The enum value names of
sk_webpencoder_compression_tare misspelled:typedef enum { LOSSY_SK_WEBPENCODER_COMPTRESSION, LOSSLESS_SK_WEBPENCODER_COMPTRESSION } sk_webpencoder_compression_t-
sk_pixmap_get_writable_addris spelled properly, butsk_pixmap_get_writeable_addr_with_xyis not. -
sk_codec_get_frame_countis implemented as
int sk_codec_get_frame_count(sk_codec_t* codec) { return AsCodec(codec)->getFrameInfo().size(); }
...which is very inefficient because it is getting an entire
std::vectorof FrameInfos to then only get.size()and discard.A better implementation should be (I think):
int sk_codec_get_frame_count(sk_codec_t* codec) { return AsCodec(codec)->getFrameCount(); }
-
-
sk_get_surfaceshould be namedsk_canvas_get_surface. -
sk_get_recording_contextshould be namedsk_canvas_get_recording_context. -
sk_picture_recorder_end_recordingis missing an argument forconst SkRect& cullRect. -
sk_picture_get_recording_canvasshould be namedsk_picture_recorder_get_recording_canvas. -
sk_bitmap_get_pixelsprobably should not be responsible for computingsize_t* length.sk_bitmap_get_byte_countalready provides an interface for this purpose. -
sk_surfaceprops_flags_tshould defineDYNAMIC_MSAAandALWAYS_DITHER. -
skottie_animation_seek_frameandskottie_animation_seek_frame_timeshould take indoubleinstead offloatfor parametert. -
void sksg_invalidation_controller_begin(...)andvoid sksg_invalidation_controller_end(...)are completely useless.InvalidationController::begin()andInvalidationController::end()returnstd::vector::{cbegin,cend}respectively, and Mono Skia's C binding simply voids them. -
sk_colorspace_to_xyzd50always returns true, but this is due to how Google Skia designed the underlying C++ function. -
typedef struct sk_pixelref_factory_t sk_pixelref_factory_t;is never used. -
typedef struct skottie_resource_provider_t skottie_resource_provider_t;is actuallyusing ResourceProvider = skresources::ResourceProvider;, which is defined bytypedef struct skresources_resource_provider_t skresources_resource_provider_t;. -
skottie_animation_make_from_stringis the same asskottie_animation_make_from_data. -
sk_surface_drawdoes not provide an interface to pass in anSkSamplingOptions. -
gr_direct_context_perform_deferred_cleanupdoes not expose the parameteropts. -
sk_image_new_raster_copyis redundant: Its function can be replaced by combiningsk_image_new_raster_copy_with_pixmapandsk_pixmap_new_with_params -
sk_paint_get_blendmodeshould not hardcode @SkBlendMode::kSrcOver@ as the default.
-
-
Development process
- The general process involves inspecting the C API and Skia C++ implementation in https://github.com/mono/skia.
clangdcan be set up (by manually applying the changes made in https://github.com/google/skia/commit/f5ea6a15bf936baae0248cb088b1b2b042706305#diff-7fc57714ef13c3325ce2a1130202edced92fcccc0c6db34a72f7b57f60d552a3 + runningbazel run //:gen_compile_flags_txt_linux_amd64 --config=ganesh_gl > compile_flags.txtas recommended by the commit message) to help navigate around the C++ source. - https://github.com/mono/SkiaSharp is used as a reference of how a non-C++ library exposes the Skia API.
- The general process involves inspecting the C API and Skia C++ implementation in https://github.com/mono/skia.
-
Notes about
cbits/andcbits_make/- NOTE: Without PCHs, compilation time is really slow because both clang++
and g++ spend a lot of time on parsing files. Try add
-ftime-reportto CFLAGS to verify this claim. - Apparently PCHs are compiler-specific. The CLI of PCH operations are
different from compiler to compiler (e.g.,
gccvsclang). To deal with this, CMake is used instead of directly writing a Makefile. Additionally, as a bonus, CMake supports generatingcompile_commands.jsonfor Clang. - Cabal compilg
cbits/is very slow. Using PCHs can help. There are possible two solutions:- Tell Cabal to create PCHs (somehow, but I don't think Cabal can do it).
- Externally compile with
cbits_make/, which has a proper and efficient build system setup. However, I am not sure how to tell Cabal to do it.
- NOTE: Without PCHs, compilation time is really slow because both clang++
and g++ spend a lot of time on parsing files. Try add
-
Google Skia's GN configs seem flawed. For example,
libskparagraph.sobuilt using the provided GN configs with flagis_component_build=truedoes not expose all symbols because-fvisibility=hiddenis (erroneously?) applied to all shared library GN targets. To fix this,skia-129-modified.nixcontains a patch to disable-fvisibility=hidden. Also see https://issues.skia.org/issues/358587937 ("skparagraph has no public symbols in component build") -
About
cbits/skia_capi/sk_types.h: because of haskell/hsc2hs#66 & https://gitlab.haskell.org/ghc/ghc/-/issues/23769, when this package is built as a dependency of an external application,src/Skia/Bindings/Types.hsccan readcbits/skia_capi/sk_types.hbut is unable to resolve the#includedirectives withincbits/skia_capi/sk_types.hand complain that the referenced headers cannot be found. This meanssk_types.hmust contain ALL the type declarations, and you cannot separate the types into multiple header files to organize.
