Skip to content

Latest commit

 

History

History
1018 lines (719 loc) · 80.8 KB

README.adoc

File metadata and controls

1018 lines (719 loc) · 80.8 KB

rouziclib

This is my library of code that is common to all my different projects such as Photosounder, Spiral and SplineEQ, but also lesser projects such as my minimalistic picture viewer, my screenshot tool or my modernised version of NASA’s Voyager Image Decoder as well as projects you don’t know about such as my private project Spacewar where all my new code is born.

What it does

It includes some of the following:

  • Fast lookup table-based fixed-point arithmetic approximations for sqrt, hypot, log2, exp2, pow, cos, atan2 (both noting angles in turns, not radians), the Gaussian function (e-x²) and the raised error function (0.5+0.5erf(x)). Some are implemented using linear interpolation, segmented quadratic polynomial approximation or simple lookup, which offers different levels of speed/precision/memory usage tradeoffs.

  • Fast polynomial lookup table-based floating-point approximations for cos, log2, exp2, pow and others.

  • Original math functions such as a short Gaussian function and its integral, the short error function which I now use for filter design. I also implemented the inverse of the error function and the integral of the error function.

  • Functions to draw lines, points, circles, roundrects and gradients, all antialiased with Gaussian filtering

  • Functions to convert from sRGB to linear RGB for loading images and to convert from linear RGB to sRGB with optional Gaussian dithering for displaying

  • Geometric functions used for computing intersections between lines, shortest the distance of a point to a line or to limit a line to a bounding box

  • Blending modes like additive, subtractive, multiplicative blending and alpha blending

  • Blitting of a buffer onto another, like for displaying a sprite

  • An original Hue-Saturation-Luminance colour space with a Luminance that is about perceptually correct (unlike the 1931 CIE XYZ colour space which gets the weights of each colour blatantly wrong) which is used for HSL to RGB conversion and for bringing out of gamut colours (such as colours that have components brighter than 1.0) to the most appropriate representable colour

  • Vector font generation, vector text rendering and a built-in minimalistic vector font that is always available. A more complete typeface is available in the vector_type directory

  • Vector procedural zoomable interface elements that for instance allow you to have a fully functional button just by calling a function with all the necessary information provided as parameters (without anything stored in memory) and simply getting the return value, with no need for storage for each instance of a control, no unique IDs or anything

  • A system to make GUI layouts based on markup as well as a window manager to handle multiple windows as well as nested subwindows properly

  • Functions to conveniently do operations on point vectors (2D or 3D) or rectangles and triangles, such as generating rectangle coordinates based on position, size and offset vectors, or calculating the scaling and positioning needed to make a rectangle fit in a certain way inside another rectangle. This forms the basis of the aforementioned interface system.

  • A drawing queue-based graphical system that lets the main thread queue drawing commands and then is rendered on the GPU using OpenCL or the CPU in a threaded way

  • A file concatenation feature called fileball similar to tarball that I use to pack and compress many files together as one header that can be included in an executable in a portable compiler-independent way, like my vector typeface.

  • Convenient directory-listing and file management features

  • Built-in dependency-free loading of various image formats (JPG, PNG, BMP, PSD, TIFF, NASA's FITS), saving of images (32-bit TIFF, PNG, JPG, BMP), loading of sounds (AIFF, WAVE, MP3, FLAC, OGG) and saving of sounds (AIFF, WAVE).

  • Modified libraries such as a version of cfftpack modified so that both single and double precision floating point FFTs can be done in the same program (not possible with the original) or a version of dirent modified to fix some path length and Unicode conversion bugs, a cut-down version of GLEW that contains only what rouziclib needs or a cut-down and much nicer version of the XXH64 hashing algorithm from xxHash.

  • Fast tiled mipmap generation, image rescaling based on flat-top bilinear filtering, Gaussian blurring, an original texture compression and YUV coding/decoding in C and OpenCL.

  • A system for loading and saving an application’s preferences to one file but in a non-centralised way with just a simple value query being enough to add a new value in its proper place

  • A sound system so that multiple functions can play sounds at the same time by providing a callback

  • Functions to easily resize buffers as needed and a generic buffer system based on this with many functions that replicate how outputing to a file stream works

  • Binary searching of arrays

  • Functions to convert between UTF-8, UTF-16 and Unicode codepoints. A subset of these functions forms the basis for fopen_utf8 which I recommend you use if you need to fopen UTF-8 paths on Windows.

  • Various functions to parse or format strings

  • Various utility functions and macros

  • Code for working with SDL, OpenGL, OpenCL, clFFT, DevIL, OpenCV, FFMPEG, LibRAW and MPFR.

All graphical functions operate on pixels in a linear colour space. Please do not use these functions directly in a gamma-compressed colour space, instead use an intermediary linear framebuffer which you can then convert to an sRGB framebuffer using the function convert_lrgb_to_srgb.

How to use it

Unusually for a library, rouziclib’s code relies on macros that are defined inside your project’s code. This means that rouziclib isn’t entirely independently compiled. So the way to make this work is to create two files in your project, a header file which directly includes the main header, but not before you add the macros you can optionally define, and a code file which includes the aforementioned header file you just created and then includes the library’s rouziclib.c. Here’s how it looks:

rl.h

#ifndef H_PRL
#define H_PRL
#ifdef __cplusplus
extern "C" {
#endif

// these are examples of optional macros that rouziclib will then use for your project
#define COL_FRGB    // this macro makes the internal format for colour be floating-point instead of fixed-point
#define RL_SDL      // this includes SDL-using code as well as the necessary SDL files
#define RL_OPENCL   // same for OpenCL

// this defines a wrapper for fprintf_rl, so your project can use a custom fprintf-type function that can for instance output to a file
#define fprintf_rl fprintf_wrapper

#include <rouziclib/rouziclib.h>

extern void fprintf_wrapper(FILE *stream, const char* format, ...);

#ifdef __cplusplus
}
#endif
#endif

rl.c

#include "rl.h"

// this creates that custom printing function that all calls to fprintf_rl in rouziclib will use

void fprintf_wrapper(FILE *stream, const char* format, ...)
{
    va_list args;

    va_start(args, format);

    vfprintf(stream, format, args);    // printf to original stream
    fflush(stream);

    va_end(args);
}

#include <rouziclib/rouziclib.c>

I realise that this is a bit unusual, but it’s pretty simple and very handy. You can for instance include rouziclib in a simple command-line C program without having to worry about dependencies as none will be included, and in another project add dependencies as you need by adding the necessary macros, so without having the recompile anything separately (as you would have to were you to use two versions of a same library compiled with different dependencies) you can have in separate projects a rouziclib with no dependencies or a rouziclib that uses SDL, DevIL, OpenCV, OpenCL, clFFT, FFMPEG and LibRAW. And since the whole library is compiled as a single file (which is your file that includes rouziclib.c) that means the compiler will inline functions from the library as they are called by other functions of the library instead of using dynamic linking, meaning that my approach of including everything into one file is beneficial for performance.

Example project

Have a look at a minimal picture viewer built around rouziclib, with explanations of its features, how it works and how to expand on it or create a similar program.

Files

  • rouziclib.c / rouziclib.h / rouziclib.m
    Every piece of code in this library is ultimately included into one of those three files, so it’s as if this whole library was only made of 2 (or 3 on macOS) files. Basically rouziclib is just like a two-file library broken down into over 380 files due to size.

  • general/

    • xyz.c / xyz.h and xyz_struct.h
      Vectors (2D, 3D, integer 2D, double-double 2D and 3x3 matrices) with conversions and operations. Very widely used throughout the library.

    • time.c / time.h
      Multiplatform standard and high resolution time-measuring, sleep, date-time string parsing and Julian date conversion.

    • estimates.c / estimates.h
      Functions for estimating the program’s framerate.

    • mouse.c / mouse.h and mouse_struct.h
      Mouse cursor logic and cursor drawing.

    • keyboard.c / keyboard.h and keyboard_struct.h
      Processing SDL keyboard events to update arrays of flags while avoiding missing quick presses like when both the down and up event arrive at the same time. Also controls the zoom-scroll system from key combos.

    • noop.c / noop.h
      Functions that do nothing and say so when that’s needed.

    • hashing.c / hashing.h
      Functions for hashing based on xxHash. I didn’t like how big and complicated the original code was so I made a much smaller, simpler and more readable version in xxh64.c.

    • threading.c / threading.h
      Multiplatform threading (threads, mutexes, semaphores, atomics), including wrappers for thread.h.

    • intrinsics.c / intrinsics.h
      Functions for testing Intel x86 CPU features at runtime, replacements for certain intrinsics that can be missing or inefficient, macros that simplify basic operations.

    • crash_dump.c / crash_dump.h
      Windows-only functions to generate a minidump file in case the program crashes.

    • audio.c / audio.h
      Audio system that can call multiple callbacks, mix their outputs, register them, deregister and stop running them automatically.

    • midi.c / midi.h
      MIDI device input on Windows.

    • structs.h
      Generic buffer structure and generic lookup table structure.

    • macros.h
      Stringify macros and a macro to detect GCC.

    • windows_includes.h
      Includes to minimise the Windows headers included and optimise compilation duration.

    • globals.c / globals.h
      Creates the globals used by the various system in rouziclib, such as the framebuffer structure (thread-local so that other threads can render to a framebuffer and therefore use the usual drawing and GUI functions, like when rendering a video), the zoom-scroll state, the mouse-keyboard state, the vector typeface, the audio system, the window manager as well as the default drawing thickness.

  • memory/

    • alloc.c / alloc.h
      Manages allocations, reallocations, deallocations, copying, 2D arrays and managing an allocation list. Contains the very essential alloc_enough() which I use extensively to resize buffers as needed.

    • fill.c / fill.h
      Like memset() but with 32 and 64-bit words instead of 8 bits.

    • swap.c / swap.h
      Functions to swap two values.

    • circular_buffer.c / circular_buffer.h
      Manages circular buffers by wrapping indices around and doing the equivalent of memset() and memcpy() on circular buffers or from linear buffers to circular buffers.

    • generic_buffer.c / generic_buffer.h
      A generic buffer is a structure that contains a buffer (usually used as a string, otherwise as a byte stream) as well as a count of how many bytes are currently used and how many are allocated. This allows for the buffer to be enlarged as needed, to avoid inefficiencies that come from running strlen() to know where a string ends, and to have functions that behave on strings just like on a FILE pointer. For instance bufprintf() works just like fprintf() except on a string (as a generic buffer), which is rather convenient, and bufwrite() works like fwrite(), which makes creating binary files in memory much easier.

    • comparison.c / comparison.h
      Contains often-needed comparison functions that like strcmp() can be used with qsort() or bsearch(). array_find_index_by_cmp() uses binary searching to find an index in a pre-sorted array based on a given value, make_order_index_array() makes an array that gives the index at which the element at the same index in an array would be if that array was to be sorted, and index_value_permutation() permutates such an array of indices so that we can access the elements of an unsorted array in the order that it would be in if it was sorted. Contains strcmp_logical() which is like strcmp() but takes numeric values into account.

  • geometry/

    • rect.c / rect.h and rect_struct.h
      Vectors for rectangles, integer rectangles and triangles, and functions to create, transform or get information from such rectangles. Also function to scale coordinates between pixel coordinates and the area rectangle the image fits into.

    • intersection.c / intersection.h
      Functions to computer intersections between lines, find the distance between a point and a line, find the segment of a line inside a rectangle, intersect rectangles, intersect a circle with a rectangle, find if a point is inside a rectangle or circle, or find the angular radius of a circle from a point.

    • rotation.c / rotation.h
      Handle 2D and 3D vector rotation.

    • fit.c / fit.h
      Functions to find how to best fit squares or rectangles in an area, manage a Hilbert curve grid, fit a rectangle inside a rectangular area or subdivide a rectangle.

    • distance.c / distance.h
      Calculate the hypothenuse in 2D and 3D and move a point away from another point using a given distance multiplier.

    • bezier.c / bezier.h
      Can turn an array of points into an array of cubic Bézier segments with automatic handles and evaluate an entire array of Bézier segments for a given value of x.

    • misc.c / misc.h
      Mainly triangle functions.

  • math/

    • functions.c / functions.h
      Basic math functions, normalised notation split, counting decimal places, the inverse error function, the integral of the error function, functions to find a value or interpolated value from an array of 2D points.

    • cpu_functions.c / cpu_functions.h
      Provides access to CPU functions such as x86’s RSQRT and functions for refining the results.

    • ieee754.c / ieee754.h
      IEEE-754 floating point manipulation, fractional part calculation and dealing with numbers at the level of units of least precision to guarantee incrementation or make floating point comparisons that take a degree of error into account.

    • double_double_arithmetic.c / double_double_arithmetic.h
      Double-double arithmetic for higher precision calculations. Mainly based on code from there which is itself based on the QD library. Also contains a double-double equivalent of strtod with exact rounding.

    • rand.c / rand.h
      Functions to generate uniform pseudo-random numbers in a given range, nmumbers with a normal (Gaussian) distribution and a PRNG based on xorshift-multiply hashing.

    • dsp.c / dsp.h
      FFT size calculation, basic array operations, root mean square computation, decibel conversion, Blackman window, short Gaussian window (an original way to make a Gaussian window finite with desirable characteristics) and its integral, polynomial step functions, ramp filter kernel computation.

    • dct.c / dct.h
      Discrete Cosine Transform computation for JPEG 8x8 blocks.

    • matrix.c / matrix.h
      Matrix determinant and inverse, including MPFR versions.

    • physics.c / physics.h
      Euler physical integration.

    • debug.c / debug.h
      Something used by my graphing calculator.

    • polynomials.c / polynomials.h
      Polynomial evaluation (1D and 2D), error calculation for approximations, coefficient operations, Chebyshev fitting by discrete cosine transform, conversion of Chebyshev coefficients to polynomial coefficients, digit reduction to make coefficients take up less digits and an attempt at optimising fits to reduce the maximum error, evaluation of Chebyshev coefficients by the Clenshaw method, integration of Chebyshev polynomials.

    • polynomials_mpfr.c / polynomials_mpfr.h
      MPFR-based versions of the functions of polynomials.c for fitting and evaluating polynomials at high arbitrary precisions, usually for the purpose of generating lookup tables.

    • gaussian_polygon.c / gaussian_polygon.h
      Reference implementation and reference approximation for the acute angle Gaussian polynomial. Implementation approximations are fitted on this.

  • approximations/

    • fixed_point.c / fixed_point.h
      Fixed point approximations for the square root, division, log2m exp2, cosine, atan2, the Gaussian function and the error function.

    • fast_float.c / fast_float.h
      Floating point approximations for log2, exp2, pow, the square root (which tends to be slower than just using sqrt()), linear to sRGB gamma conversion and exp over a small range.

    • fast_cos.c / fast_cos.h
      Floating point approximations for the cosine at different levels of precision, in turns or radians.

    • high_prec.c / high_prec.h
      Higher precision (usually with maximum errors around the 10-33 - 10-30 range) approximations in double-double arithmetic for functions such as the square root, sine/cosine, arcsin, exp/exp2, erf.

    • simd.c / simd.h
      Intel x86 SIMD versions of cosine, Gaussian, error function and linear to sRGB conversion approximations.

  • graphics/

    • graphics.c / graphics.h and graphics_struct.h
      Functions to manage the raster_t and framebuffer_t structures and access pixels in various formats.

    • pixel_conv.c / pixel_conv.h
      Conversions between different pixel formats, with Intel x86 SIMD versions.

    • srgb.c / srgb.h
      Conversions between linear valued and gamma-compressed sRGB.

    • sqrgb.c / sqrgb.h
      Conversions for the 10-bit square root pixel format.

    • yuv.c / yuv.h
      YUV conversions.

    • colour.c / colour.h
      Colour operations, inversion, HSL conversions, channel access, out-of-gamut desaturation and luminosity adjustment.

    • blending.c / blending.h and blending_struct.h
      Pixel blending functions.

    • flattop_interpolation.c / flattop_interpolation.h
      Functions used for flat-top filtering/interpolation of images.

    • blit.c / blit.h
      Image blitting with no scaling, flat-top filtering or bilinear interpolation and rotation.

    • drawline.c / drawline.h
      Draw Gaussian-antialiased thin lines and Gaussian dots. Also contains a generic Bresenham line drawing function that takes a function pointer to edit a pixel in custom ways.

    • drawrect.c / drawrect.h
      Draw Gaussian-antialiased full or dark rectangles.

    • drawpolygon.c / drawpolygon.h
      Draw Gaussian-antialiased full concave or convex polygons.

    • drawprimitives.c / drawprimitives.h
      Draw Gaussian-antialiased circles (full or hollow), hollow rectangles and chamfered (angular) rectangles, rounded rectangles, dashed lines, Gaussian gradients and dots, hollow triangles and mouse cursors.

    • drawqueue.c / drawqueue.h
      Drawing queue rendering system. Instead of directly rasterising graphic primitives from the main thread, drawing commands are added to the drawing queue for later rendering, usually in OpenCL by the GPU, using multiple arrays that describe a grid containing each its own sequence of drawing instructions for a square area of pixels.

    • drawqueue_enqueue.c / drawqueue_enqueue.h
      Optional part of the drawing queue system. Instead of directly queueing from the main thread, which can be rather time-consuming, this allows to enqueue simpler drawing commands (essentially a simple reflection of the arguments provided by the caller for each drawing function) that require practically no preprocessing from the main thread to be processed for addition to the drawing queue in another dedicated thread, the "dqnq thread".

    • drawqueue/

      • drawqueue_soft.c / drawqueue_soft.h
        Render drawing queue on the CPU with multiple threads using SIMD instructions. Used as a fallback for the OpenCL renderer. The 3 files that follow implement the functions needed by this system to draw rectangles, lines and images.

      • drawrect.c / drawrect.h

      • drawline.c / drawline.h

      • blit.c / blit.h

      • opencl/
        The files below make up the OpenCL version of my drawing queue renderer.

        • bits.cl
          Read bits in a binary stream. Used for decoding my compressed texture format which uses 3 bits per pixel.

        • blending.cl
          Various blending modes available when closing a bracket.

        • blit.cl
          Read textures in various formats and blit them with flat-top filtering or bilinear interpolation and rotation.

        • colour.cl
          CIELab L, YUV and HSL conversions, colour matrix and transfer curves.

        • drawcircle.cl
          Draw full and hollow Gaussian-antialiased circles.

        • drawline.cl
          Draw Gaussian-antialiased thin lines and Gaussian dots.

        • drawqueue.cl
          Includes all the other .cl files and is compiled to drawqueue.cl.h for inclusion in the program, which then gets compiled by the OpenCL compiler at run time to generate the OpenCL kernels. Reads the buffers sent from the host and interprets them to execute the drawing functions.

        • drawrect.cl
          Draw full Gaussian-antialiased rectangles, either additively or with multiplication to make dark rectangles. Can also add a plain colour to a rendering sector which is used in optimising the drawing of the insides of full rectangles and circles far from the edges.

        • drawtriangle.cl
          Draw full Gaussian-antialiased triangles or tetragons. It took me from 2014 to 2022 to come up with this brilliant algorithm. Have a look at this shader to see what it does and how it works.

        • gaussian.cl
          Fast error function (erf()) approximation.

        • rand.cl
          Xorshift-multiply hashing PRNG used to generate a practically non-repeating Gaussian noise texture used for dithering.

        • srgb.cl
          sRGB conversions, out-of-gamut colour desaturation and Gaussian dithering and optional quantisation (with max_s) to simulate lower colour bit depths.

        • trig.cl
          Arccos and arcsin approximations.

    • draw_effects.c / draw_effects.h
      Apply effects to the image using the drawqueue system.

    • processing.c / processing.h
      Apply Gaussian blur to an image, scale an image using flat-top filtering, interpolate a pixel with bilinear interpolation and apply operations to pixels.

    • mipmap.c / mipmap.h
      Turns a simple raster image into a multi-level tiled mipmap.

    • cl_memory.c / cl_memory.h
      Manage the memory buffer used to exchange data by the drawqueue system. For instance an image that must be displayed on screen will have its pixel data copied to the big buffer, then when using OpenCL the updated parts of that buffer will be copied to the GPU so that a GPU-side mirror of this buffer is maintained and the image can be displayed using the OpenCL code on the GPU.

    • texture_compression.c / texture_compression.h
      My original texture compression. It compresses each block of pixels to 3-bits and uses two colours for each block, giving 8 possible colours being interpolated between those two colours.

  • vector/

  • text/

    • unicode_data.c / unicode_data.h
      Gives access to Unicode data about each Unicode codepoint.

    • unicode.c / unicode.h
      Functions to deal with UTF-8 or UTF-16 strings.

    • unicode_bidi.c / unicode_bidi.h
      Used to decompose a UTF-8 string into sections depending on the directionality of its codepoints (for instance when mixing Arabic, Latin and digits).

    • unicode_arabic.c / unicode_arabic.h
      Allows the text renderer to know which form of an Arabic letter to use.

    • parse.c / parse.h
      Various text parsing tools to skip parts of a string, extract fields according to separators, convert my dozenal number format (for instance "1;4" becomes 1.333…​), count lines, find lines, convert a multiline string into an array of lines, case insensitive string search, memmem() replacement where needed, pattern finding (like date-time or timestamps), parse files with XY vector data, parse XML fields, parse musical notes such as "G#3+02.1".

    • format.c / format.h
      Various string printing functions to print large numbers in a readable way, print dozenal numbers, print English ordinals (like 1st, 4th, 11th, 22nd), convert short months like "Jan" to long months like "January", transform date strings, print compilation and compiler information, indent lines, print time, print duration in a nice readable way (like "1 day and 8.3 hours"), print timestamps, convert text to a C string literal with proper escaping and conversion of a file to a C header that contains a string representing the contents of the file.

    • string.c / string.h
      String utilities to copy strings, replace all instances of a character in a string, convert a string to lowercase, alternatives to sprintf() with options to allocate, reallocate and append, string line insertion or replacement.

    • edit.c / edit.h and textedit_struct.h
      Text editor GUI control with undo functionality.

    • undo.c / undo.h
      Undo functionality of text editor controls.

    • history.c / history.h
      I have no idea what this is, wtf.

  • vector_type/

    • vector_type.c / vector_type.h and vector_type_struct.h
      Mainly contains functions to retrieve glyphs from codepoints.

    • make_font.c / make_font.h
      Generates the vector typeface from the markup.

    • draw.c / draw.h
      Draw a string on screen using the vector typeface.

    • stats.c / stats.h
      Functions to obtain the width of glyphs, and using those find the width of words and lines, which are then used to fit strings into rectangles.

    • fit.c / fit.h
      Fit strings into rectangles with line breaks either to maximise the size of the letters or to break the lines at a given threshold. Currently only works with strings that don’t already contain line breaks, except for draw_string_bestfit_asis() which doesn’t add line breaks.

    • cjk.c / cjk.h
      Generates CJK glyphs by combining strokes using a list of combinations. Doesn’t really produce anything readable.

    • truetype.c / truetype.h
      Loads a TrueType font as a triangle mesh and can render a triangle mesh into a polynomial grid.

    • insert_rect.c / insert_rect.h
      An insert rect is a space in a string to be drawn on screen, and that space has its coordinates reported so that they can be used to insert an interface element where the space is. The width of the space is specified by combining private use Unicode codepoints that each represent a different spacing, the sum of which is the width of the space, and then a variation selector character which represents an index in an array. So for example by using the character that represents a space of 36 units, followed by the character that represents 3 units, followed by the character that represents an index of 0, I can make a space 39 units wide in some text and then by querying the insert rect at index 0 get the on-screen coordinates for that space so that I can place something there. I use it mainly for inserting text edit controls to enter a value in the middle of some text, so the text input field is automatically adjusted to be the right size and position to fit in perfectly.

  • gui/

    • zoom.c / zoom.h and zoom_struct.h
      Zoom-scroll system, with conversion between "world coordinates" (positions on the infinite plane) and screen coordinates and handling of the mouse to scroll around, zoom in and out, reset the view or change the zoom level and focus programatically. The GUI system relies on this system.

    • focus.c / focus.h and focus_struct.h
      Can affect the drawing thickness and brightness of drawn elements to simulate a camera being out of focus, albeit with Gaussian blur rather than a circular kernel.

    • positioning.c / positioning.h
      Convert local coordinates to world coordinates using an offset and scale. This is used when making interface elements fit into an area.

    • layout.c / layout.h
      Manage control grids.

    • drawelements.c / drawelements.h
      Drawing functions for various interface elements.

    • visualisations.c / visualisations.h
      Draw a bar graph.

    • inputprocessing.c / inputprocessing.h and inputprocessing_struct.h
      The core of the GUI system, this is how controls are identified when hovered or clicked and how each type of control processes mouse inputs.

    • knob_functions.c / knob_functions.h
      Knobs can use a few different functions so that they don’t have to always be linear. This includes the logarithmic scale, the reciprocal scale, the offset decibel scale (a scale that goes to -infinite dB, perfect for decibel volume knobs), the offset logarithmic scale (which can be nearly linear at 0 and nearly logarithmic at the other end), and the very convenient tangential scale which is linear around 0, handles negatives as well as positives and increases to any limit needed at the ends, meaning you can have a knob with a range that mainly covers small numbers but still can have an extreme range of possible values.

    • controls.c / controls.h and controls_struct.h
      How the basic GUI controls are created.

    • control_array.c / control_array.h
      Program arrays of controls.

    • selection.c / selection.h
      Generic selection logic, the kind you expect when selecting files in a folder.

    • make_gui.c / make_gui.h
      Make a GUI from layout markup, which can be generated by using the toolbar found in the next file. Contains functions to implement every element type using the data in a layout structure and various functions to modify them.

    • editor_toolbar.c / editor_toolbar.h
      Toolbar to create and modify GUI layouts.

    • floating_window.c / floating_window.h
      Create and handle floating windows with a draggable title bar, a resizing control, a pinning control and a closing button.

    • window_manager.c / window_manager.h
      Window functions can be added to a registry and the window manager runs the window functions in an order that it manages mainly based on which window was last clicked and relations between subwindows and their parent window.

  • fileio/

    • bits.c / bits.h
      Read, set or manipulate bits in a bit stream. Also reverse iterators.

    • open.c / open.h
      fopen() wrapper that takes UTF-8 paths, with another version that can create any folder needed to make the file being opened writable, query a file’s size, load a file into memory, optionally with conversion of DOS line endings, save a whole buffer or string or array of strings to file, count lines in a text file, check if a file or folder exists.

    • io_override.c / io_override.h
      By appending _override to standard functions fopen, fclose, fread, fwrite, we can switch between using FILE streams and using buffer_t generic buffers without changing code, so a same function can work on either type by having either io_override_set_FILE() or io_override_set_buffer() called first.

    • endian.c / endian.h
      Functions to load or write 8 to 64 bit integers in little or big endian to and from memory, file or generic buffer. By default to speed things up the platform is assumed to be little endian, this can be changed by defining RL_DONT_ASSUME_LITTLE_ENDIAN.

    • image.c / image.h
      Convert images between different pixel formats, load and save images and image tiles in various formats, partly using stb_image and stb_image_write.

    • image_bmp.c / image_bmp.h
      Save BMP images.

    • write_icc.c / write_icc.h
      Write an ICC profile to file. Currently only does linear sRGB and greyscale.

    • image_tiff.c / image_tiff.h
      Load TIFF files in 8, 16 and 32-bit bits per channel, with either no compression or LZW compression. Save images to 32-bit per channel TIFF files, uses a couple of different methods to make TIFF files (non-BigTIFF) up to 8 GB that open with almost any program (even though in theory TIFF supports only 4 GB) or TIFF files even larger than 8 GB that open with many other programs (such as Photoshop or GIMP).

    • image_tiff_lzw.c / image_tiff_lzw.h
      TIFF LZW decoder.

    • image_fts.c / image_fts.h
      Load NASA FITS images. Only tested with SOHO mission images.

    • image_screen.c / image_screen.h
      Function to take screenshots of the main display on Windows.

    • sound_format.c / sound_format.h
      Convert between different sound sample formats and channel layouts.

    • sound_aiff.c / sound_aiff.h
      Load and save AIFF sound files.

    • sound_wav.c / sound_wav.h
      Load and save WAVE sound files.

    • path.c / path.h
      Transform file paths to remove, extract or append elements, make appdata paths to save configuration files, check that a path is to an image or video file or change the current working directory.

    • dir.c / dir.h and dir_struct.h
      Load a directory to any depth as a tree using a modified version of dirent.c / dirent.h (I fixed some bugs from the original this was based on and reorganised it a bit) on Windows. Query volumes and their free space on Windows.

    • file_management.c / file_management.h
      Create symbolic links, folders (even with multiple depths), move, copy, remove files and folders, open files or URLs in whatever program they’re usually opened, show files in Explorer (Windows).

    • process.c / process.h
      Launch another process (Windows). Load the program’s commandline arguments argv at any point in the program.

    • dynamic_loading.c / dynamic_loading.h
      Loading of dynamic libraries.

    • fileball.c / fileball.h and fileball_struct.h
      Manage fileball archives, a simple format to pack many files into one and optionally compress them using Deflate (via miniz). This is used to pack the many files that make up the vector typeface into a C header with compression.

    • prefs.c / prefs.h
      Preference management system. This is what I use to load and save preferences in my programs. Besides being quite elegant and readable it can be used independently by different parts of a program without any awareness of what the other parts do.

    • dialog.c / dialog.h
      Open and Save file dialogs on Windows and macOS. Both platforms use the same filter format.

    • ffmpeg_sound_player.c / ffmpeg_sound_player.h
      Callback to load and play an audio stream using FFMPEG. The seeking leaves a bit to be desired, it can be quite slow compared to the video stream seeking and I don’t know why.

  • interpreter/
    RLIP, my interpreted programming system.

    • interpreter.c / interpreter.h
      The opcodes, structures and default inputs are defined there.

    • compilation.c / compilation.h
      The complicated part of RLIP where source code is turned into a simple list of opcodes.

    • execution.c / execution.h
      The simple part of RLIP where the compiled opcodes are ran.

    • decompilation.c / decompilation.h
      The decompiler shows what the opcodes mean so I can check what’s really going on.

    • real_functions.h
      This defines the custom real arithmetic options. To define each format structures are filled and can be added to the RLIP compiler inputs with a simple macro such as RLIP_REAL_DOUBLEDOUBLE for double-double arithmetic.

    • expression.c / expression.h
      This turns mathematical expressions into RLIP programs. This serves as a replacement for the more limited TinyExpr.

  • wahe/

  • libraries/

Vector typeface

The vector_type folder contains the files that make up my vector typeface which is embedded into rouziclib. Unlike usual digital typefaces which typically define outlines using splines, with a set of different fixed weights, my typeface defines strokes, it is only made of thin straight lines and it has only one weight which is infinite thinness. Simplicity is the goal, both simplicity of design, each glyph is made of a few straight lines, no curves, no outlines, no weights, so the glyph for A is defined with 3 straight lines instead of a more typical 10 or 11 for outlines, and simplicity of implementation, as every glyph can be displayed provided only that the current display system has an implementation for draw_line_thin(). I currently have 4 different display systems in rouziclib (fixed-point framebuffer, floating-point framebuffer, OpenCL drawing queue and CPU multithreaded SIMD drawing queue) and every system lacks features that another system might have. With this I only need to get simple line drawing implemented to be able to display text and interface symbols that come from the typeface.

Another difference with typical computer fonts is that the files that make up my typeface are meant to be read and edited by hand (although my project Spacewar contains tools to generate glyphs, which I used mainly to make Japanese kana glyphs by tracing). I believe that readable text markup is a powerful way to represent anything, be it entire projects (like the PHA files of Photosounder), configuration files, or as in this case the data and commands needed to generate a typeface. Unlike meme formats like XML or JSON it’s more readable, elegant, natural and doesn’t require the use of a parsing library (little more than sscanf() is needed to parse my markup), it doesn’t require specialised tools, the data is more portable because it’s more understandable, and because it’s so simple and natural it’s more future-proof, a future parser can easily make use of outdated markup.

Structure

When rouziclib loads the typeface, it starts by loading type_index.txt, which itself points to 3 types of files through 3 commands.

  • range points to a file that represents a Unicode range of glyphs. The range start and end points indicated are only there so that my glyph design tool (in Spacewar) would know in which file to put a glyph.

  • substitutions points to the file that defines certain glyphs as being exactly another glyph. For instance the Greek Ο, the Cyrillic О and the Armenian Օ all look pretty similar to the Latin O, so substitution is used. When the renderer tries to render a Cyrillic О it draws the Latin O instead, so we don’t even store a copy of the substituted glyph.

  • cjkdecomp contains commands (coded as a number) to assemble two CJK glyphs into a new one, so that the basic strokes I designed can be assembled into making any CJK glyph. Because it’s a rather heavy file and that very few glyphs looked correct it’s excluded from the compressed header used to embed the typeface in a program. It’s based on this file and hopefully one day in the future I’ll have a better more solid way of generating CJK glyphs.

Glyph notation

Let’s have a look at a simple glyph, A:

glyph 'A'
        p1      2       0
        p2      1;2;8   2;4
        p3      0       6
        mirror  v 0     p1 p2   p4
        lines   p1 p3 p4
        lines   p2 p5
        bounds  -1;9 1;9

There are many things to explain. The shape of the glyph A is defined by only 3 points, this is because it’s a symmetrical glyph, it would be inelegant to not automate symmetry along the vertical axis. So 3 points are defined, p1, the bottom of the right leg at x=2 and y=0 (x=0 is the middle of the glyph, y=0 is the baseline, y=6 is the cap height), p3 is the top middle point, and p2 defines the middle bar. Points p1 to p2 are then mirrored along the vertical axis, the mirrored points starting from p4, and then those 5 points need to be connected to form lines, p1 to p3 and then p4 to form the Λ part of the glyph, and then p2 to p5 for the horizontal line. The last command is optional but quite necessary for such a glyph, it’s used for kerning. Without it the glyph would be considered to have bounds at x=-2 and x=2, which in the case of glyphs such as A or T looks wrong, it makes them look like they’re too far away from other letters. By setting narrower bounds the distance to other letters (which is 1.5 units by default) is reduced, in this case by 0.25 units.

Quasi-base 12 notation

Now you must be wondering, what’s going on with the semicolons? What can "1;2;8" possibly mean? Well you see, it’s nice to define points on a 4 by 6 grid, but what if you need to go finer, what if you need to put a point on a third of a unit? Should I write it as "0.33333333333333333"? That’s very inelegant. We use base 10, and the problem with this is that it’s only good if you want to represent halves, fifths or tenths. If you want to represent thirds, sixths, ninths or twelfths it’s absolutely horrible. Unfortunately using thirds is a very essential part of geometrically designing such things as glyphs, so I wanted a better approach. I chose to represent numbers as a combination of fractions of powers of 12, separated by a semicolon. For instance 2;10;8 means 2 + 10/12 + 8/144, in other words 2.888888888888. Since it’s not a strict dozenal representation it’s easy to type (no need for special characters for 10 and 11), and since it’s a way to represent fractions real numbers can be used to make up for the deficiencies of a dozenal system. For instance 1/60 (0.01666666) in strict dozenal notation also yields an endless number, whereas with my approach I can simply write it as 0;0.2, which means 0 + 0.2/12. It’s simple and elegant.

The point p2 of the glyph A is a good example of the need for such notation. The middle bar looked way too high at a height of 3 units, even 2.5 units looked too high, and 2 and a third seemed like a natural choice, so it was convenient to represent the height as 2;4. Then came the problem of the horizontal position, the bar has to stop right where it meets the diagonal lines. That means it has to stop at a x position of 2 * (6-(2+1/3))/6 = 1.2222222222, or 1;2;8. The number 1;2;8, by only taking 5 characters yet having full precision, fits nicely in its 8-character column. When designing things geometrically you need to think in terms of fractions and formulas, therefore a suitable representation is needed. That itself helps you in not being scared of using fractions that are inconvenient to use in decimal notation.

I also use a similar notation for angles. For geometrical design I think it’s most sensible to think of angles as fractions of a full circle, using radians would be absurd, and in a way traditional notation by degrees lets you do this, but in a flawed way (because you must multiply everything by 360 and 360 isn’t a multiple of 16, and frankly I’m not a fan of using a strange number like 45 to represent an eighth of a circle). If you want to represent an angle that is a third of a circle, it would make sense to represent it as 0;4. However since in geometry there is hardly any use for angles that are larger than a full circle, that means every angle in my notation would have to start with "0;". So I do away with it and simply represent a third of a circle as 4. This notation has advantages over using degrees, for instance 1/32 of a circle is 11.25°, a rather awkward 4-digit number, but simply 0;4;6 in my representation, which is quite simple when you think of ";6" as simply "a half". That representation also has the benefit of matching the familiar clock face, you already know what a 2 o’clock angle looks like.

Curve segments, subglyphs and other advanced features

I use this notation in designing glyphs for rotations or the command called curveseg which defines a new point that forms a line that forms a curve by defining an angle to the previous line in the curve, and also defining a length that is either a ratio of the previous line’s length or an absolute length. My design for the glyph ֍ is a good example of this:

glyph 058D
	subglyph A
	p1      0       0
	p2      0       0;10;9
	curveseg p3     0;9     0;10x
	curveseg p4     0;9     0;10;3x
	curveseg p5     0;9     0;10;6x
	curveseg p6     0;9     0;10;9x
	curveseg p7     0;9     0;11x
	curveseg p8     0;9     0;11;3x
	curveseg p9     0;9     0;11;6x
	curveseg p10    0;9     0;11;9x
	curveseg p11    0;9     0;10;0;10;5x
	lines p1 to p11
	subend

	copy 058D A
	rotate 1;6
	copy 058D A
	rotate 1;6
	copy 058D A
	rotate 1;6
	copy 058D A
	rotate 1;6
	copy 058D A
	rotate 1;6
	copy 058D A
	rotate 1;6
	copy 058D A
	rotate 1;6
	copy 058D A
	rotate 1;6
	move 0 3

It starts with a subglyph called "A" (subglyphs get a single ASCII character to identify them), a subglyph which will be copied, then the whole glyph rotated by 1;6 (one eighth of a circle or 45°), this process repeated eight times and then the whole glyph moved up 3 units to be properly centered. The design of the subglyph, which is one branch of the symbol, is the most interesting. It starts with a vertical line, p1, the center of the whole glyph and start of this branch, and p2, the first point along the curve. The vertical position of p2 defines the scale of the glyph, if p2 was only 0;9 instead of 0;10;9 the whole glyph would be exactly the same except about 84% its current size. The point p3 is defined using curveseg, and what the first parameter means is that the line p2-p3 forms an angle of 0;9 (1/16 of a circle or 22.5°) with the previous line (p1-p2). The second parameter means that the line p2-p3 is 0;10 times the length of the previous line. The lines that follow are each smaller than the line that immediately precedes by a given ratio, however this ratio progressively increases. This means that while lines become smaller and smaller, their length changes less and less. As the ratio goes towards 1, the curve becomes more circular, so that while our branch starts off a bit like a logarithmic spiral, in the end it becomes almost circular. It would be impossible to design such a glyph, least of all in such an elegant manner, without a command like curveseg. Note that the last line, p10 to p11, had its length ratio finely adjusted so that it would stop where it meets the next branch.


The glyph ֍ as it appears in Spacewar's typeface editor.

More such commands are briefly described in make_font.h, such as rect to create points that form a rectangle, circle to create any number of points along a circle, and commands to move, scale, rotate or shear a glyph or parts of a glyph to help in combining glyphs or subglyphs.

RLIP - rouziclib interpreted programming

When parsing expressions with TinyExpr didn’t cut it anymore, as was the case with my image calculator, something else was needed, something that allows you to type in interpreted programs. Since it was made with my image calculator in mind you may think of it as a kind of shader language, except that it runs on the CPU.

Let’s first look at how to run a basic example:

// Inputs
static double x;
rlip_inputs_t inputs[] = { RLIP_FUNC, {"x", &x, "pd"} };
const char source[] = "d v1 = cos x\n return v1";

// Compilation
buffer_t comp_log={0};
rlip_t prog = rlip_compile(source, inputs, sizeof(inputs)/sizeof(*inputs), 1, &comp_log);
if (comp_log.len)
	fprintf_rl(stdout, "Compilation log: %s\n", comp_log.buf);
free_buf(&comp_log);

// Decompilation
buffer_t decomp = rlip_decompile(&prog);
fprintf_rl(stdout, "Decompilation:\n%s\n", decomp.buf);
free_buf(&decomp);

// Execution
x = 4.;
volatile int exec_on = 1;
prog.exec_on = &exec_on;
rlip_execute_opcode(&prog);
fprintf_rl(stdout, "Program returned %.16g\n", prog.return_value[0]);
free_rlip(&prog);

First the inputs. We create a variable x, then the inputs structure which only contains the default functions plus our x which will also be accessed by the name "x". It is defined as a type pd which means a pointer to a double. Then our source code:

d v1 = cos x
return v1

We declare a variable named v1 as a type d (a double) which will receive the result of cos(x). Here cos comes from a function pointer provided in RLIP_FUNC, it’s not a builtin operation. Then v1 is defined as a return value. Every such program must have return values, the number of which is defined by the fourth argument of rlip_compile().

Then the source and inputs are compiled, which as always is a bit complicated, though I tried to keep it simple. Essentially my compiler relies on a registry to keep track of everything, and the argument to each compiled instruction is an index to one of the 3 arrays (besides the array of opcodes), namely an array of double, of int64_t and of pointers. Each opcode has specific types so it knows in which array to look for its arguments. Looking at the decompilation will make it clear:

op_load_d            9      41
op_func1_dd          8       6       9
op_ret_d             8
op_end

The first operation loads the value of x in the double array. x was provided as a pointer which ended up at index 41 in the pointer array, and it will be copied to index 9 in the double array. After this x won’t be loaded anew (this is ensured by keeping all the load commands at the very beginning of the program so they can’t be looped) and will be treated as a normal double variable, not a pointer to a value anymore. After this we can call cos, which was also provided as a pointer and is at index 6 in the pointer array. So op_func1_dd calls the function at the address at index 6 in the pointer array, with one argument which is from index 9 in the double array. It then stores the result at index 8 in the double array. Then this result is copied from the array to prog.return_value, which is how we access it.

Given that we set x to 4.0 before executing the compiled program we obtain the expected result:

Program returned -0.6536436208636119

Before calling rlip_execute_opcode() we set up something you might consider a kill switch. When rlip_execute_opcode() runs it constantly checks for the value pointed to by prog.exec_on, if it’s 0 the execution stops. It’s quite necessary in case you have a program that doesn’t stop running on its own. Since I execute my compiled programs in a thread that also depends on a volatile int flag to keep running I set prog.exec_on to point to the same flag so that the program stops running when I signal for my thread to stop running. Programs get compiled and executed as I type them, one character after the other, so it’s important that invalid programs don’t get executed and that infinite loops aren’t a problem. The compiler also provides very useful error messages so you always know where it chokes.

Language features

I tried to keep the language minimalistic and simple in syntax to keep the compiler simple. It looks more like an assembly language than anything else, and I wanted to make sure that unlike more sophisticated languages with their fancy operators you’d always know the type of the operation you’re writing. For instance in C the operator / could be a floating-point division as well as an integer division. Few question the wisdom of doing this, yet it can be a source of undesired behaviour. To make the operator do what you want you might have to cast the arguments, which isn’t very elegant. With RLIP a division is either div in which case all arguments are cast to double if necessary (using the variables named rd1 and rd2 for temporary storage) or divi in which case the arguments are made int64_t (using ri1 and ri2 if needed). This is particularly important given that arguments provided as numbers or simple mathematical expressions are interpreted as double. The return value can also be cast to the other type automatically using rd0 or ri0 as temporary storage.

Speaking of numbers and expressions, a strange feature of RLIP is that if you do something like v1 = add v1 -1/3 this will create, in the double array, a variable named "-1/3" with the value you’d expect, but a variable nonetheless, so you could access that variable later by that name and give it a different value. Hardly a desirable feature, just what happens when you try to keep things simple.

Let’s go through each coding feature:

  • Variables are declared in the program as type d (double) or i (int64_t) the first time you use them. You can keep the d or i the next time you use the = operator on them but that’s not necessary. A valid example:

d x2 = sq x
x2 = add x2 0.5
d x2 = sqrt x2
  • Locations, which is where goto commands go to, are defined by a name followed by :. For instance loop_start:. Unlike with variables it’s possible to goto a location that is defined later in the program, so both backward and forward jumps are possible.

  • When using <variable> = …​ there are a few possibilities. What follows can be a builtin command with arguments such as v1 = sub v1 v2, it can be a simple assignment like i max_iter = 400, loading a value from an input pointer, copying a value (with casting if needed) like x1 = x_tmp, or running a function provided as a function pointer like d xc = clamp x.

  • Comparison is its own operation which returns either 0 or 1 as an integer and must be stored in a variable declared as type i. For example: i cond1 = cmp x < 0 makes the integer variable named "cond1" contain the result of the comparison. The command cmp compares two arguments as double, the command cmpi as int64_t. This makes such things as conditional jumps based on two conditions possible:

i cond1 = cmpi ni < max_iter
i cond2 = cmp x2 < 4
cond1 = and cond1 cond2
if cond1 goto loop_start
  • Other commands start directly with the command name, like the statement if <condition integer variable> goto <location>, or the mandatory return <variables> command as well as the commands of convenience set0 <variable> or inc1 <variable>, both of which have the distinction of having the operand type depend on the type of the variable, since there is only one variable that is both the source and the destination.

  • When a line starts with expr the rest of the line is treated as an expression, turned into RLIP commands which are then inserted into the RLIP listing at the start of the compilation. So for instance you can write expr d v = 0.5+0.5cos(x/(2*pi)) which will be internally replaced by the following lines:

d exprd_v0 = mul 2 pi
d exprd_v0 = div x exprd_v0
d exprd_v0 = cos exprd_v0
d exprd_v0 = mul 0.5 exprd_v0
d v = add 0.5 exprd_v0
  • Unrecognised lines are just ignored, as are things that follow the expected number of arguments, so we can have comment lines or comments appended to regular lines.

The execution code makes it fairly clear what each operand does and how it is used in code.

RLIP fractal example

Here’s a good example of what can be done with RLIP. This only takes pointers to double variables x and y as input, and the output is 3 values that define the pixel colour for these input coordinates.

// Parameters
d threshold = 4
i max_iter = 400

d x1 = 0
d y1 = 0
i ni = 0

// Fractal loop
loop:
    d x2 = sqsub x1 y1
    x2 = add x2 x
    y1 = mmul y1 x1 2
    y1 = add y1 y
    x1 = x2

    inc1 ni
    i cond1 = cmpi ni < max_iter

    x2 = sqadd x1 y1
    i cond2 = cmp x2 < threshold
    cond1 = and cond1 cond2
if cond1 goto loop
// Loop end

// Calculate colours based on iteration count
d v = log ni
v = mad v 80 1600

d red = mad v 0.036 0.2
d grn = mad v 0.0187 0.2
d blu = mad v 0.013 0.2
red = sin red
grn = sin grn
blu = sin blu
red = mad red 0.5 0.5
grn = mad grn 0.5 0.5
blu = mad blu 0.5 0.5
end:
return red grn blu


The output when this little program runs on every pixel of an image.

In case you’re curious to see what it decompiles to:

op_load_d           17      40
op_load_d           15      39
op_set_d             8       9
op_cvt_d_i           8      10
op_set_d            11      12
op_set_d            13      12
op_cvt_d_i           9      12
op_sqsub_dd         14      11      13
op_add_dd           14      14      15
op_mmul_ddd         13      13      11      16
op_add_dd           13      13      17
op_set_d            11      14
op_inc1_i            9
op_cmp_ii_lt        10       9       8
op_sqadd_dd         14      11      13
op_cmp_dd_lt        11      14       8
op_and_ii           10      10      11
op_jmp_cond         10     -38
op_cvt_i_d           2       9
op_func1_dd         18      10       2
op_mad_ddd          18      18      19      20
op_mad_ddd          21      18      22      23
op_mad_ddd          24      18      25      23
op_mad_ddd          26      18      27      23
op_func1_dd         21      14      21
op_func1_dd         24      14      24
op_func1_dd         26      14      26
op_mad_ddd          21      21      28      28
op_mad_ddd          24      24      28      28
op_mad_ddd          26      26      28      28
op_ret_ddd          21      24      26
op_end

Custom arithmetic

We’ve used the d type for double variables and pointers, the i type for int64_t, but there’s also a third type, r, and this one is whatever you want it to be. I created it so that I could use double-double arithmetic or MPFR’s arbitrary precision arithmetic with RLIP, which allows me to calculate in higher precision or even use RLIP to implement code that uses MPFR, because MPFR syntax is awkward and ugly.

The file real_functions.h shows how a custom arithmetic for RLIP can be added. Functions to do basic operations in the custom format are created and referenced in structures that are provided to the RLIP compiler. Then all that is needed is to add the macro for the chosen arithmetic to the RLIP compiler inputs like so for example:

rlip_inputs_t inputs[] = { RLIP_REAL_DOUBLEDOUBLE, RLIP_FUNC, {"x", &x, "pd"} };

An example RLIP program would be const char source[] = "r v1 = cos_ x\n return_real v1";, v1 is declared as a r which is our custom real format (in this case a double-double), and cos_ is defined in real_functions.h as the double-double implementation of cos. Finally we use return_real instead of return to return the number in our format instead of double. We can read the higher precision result like so:

ddouble_t result = *((ddouble_t *) &prog.return_real[0]);
fprintf_rl(stdout, "{%.17g, %.17g}\n", result.hi, result.lo);

Which gives us the result in double-double format: {-0.65364362086361194, 2.5846614087018284e-17}. The same can be done with RLIP_REAL_MPFR without changing the source. We obtain the result by doing this:

mpfr_t *result = ((mpfr_t *) &prog.return_real[0]);
mpfr_fprintf(stdout, "Program returned %.60Rg\n\n", *result);

Giving us a very high precision result of -0.65364362086361191463916818309775038142413359664621824700701.

Expression parsing

With expression.c we can parse mathematical expressions such as 0.5 + 0.5 * -cos(2pi (x-1)) into RLIP. My expression parser offers several advantages over TinyExpr (which I would previously use extensively) which compel me to move away from the latter:

  • My expression parser works with integers (which are used as results for comparisons and can be used as function arguments) and either doubles or the custom arithmetic format whereas TinyExpr only uses doubles for everything.

  • Implied multiplications are supported. For instance 2x means the same thing as 2*x and x cos(pi x) the same as x*cos(pi*x).

  • TinyExpr parses recursively, generates a recursive data structure and the execution is also recursive, which isn’t optimal for clarity and speed of execution. In contrast my parser proceeds in simple loops over a string and then a symbol array and produces a flat RLIP listing which is itself compiled into a flat list of opcodes.

  • Both my expression parser and the RLIP interpreter output detailed error information, so if you have unmatched brackets you’ll know it by looking at the log. TinyExpr doesn’t tell you what’s wrong with your expression.

  • I wrote my expression parser so it’s easy for me to change it to suit my needs, whereas I tried but failed to modify TinyExpr.

  • My expression parser can interpret my dozenal numbers as numbers, so interpreting 1;4 * 3 will give 4.0.

An expression may be compiled and executed multiple times like so:

// Inputs
const char expression[] = "0.5 + 0.5erf(2x)";
static double x;
rlip_inputs_t inputs[] = { RLIP_FUNC, {"x", &x, "pd"} };

// Expression interpretation + compilation to opcodes
rlip_t prog = rlip_expression_compile(expression, inputs, sizeof(inputs)/sizeof(*inputs), 0, NULL);
volatile int exec_on = 1;
prog.exec_on = &exec_on;

// Execution
for (x = -3.; x <= 3.; x += 0.125)
{
	rlip_execute_opcode(&prog);
	fprintf_rl(stdout, "f(%6g) = %.16f\n", x, prog.return_value[0]);
}
free_rlip(&prog);

Tools

Tools used to generate parts of rouziclib, such as the compressed typeface header, the many lookup tables or the C header containing the OpenCL code, are described here.

Quick reference

This library being my own personal library, the documentation is sparse and limited to the bare minimum that I need to use it. The bulk of it is in quick_reference.h, which has the .h suffix only to benefit from syntax highlighting. I consult this file when coding something, as a sort of template. For instance if I want to draw a line, I don’t remember the syntax so I just copy the line in quick_reference.h that says draw_line_thin(sc_xy(p0), sc_xy(p1), drawing_thickness, white, blend_add, intensity); and then adapt it to my needs. Same thing when I want to redimension an array as needed, I search for alloc_enough in the file and adapt the relevant line. It can also serve as a checklist of sorts, for instance when making threads, to make sure I haven’t forgotten anything. Having templates all in one file beats looking through source code for examples of how to use a function, so having a file filled with ready to copy examples of how to use a functionality and occasionally with an explanation of what arguments do is something I recommend. I typically add code to this file when I catch myself looking more than once for an example of code of one functionality of rouziclib.