/* libmypaint - The MyPaint Brush Library
 * Copyright (C) 2007-2011 Martin Renold <martinxyz@gmx.ch>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <assert.h>

#if MYPAINT_CONFIG_USE_GLIB
#include <glib.h>
#include "glib/mypaint-brush.h"
#endif

#include "mypaint-brush.h"

#include "mypaint-brush-settings.h"
#include "mypaint-mapping.h"
#include "helpers.h"
#include "rng-double.h"

#include <json.h>

#ifdef _MSC_VER
#if _MSC_VER < 1700     // Visual Studio 2012 and later has isfinite and roundf
  #include <float.h>
  static inline int    isfinite(double x) { return _finite(x); }
  static inline float  roundf  (float  x) { return x >= 0.0f ? floorf(x + 0.5f) : ceilf(x - 0.5f); }
#endif
#endif

// Conversion from degrees to radians
#define RADIANS(x) ((x) * M_PI / 180.0)
// Conversion from radians to degrees
#define DEGREES(x) (((x) / (2 * M_PI)) * 360.0)

#define ACTUAL_RADIUS_MIN 0.2
#define ACTUAL_RADIUS_MAX 1000 // safety guard against radius like 1e20 and against rendering overload with unexpected brush dynamics

#define GRID_SIZE 256.0

/* Named indices for the smudge bucket arrays */
enum {
  SMUDGE_R, SMUDGE_G, SMUDGE_B, SMUDGE_A,
  PREV_COL_R, PREV_COL_G, PREV_COL_B, PREV_COL_A,
  PREV_COL_RECENTNESS,
  SMUDGE_BUCKET_SIZE
};

/* The Brush class stores two things:
   b) settings: constant during a stroke (eg. size, spacing, dynamics, color selected by the user)
   a) states: modified during a stroke (eg. speed, smudge colors, time/distance to next dab, position filter states)

   FIXME: Actually those are two orthogonal things. Should separate them:
          a) brush settings class that is saved/loaded/selected  (without states)
          b) brush core class to draw the dabs (using an instance of the above)

   In python, there are two kinds of instances from this: a "global
   brush" which does the cursor tracking, and the "brushlist" where
   the states are ignored. When a brush is selected, its settings are
   copied into the global one, leaving the state intact.
 */


/**
  * MyPaintBrush:
  *
  * The MyPaint brush engine class.
  */
struct MyPaintBrush {

    gboolean print_inputs; // debug menu
    // for stroke splitting (undo/redo)
    double stroke_total_painting_time;
    double stroke_current_idling_time;

    // the states (get_state, set_state, reset) that change during a stroke
    float states[MYPAINT_BRUSH_STATES_COUNT];
    // smudge bucket array: part of the state, but stored separately.
    // Usually used for brushes with multiple offset dabs, where each
    // dab is assigned its own bucket containing a smudge state.
    float *smudge_buckets;
    int num_buckets;
    int min_bucket_used;
    int max_bucket_used;

    double random_input;
    float skip;
    float skip_last_x;
    float skip_last_y;
    float skipped_dtime;
    RngDouble * rng;

    // Those mappings describe how to calculate the current value for each setting.
    // Most of settings will be constant (eg. only their base_value is used).
    MyPaintMapping * settings[MYPAINT_BRUSH_SETTINGS_COUNT];

    // the current value of all settings (calculated using the current state)
    float settings_value[MYPAINT_BRUSH_SETTINGS_COUNT];

    // see also brushsettings.py

    // cached calculation results
    float speed_mapping_gamma[2];
    float speed_mapping_m[2];
    float speed_mapping_q[2];

    gboolean reset_requested;
    json_object *brush_json;
    int refcount;
};

/* Macros for accessing states and setting values
   Although macros are never nice, simple file-local macros are warranted
   here since it massively improves code readability.
   The INPUT macro relies on 'inputs' being a float array of size MYPAINT_BRUSH_INPUTS_COUNT,
   accessible in the scope where the macro is used.
*/
#define STATE(self, state_name) ((self)->states[MYPAINT_BRUSH_STATE_##state_name])
#define SETTING(self, setting_name) ((self)->settings_value[MYPAINT_BRUSH_SETTING_##setting_name])
#define BASEVAL(self, setting_name) (mypaint_mapping_get_base_value((self)->settings[MYPAINT_BRUSH_SETTING_##setting_name]))
#define INPUT(input_name) (inputs[MYPAINT_BRUSH_INPUT_##input_name])

void settings_base_values_have_changed (MyPaintBrush *self);


#include "glib/mypaint-brush.c"

void
brush_reset(MyPaintBrush *self)
{
    self->skip = 0;
    self->skip_last_x = 0;
    self->skip_last_y = 0;
    self->skipped_dtime = 0;
    // Clear states
    memset(self->states, 0, sizeof(self->states));
    // Set the flip state such that it will be at "1" for the first
    // dab, and then switch between -1 and 1 for the subsequent dabs.
    STATE(self, FLIP) = -1;
    // Clear smudge buckets
    if (self->smudge_buckets) {
      int min_index = self->min_bucket_used;
      if (min_index != -1) {
        int max_index = self->max_bucket_used;
        size_t num_bytes = (max_index - min_index) * sizeof(self->smudge_buckets[0]) * SMUDGE_BUCKET_SIZE;
        memset(self->smudge_buckets + min_index, 0, num_bytes);
        self->min_bucket_used = -1;
        self->max_bucket_used = -1;
      }
    }
}

/**
  * mypaint_brush_new:
  *
  * Create a new MyPaint brush engine instance.
  * Initial reference count is 1. Release references using mypaint_brush_unref()
  */
MyPaintBrush *
mypaint_brush_new(void)
{
  return mypaint_brush_new_with_buckets(0);
}

MyPaintBrush *
mypaint_brush_new_with_buckets(int num_smudge_buckets)
{
    MyPaintBrush *self = (MyPaintBrush *)malloc(sizeof(MyPaintBrush));

    if (!self) {
      return NULL;
    }

    if (num_smudge_buckets > 0) {
      float *bucket_array = malloc(num_smudge_buckets * SMUDGE_BUCKET_SIZE * sizeof(float));
      if (!bucket_array) {
        free(self);
        return NULL;
      }
      self->smudge_buckets = bucket_array;
      self->num_buckets = num_smudge_buckets;
      // Set up min/max to initialize (clear) the array in the call to brush_reset.
      self->min_bucket_used = 0;
      self->max_bucket_used = self->num_buckets - 1;
    } else {
      self->smudge_buckets = NULL;
      self->num_buckets = 0;
    }

    self->refcount = 1;
    for (int i = 0; i < MYPAINT_BRUSH_SETTINGS_COUNT; i++) {
      self->settings[i] = mypaint_mapping_new(MYPAINT_BRUSH_INPUTS_COUNT);
    }
    self->rng = rng_double_new(1000);
    self->random_input = 0;
    self->print_inputs = FALSE;

    brush_reset(self);

    mypaint_brush_new_stroke(self);
    settings_base_values_have_changed(self);

    self->reset_requested = TRUE;

    self->brush_json = json_object_new_object();

    return self;
}

void
brush_free(MyPaintBrush *self)
{
    for (int i = 0; i < MYPAINT_BRUSH_SETTINGS_COUNT; i++) {
        mypaint_mapping_free(self->settings[i]);
    }
    rng_double_free (self->rng);
    self->rng = NULL;

    if (self->brush_json) {
        json_object_put(self->brush_json);
    }

    free(self->smudge_buckets);
    free(self);
}

/**
  * mypaint_brush_unref: (skip)
  *
  * Decrease the reference count. Will be freed when it hits 0.
  */
void
mypaint_brush_unref(MyPaintBrush *self)
{
    self->refcount--;
    if (self->refcount == 0) {
        brush_free(self);
    }
}
/**
  * mypaint_brush_ref: (skip)
  *
  * Increase the reference count.
  */
void
mypaint_brush_ref(MyPaintBrush *self)
{
    self->refcount++;
}

/**
  * mypaint_brush_get_total_stroke_painting_time:
  *
  * Return the total amount of painting time for the current stroke.
  */
double
mypaint_brush_get_total_stroke_painting_time(MyPaintBrush *self)
{
    return self->stroke_total_painting_time;
}

/**
  * mypaint_brush_set_print_inputs:
  *
  * Enable/Disable printing of brush engine inputs on stderr. Intended for debugging only.
  */
void
mypaint_brush_set_print_inputs(MyPaintBrush *self, gboolean enabled)
{
    self->print_inputs = enabled;
}

/**
  * mypaint_brush_reset:
  *
  * Reset the current brush engine state.
  * Used when the next mypaint_brush_stroke_to() call is not related to the current state.
  * Note that the reset request is queued and changes in state will only happen on next stroke_to()
  */
void
mypaint_brush_reset(MyPaintBrush *self)
{
    self->reset_requested = TRUE;
}

/**
  * mypaint_brush_new_stroke:
  *
  * Start a new stroke.
  */
void
mypaint_brush_new_stroke(MyPaintBrush *self)
{
    self->stroke_current_idling_time = 0;
    self->stroke_total_painting_time = 0;
}

/**
  * mypaint_brush_set_base_value:
  *
  * Set the base value of a brush setting.
  */
void
mypaint_brush_set_base_value(MyPaintBrush *self, MyPaintBrushSetting id, float value)
{
    assert (id < MYPAINT_BRUSH_SETTINGS_COUNT);
    mypaint_mapping_set_base_value(self->settings[id], value);

    settings_base_values_have_changed (self);
}

/**
  * mypaint_brush_get_base_value:
  *
  * Get the base value of a brush setting.
  */
float
mypaint_brush_get_base_value(MyPaintBrush *self, MyPaintBrushSetting id)
{
    assert (id < MYPAINT_BRUSH_SETTINGS_COUNT);
    return mypaint_mapping_get_base_value(self->settings[id]);
}

/**
  * mypaint_brush_set_mapping_n:
  *
  * Set the number of points used for the dynamics mapping between a #MyPaintBrushInput and #MyPaintBrushSetting.
  */
void
mypaint_brush_set_mapping_n(MyPaintBrush *self, MyPaintBrushSetting id, MyPaintBrushInput input, int n)
{
    assert (id < MYPAINT_BRUSH_SETTINGS_COUNT);
    mypaint_mapping_set_n(self->settings[id], input, n);
}

/**
  * mypaint_brush_get_mapping_n:
  *
  * Get the number of points used for the dynamics mapping between a #MyPaintBrushInput and #MyPaintBrushSetting.
  */
int
mypaint_brush_get_mapping_n(MyPaintBrush *self, MyPaintBrushSetting id, MyPaintBrushInput input)
{
    return mypaint_mapping_get_n(self->settings[id], input);
}

/**
  * mypaint_brush_is_constant:
  *
  * Returns TRUE if the brush has no dynamics for the given #MyPaintBrushSetting
  */
gboolean
mypaint_brush_is_constant(MyPaintBrush *self, MyPaintBrushSetting id)
{
    assert (id < MYPAINT_BRUSH_SETTINGS_COUNT);
    return mypaint_mapping_is_constant(self->settings[id]);
}

/**
  * mypaint_brush_get_inputs_used_n:
  *
  * Returns how many inputs are used for the dynamics of a #MyPaintBrushSetting
  */
int
mypaint_brush_get_inputs_used_n(MyPaintBrush *self, MyPaintBrushSetting id)
{
    assert (id < MYPAINT_BRUSH_SETTINGS_COUNT);
    return mypaint_mapping_get_inputs_used_n(self->settings[id]);
}

/**
  * mypaint_brush_set_mapping_point:
  *
  * Set a X,Y point of a dynamics mapping.
  * The index must be within the number of points set using mypaint_brush_set_mapping_n()
  */
void
mypaint_brush_set_mapping_point(MyPaintBrush *self, MyPaintBrushSetting id, MyPaintBrushInput input, int index, float x, float y)
{
    assert (id < MYPAINT_BRUSH_SETTINGS_COUNT);
    mypaint_mapping_set_point(self->settings[id], input, index, x, y);
}

/**
 * mypaint_brush_get_mapping_point:
 * @x: (out): Location to return the X value
 * @y: (out): Location to return the Y value
 *
 * Get a X,Y point of a dynamics mapping.
 **/
void
mypaint_brush_get_mapping_point(MyPaintBrush *self, MyPaintBrushSetting id, MyPaintBrushInput input, int index, float *x, float *y)
{
    assert (id < MYPAINT_BRUSH_SETTINGS_COUNT);
    mypaint_mapping_get_point(self->settings[id], input, index, x, y);
}

/**
 * mypaint_brush_get_state:
 *
 * Get an internal brush engine state.
 * Normally used for debugging, but can be used to implement record & replay functionality.
 **/
float
mypaint_brush_get_state(MyPaintBrush *self, MyPaintBrushState i)
{
    assert (i < MYPAINT_BRUSH_STATES_COUNT);
    return self->states[i];
}

/**
 * mypaint_brush_set_state:
 *
 * Set an internal brush engine state.
 * Normally used for debugging, but can be used to implement record & replay functionality.
 **/
void
mypaint_brush_set_state(MyPaintBrush *self, MyPaintBrushState i, float value)
{
    assert (i < MYPAINT_BRUSH_STATES_COUNT);
    self->states[i] = value;
}

  // returns the fraction still left after t seconds
  float exp_decay (float T_const, float t)
  {
    // the argument might not make mathematical sense (whatever.)
    if (T_const <= 0.001) {
      return 0.0;
    }

    const float arg = -t / T_const;
    return expf(arg);
  }


  void settings_base_values_have_changed (MyPaintBrush *self)
  {
    // precalculate stuff that does not change dynamically

    // Precalculate how the physical speed will be mapped to the speed input value.
    // The formula for this mapping is:
    //
    // y = log(gamma+x)*m + q;
    //
    // x: the physical speed (pixels per basic dab radius)
    // y: the speed input that will be reported
    // gamma: parameter set by the user (small means a logarithmic mapping, big linear)
    // m, q: parameters to scale and translate the curve
    //
    // The code below calculates m and q given gamma and two hardcoded constraints.
    //
    for (int i = 0; i < 2; i++) {
      const float gamma = expf(i == 0 ? BASEVAL(self, SPEED1_GAMMA) : BASEVAL(self, SPEED2_GAMMA));

      const float fix1_x = 45.0;
      const float fix1_y = 0.5;
      const float fix2_x = 45.0;
      const float fix2_dy = 0.015;

      const float c1 = log(fix1_x + gamma);
      const float m = fix2_dy * (fix2_x + gamma);
      const float q = fix1_y - m * c1;

      self->speed_mapping_gamma[i] = gamma;
      self->speed_mapping_m[i] = m;
      self->speed_mapping_q[i] = q;
    }
  }

typedef struct {
  float x;
  float y;
} Offsets;

Offsets
directional_offsets(const MyPaintBrush* const self, const float base_radius, const int brush_flip)
{
    const float offset_mult = expf(SETTING(self, OFFSET_MULTIPLIER));
    // Sanity check - it is easy to reach infinite multipliers w. logarithmic parameters
    if (!isfinite(offset_mult)) {
        Offsets offs = {0.0f, 0.0f};
        return offs;
    }

    float dx = SETTING(self, OFFSET_X);
    float dy = SETTING(self, OFFSET_Y);

    //Anti_Art offsets tweaked by BrienD.  Adjusted with ANGLE_ADJ and OFFSET_MULTIPLIER
    const float offset_angle_adj = SETTING(self, OFFSET_ANGLE_ADJ);
    const float dir_angle_dy = STATE(self, DIRECTION_ANGLE_DY);
    const float dir_angle_dx = STATE(self, DIRECTION_ANGLE_DX);
    const float angle_deg = fmodf(DEGREES(atan2f(dir_angle_dy, dir_angle_dx)) - 90, 360);

    //offset to one side of direction
    const float offset_angle = SETTING(self, OFFSET_ANGLE);
    if (offset_angle) {
        const float dir_angle = RADIANS(angle_deg + offset_angle_adj);
        dx += cos(dir_angle) * offset_angle;
        dy += sin(dir_angle) * offset_angle;
    }

    //offset to one side of ascension angle
    const float view_rotation = STATE(self, VIEWROTATION);
    const float offset_angle_asc = SETTING(self, OFFSET_ANGLE_ASC);
    if (offset_angle_asc) {
        const float ascension = STATE(self, ASCENSION);
        const float asc_angle = RADIANS(ascension - view_rotation + offset_angle_adj);
        dx += cos(asc_angle) * offset_angle_asc;
        dy += sin(asc_angle) * offset_angle_asc;
      }

    //offset to one side of view orientation
    const float view_offset = SETTING(self, OFFSET_ANGLE_VIEW);
    if (view_offset) {
        const float view_angle = RADIANS(view_rotation + offset_angle_adj);
        dx += cos(-view_angle) * view_offset;
        dy += sin(-view_angle) * view_offset;
    }

    //offset mirrored to sides of direction
    const float offset_dir_mirror = MAX(0.0, SETTING(self, OFFSET_ANGLE_2));
    if (offset_dir_mirror) {
        const float dir_mirror_angle = RADIANS(angle_deg + offset_angle_adj * brush_flip);
        const float offset_factor = offset_dir_mirror * brush_flip;
        dx += cos(dir_mirror_angle) * offset_factor;
        dy += sin(dir_mirror_angle) * offset_factor;
    }

    //offset mirrored to sides of ascension angle
    const float offset_asc_mirror = MAX(0.0, SETTING(self, OFFSET_ANGLE_2_ASC));
    if (offset_asc_mirror) {
        const float ascension = STATE(self, ASCENSION);
        const float asc_angle = RADIANS(ascension - view_rotation + offset_angle_adj * brush_flip);
        const float offset_factor = brush_flip * offset_asc_mirror;
        dx += cos(asc_angle) * offset_factor;
        dy += sin(asc_angle) * offset_factor;
    }

    //offset mirrored to sides of view orientation
    const float offset_view_mirror = MAX(0.0, SETTING(self, OFFSET_ANGLE_2_VIEW));
    if (offset_view_mirror) {
        const float offset_factor = brush_flip * offset_view_mirror;
        const float offset_angle_rad = RADIANS(view_rotation + offset_angle_adj);
        dx += cos(-offset_angle_rad) * offset_factor;
        dy += sin(-offset_angle_rad) * offset_factor;
    }
    // Clamp the final offsets to avoid potential memory issues (extreme memory use from redraws)
    // Allow offsets up to the 1080 * 3 pixels. Unlikely to hamper anyone artistically.
    const float lim = 3240;
    const float base_mul = base_radius * offset_mult;
    Offsets offs = {CLAMP(dx * base_mul, -lim, lim),  CLAMP(dy * base_mul, -lim, lim)};
    return offs;
}

// Debugging: print brush inputs/states (not all of them)
void print_inputs(MyPaintBrush *self, float* inputs)
{
    printf(
        "press=% 4.3f, speed1=% 4.4f\tspeed2=% 4.4f",
        INPUT(PRESSURE),
        INPUT(SPEED1),
        INPUT(SPEED2)
        );
    printf(
        "\tstroke=% 4.3f\tcustom=% 4.3f",
        INPUT(STROKE),
        INPUT(CUSTOM)
        );
    printf(
        "\tviewzoom=% 4.3f\tviewrotation=% 4.3f",
        INPUT(VIEWZOOM),
        STATE(self, VIEWROTATION)
        );
    printf(
        "\tasc=% 4.3f\tdir=% 4.3f\tdec=% 4.3f\tdabang=% 4.3f",
        INPUT(TILT_ASCENSION),
        INPUT(DIRECTION),
        INPUT(TILT_DECLINATION),
        STATE(self, ACTUAL_ELLIPTICAL_DAB_ANGLE)
        );
    printf(
        "\txtilt=% 4.3f\tytilt=% 4.3fattack=% 4.3f",
        INPUT(TILT_DECLINATIONX),
        INPUT(TILT_DECLINATIONY),
        INPUT(ATTACK_ANGLE)
        );
    printf("\n");
}

  // This function runs a brush "simulation" step. Usually it is
  // called once or twice per dab. In theory the precision of the
  // "simulation" gets better when it is called more often. In
  // practice this only matters if there are some highly nonlinear
  // mappings in critical places or extremely few events per second.
  //
  // note: parameters are is dx/ddab, ..., dtime/ddab (dab is the number, 5.0 = 5th dab)
  void update_states_and_setting_values (MyPaintBrush *self, float step_ddab, float step_dx, float step_dy, float step_dpressure, float step_declination, float step_ascension, float step_dtime, float step_viewzoom, float step_viewrotation, float step_declinationx, float step_declinationy, float step_barrel_rotation)
  {
    if (step_dtime < 0.0) {
      printf("Time is running backwards!\n");
      step_dtime = 0.001;
    } else if (step_dtime == 0.0) {
      // FIXME: happens about every 10th start, workaround (against division by zero)
      step_dtime = 0.001;
    }

    STATE(self, X) += step_dx;
    STATE(self, Y) += step_dy;
    STATE(self, PRESSURE) += step_dpressure;

    STATE(self, DABS_PER_BASIC_RADIUS) = SETTING(self, DABS_PER_BASIC_RADIUS);
    STATE(self, DABS_PER_ACTUAL_RADIUS) = SETTING(self, DABS_PER_ACTUAL_RADIUS);
    STATE(self, DABS_PER_SECOND) = SETTING(self, DABS_PER_SECOND);

    STATE(self, DECLINATION) += step_declination;
    STATE(self, ASCENSION) += step_ascension;
    STATE(self, DECLINATIONX) += step_declinationx;
    STATE(self, DECLINATIONY) += step_declinationy;

    STATE(self, VIEWZOOM) = step_viewzoom;
    const float viewrotation = mod_arith(DEGREES(step_viewrotation) + 180.0, 360.0) - 180.0;
    STATE(self, VIEWROTATION) = viewrotation;

    { // Gridmap state update
        const float x = STATE(self, ACTUAL_X);
        const float y = STATE(self, ACTUAL_Y);
        const float scale = expf(SETTING(self, GRIDMAP_SCALE));
        const float scale_x = SETTING(self, GRIDMAP_SCALE_X);
        const float scale_y = SETTING(self, GRIDMAP_SCALE_Y);
        const float scaled_size = scale * GRID_SIZE;
        STATE(self, GRIDMAP_X) = mod_arith(fabsf(x * scale_x), scaled_size) / scaled_size * GRID_SIZE;
        STATE(self, GRIDMAP_Y) = mod_arith(fabsf(y * scale_y), scaled_size) / scaled_size * GRID_SIZE;
        if (x < 0.0) {
            STATE(self, GRIDMAP_X) = GRID_SIZE - STATE(self, GRIDMAP_X);
        }
        if (y < 0.0) {
            STATE(self, GRIDMAP_Y) = GRID_SIZE - STATE(self, GRIDMAP_Y);
        }
    }

    float base_radius = expf(BASEVAL(self, RADIUS_LOGARITHMIC));
    STATE(self, BARREL_ROTATION) += step_barrel_rotation;

    // FIXME: does happen (interpolation problem?)
    if (STATE(self, PRESSURE) <= 0.0) STATE(self, PRESSURE) = 0.0;
    const float pressure = STATE(self, PRESSURE);

    { // start / end stroke (for "stroke" input only)
      const float lim = 0.0001;
      const float threshold = BASEVAL(self, STROKE_THRESHOLD);
      const float started = STATE(self, STROKE_STARTED);
      if (!started && pressure > threshold + lim) {
        // start new stroke
        STATE(self, STROKE_STARTED) = 1;
        STATE(self, STROKE) = 0.0;
      } else if (started && pressure <= threshold * 0.9 + lim) {
        // end stroke
        STATE(self, STROKE_STARTED) = 0;
      }
    }

    // now follows input handling

    //adjust speed with viewzoom
    const float norm_dx = step_dx / step_dtime * STATE(self, VIEWZOOM);
    const float norm_dy = step_dy / step_dtime * STATE(self, VIEWZOOM);

    const float norm_speed = hypotf(norm_dx, norm_dy);
    //norm_dist should relate to brush size, whereas norm_speed should not
    const float norm_dist = hypotf(step_dx / step_dtime / base_radius, step_dy / step_dtime / base_radius) * step_dtime;

    float inputs[MYPAINT_BRUSH_INPUTS_COUNT];

    INPUT(PRESSURE) = pressure * expf(BASEVAL(self, PRESSURE_GAIN_LOG));

    const float m0 = self->speed_mapping_m[0];
    const float q0 = self->speed_mapping_q[0];
    const float m1 = self->speed_mapping_m[1];
    const float q1 = self->speed_mapping_q[1];
    INPUT(SPEED1) = log(self->speed_mapping_gamma[0] + STATE(self, NORM_SPEED1_SLOW)) * m0 + q0;
    INPUT(SPEED2) = log(self->speed_mapping_gamma[1] + STATE(self, NORM_SPEED2_SLOW)) * m1 + q1;

    INPUT(RANDOM) = self->random_input;
    INPUT(STROKE) = MIN(STATE(self, STROKE), 1.0);

    //correct direction for varying view rotation
    const float dir_angle = atan2f(STATE(self, DIRECTION_DY), STATE(self, DIRECTION_DX));
    INPUT(DIRECTION) = mod_arith(DEGREES(dir_angle) + viewrotation + 180.0, 180.0);
    const float dir_angle_360 = atan2f(STATE(self, DIRECTION_ANGLE_DY), STATE(self, DIRECTION_ANGLE_DX));
    INPUT(DIRECTION_ANGLE) = fmodf(DEGREES(dir_angle_360) + viewrotation + 360.0, 360.0) ;
    INPUT(TILT_DECLINATION) = STATE(self, DECLINATION);
    //correct ascension for varying view rotation, use custom mod
    INPUT(TILT_ASCENSION) = mod_arith(STATE(self, ASCENSION) + viewrotation + 180.0, 360.0) - 180.0;
    INPUT(VIEWZOOM) = BASEVAL(self, RADIUS_LOGARITHMIC) - logf(base_radius / STATE(self, VIEWZOOM));
    INPUT(ATTACK_ANGLE) = smallest_angular_difference(STATE(self, ASCENSION), mod_arith(DEGREES(dir_angle_360) + 90, 360));
    INPUT(BRUSH_RADIUS) = BASEVAL(self, RADIUS_LOGARITHMIC);

    INPUT(GRIDMAP_X) = CLAMP(STATE(self, GRIDMAP_X), 0.0, GRID_SIZE);
    INPUT(GRIDMAP_Y) = CLAMP(STATE(self, GRIDMAP_Y), 0.0, GRID_SIZE);

    INPUT(TILT_DECLINATIONX) = STATE(self, DECLINATIONX);
    INPUT(TILT_DECLINATIONY) = STATE(self, DECLINATIONY);

    INPUT(CUSTOM) = STATE(self, CUSTOM_INPUT);
    INPUT(BARREL_ROTATION) = mod_arith(STATE(self, BARREL_ROTATION), 360);

    if (self->print_inputs) {
        print_inputs(self, inputs);
    }

    for (int i = 0; i < MYPAINT_BRUSH_SETTINGS_COUNT; i++) {
      self->settings_value[i] = mypaint_mapping_calculate(self->settings[i], (inputs));
    }

    {
      const float fac = 1.0 - exp_decay(SETTING(self, SLOW_TRACKING_PER_DAB), step_ddab);
      STATE(self, ACTUAL_X) += (STATE(self, X) - STATE(self, ACTUAL_X)) * fac;
      STATE(self, ACTUAL_Y) += (STATE(self, Y) - STATE(self, ACTUAL_Y)) * fac;
    }

    { // slow speed
      const float fac1 = 1.0 - exp_decay(SETTING(self, SPEED1_SLOWNESS), step_dtime);
      STATE(self, NORM_SPEED1_SLOW) += (norm_speed - STATE(self, NORM_SPEED1_SLOW)) * fac1;
      const float fac2 = 1.0 - exp_decay (SETTING(self, SPEED2_SLOWNESS), step_dtime);
      STATE(self, NORM_SPEED2_SLOW) += (norm_speed - STATE(self, NORM_SPEED2_SLOW)) * fac2;
    }

    { // slow speed, but as vector this time
      float time_constant = expf(SETTING(self, OFFSET_BY_SPEED_SLOWNESS)*0.01)-1.0;
      // Workaround for a bug that happens mainly on Windows, causing
      // individual dabs to be placed far far away. Using the speed
      // with zero filtering is just asking for trouble anyway.
      if (time_constant < 0.002) time_constant = 0.002;
      const float fac = 1.0 - exp_decay (time_constant, step_dtime);
      STATE(self, NORM_DX_SLOW) += (norm_dx - STATE(self, NORM_DX_SLOW)) * fac;
      STATE(self, NORM_DY_SLOW) += (norm_dy - STATE(self, NORM_DY_SLOW)) * fac;
    }

    { // orientation (similar lowpass filter as above, but use dabtime instead of wallclock time)
      // adjust speed with viewzoom
      float dx = step_dx * STATE(self, VIEWZOOM);
      float dy = step_dy * STATE(self, VIEWZOOM);

      const float step_in_dabtime = hypotf(dx, dy);
      const float fac = 1.0 - exp_decay(expf(SETTING(self, DIRECTION_FILTER) * 0.5) - 1.0, step_in_dabtime);

      const float dx_old = STATE(self, DIRECTION_DX);
      const float dy_old = STATE(self, DIRECTION_DY);

      // 360 Direction
      STATE(self, DIRECTION_ANGLE_DX) += (dx - STATE(self, DIRECTION_ANGLE_DX)) * fac;
      STATE(self, DIRECTION_ANGLE_DY) += (dy - STATE(self, DIRECTION_ANGLE_DY)) * fac;

      // use the opposite speed vector if it is closer (we don't care about 180 degree turns)
      if (SQR(dx_old-dx) + SQR(dy_old-dy) > SQR(dx_old-(-dx)) + SQR(dy_old-(-dy))) {
        dx = -dx;
        dy = -dy;
      }
      STATE(self, DIRECTION_DX) += (dx - STATE(self, DIRECTION_DX)) * fac;
      STATE(self, DIRECTION_DY) += (dy - STATE(self, DIRECTION_DY)) * fac;
    }

    { // custom input
      const float fac = 1.0 - exp_decay (SETTING(self, CUSTOM_INPUT_SLOWNESS), 0.1);
      STATE(self, CUSTOM_INPUT) += (SETTING(self, CUSTOM_INPUT) - STATE(self, CUSTOM_INPUT)) * fac;
    }

    { // stroke length
      const float frequency = expf(-SETTING(self, STROKE_DURATION_LOGARITHMIC));
      const float stroke = MAX(0, STATE(self, STROKE) + norm_dist * frequency);
      const float wrap = 1.0 + MAX(0, SETTING(self, STROKE_HOLDTIME));
      // If the hold time is above 9.9, it is considered infinite, and if the stroke value has reached
      // that threshold it is no longer updated (until the stroke is reset, or the hold time changes).
      if (stroke >= wrap && wrap > 9.9 + 1.0) {
        STATE(self, STROKE) = 1.0;
      } else if (stroke >= wrap) {
        STATE(self, STROKE) = fmodf(stroke, wrap);
      } else {
        STATE(self, STROKE) = stroke;
      }
    }

    // calculate final radius
    const float radius_log = SETTING(self, RADIUS_LOGARITHMIC);
    STATE(self, ACTUAL_RADIUS) = expf(radius_log);
    if (STATE(self, ACTUAL_RADIUS) < ACTUAL_RADIUS_MIN) STATE(self, ACTUAL_RADIUS) = ACTUAL_RADIUS_MIN;
    if (STATE(self, ACTUAL_RADIUS) > ACTUAL_RADIUS_MAX) STATE(self, ACTUAL_RADIUS) = ACTUAL_RADIUS_MAX;

    // aspect ratio (needs to be calculated here because it can affect the dab spacing)
    STATE(self, ACTUAL_ELLIPTICAL_DAB_RATIO) = SETTING(self, ELLIPTICAL_DAB_RATIO);
    // correct dab angle for view rotation
    STATE(self, ACTUAL_ELLIPTICAL_DAB_ANGLE) = mod_arith(SETTING(self, ELLIPTICAL_DAB_ANGLE) - viewrotation + 180.0, 180.0) - 180.0;
  }

  float *fetch_smudge_bucket(MyPaintBrush *self) {
    if (!self->smudge_buckets || !self->num_buckets) {
      return &STATE(self, SMUDGE_RA);
    }
    const int bucket_index = CLAMP(roundf(SETTING(self, SMUDGE_BUCKET)), 0, self->num_buckets - 1);
    if (self->min_bucket_used == -1 || self->min_bucket_used > bucket_index) {
      self->min_bucket_used = bucket_index;
    }
    if (self->max_bucket_used < bucket_index) {
      self->max_bucket_used = bucket_index;
    }
    return &self->smudge_buckets[bucket_index * SMUDGE_BUCKET_SIZE];
  }

  gboolean
  update_smudge_color(
      const MyPaintBrush* self, MyPaintSurface* surface, float* const smudge_bucket, const float smudge_length, int px,
      int py, const float radius, const float legacy_smudge, const float paint_factor, gboolean legacy)
  {

      // Value between 0.01 and 1.0 that determines how often the canvas should be resampled
      float update_factor = MAX(0.01, smudge_length);


      // Calling get_color() is almost as expensive as rendering a
      // dab. Because of this we use the previous value if it is not
      // expected to hurt quality too much. We call it at most every
      // second dab.
      float r, g, b, a;
      const float smudge_length_log = SETTING(self, SMUDGE_LENGTH_LOG);

      const float recentness = smudge_bucket[PREV_COL_RECENTNESS] * update_factor;
      smudge_bucket[PREV_COL_RECENTNESS] = recentness;

      const float margin = 0.0000000000000001;
      if (recentness < MIN(1.0, powf(0.5 * update_factor, smudge_length_log) + margin)) {
          if (recentness == 0.0) {
              // first initialization of smudge color (initiate with color sampled from canvas)
              update_factor = 0.0;
          }
          smudge_bucket[PREV_COL_RECENTNESS] = 1.0;

          const float radius_log = SETTING(self, SMUDGE_RADIUS_LOG);
          const float smudge_radius = CLAMP(radius * expf(radius_log), ACTUAL_RADIUS_MIN, ACTUAL_RADIUS_MAX);

          // Sample colors on the canvas, using a negative value for the paint factor
          // means that the old sampling method is used, instead of weighted spectral.
          if (legacy) {
              mypaint_surface_get_color(surface, px, py, smudge_radius, &r, &g, &b, &a);
          } else {
              mypaint_surface2_get_color(
                  (MyPaintSurface2*)surface, px, py, smudge_radius, &r, &g, &b, &a, legacy_smudge ? -1.0 : paint_factor);
          }

          // don't draw unless the picked-up alpha is above a certain level
          // this is sort of like lock_alpha but for smudge
          // negative values reverse this idea
          const float smudge_op_lim = SETTING(self, SMUDGE_TRANSPARENCY);
          if ((smudge_op_lim > 0.0 && a < smudge_op_lim) || (smudge_op_lim < 0.0 && a > -smudge_op_lim)) {
              return TRUE; // signals the caller to return early
          }
          smudge_bucket[PREV_COL_R] = r;
          smudge_bucket[PREV_COL_G] = g;
          smudge_bucket[PREV_COL_B] = b;
          smudge_bucket[PREV_COL_A] = a;
      } else {
          r = smudge_bucket[PREV_COL_R];
          g = smudge_bucket[PREV_COL_G];
          b = smudge_bucket[PREV_COL_B];
          a = smudge_bucket[PREV_COL_A];
      }

      if (legacy_smudge) {
          const float fac_old = update_factor;
          const float fac_new = (1.0 - update_factor) * a;
          smudge_bucket[SMUDGE_R] = fac_old * smudge_bucket[SMUDGE_R] + fac_new * r;
          smudge_bucket[SMUDGE_G] = fac_old * smudge_bucket[SMUDGE_G] + fac_new * g;
          smudge_bucket[SMUDGE_B] = fac_old * smudge_bucket[SMUDGE_B] + fac_new * b;
          smudge_bucket[SMUDGE_A] = CLAMP((fac_old * smudge_bucket[SMUDGE_A] + fac_new), 0.0, 1.0);
      } else if (a > WGM_EPSILON * 10) {
          float prev_smudge_color[4] = {smudge_bucket[SMUDGE_R], smudge_bucket[SMUDGE_G], smudge_bucket[SMUDGE_B],
                                        smudge_bucket[SMUDGE_A]};
          float sampled_color[4] = {r, g, b, a};

          float* smudge_new = mix_colors(prev_smudge_color, sampled_color, update_factor, paint_factor);
          smudge_bucket[SMUDGE_R] = smudge_new[SMUDGE_R];
          smudge_bucket[SMUDGE_G] = smudge_new[SMUDGE_G];
          smudge_bucket[SMUDGE_B] = smudge_new[SMUDGE_B];
          smudge_bucket[SMUDGE_A] = smudge_new[SMUDGE_A];
      } else {
          // To avoid color noise from spectral mixing with a low alpha,
          // we'll just decrease the alpha of the existing smudge color.
          smudge_bucket[SMUDGE_A] = (smudge_bucket[SMUDGE_A] + a) / 2;
      }
      return FALSE; // signals the caller to not return early (the default)
  }

  float
  apply_smudge(
      const float* const smudge_bucket, const float smudge_value, const gboolean legacy_smudge,
      const float paint_factor, float* color_r, float* color_g, float* color_b)
  {
      float smudge_factor = MIN(1.0, smudge_value);

      // If the smudge color somewhat transparent, then the resulting
      // dab will do erasing towards that transparency level.
      // see also ../doc/smudge_math.png
      const float eraser_target_alpha =
          CLAMP((1.0 - smudge_factor) + smudge_factor * smudge_bucket[SMUDGE_A], 0.0, 1.0);

      if (eraser_target_alpha > 0) {
          if (legacy_smudge) {
              const float col_factor = 1.0 - smudge_factor;
              *color_r = (smudge_factor * smudge_bucket[SMUDGE_R] + col_factor * *color_r) / eraser_target_alpha;
              *color_g = (smudge_factor * smudge_bucket[SMUDGE_G] + col_factor * *color_g) / eraser_target_alpha;
              *color_b = (smudge_factor * smudge_bucket[SMUDGE_B] + col_factor * *color_b) / eraser_target_alpha;
          } else {
              float smudge_color[4] = {smudge_bucket[SMUDGE_R], smudge_bucket[SMUDGE_G], smudge_bucket[SMUDGE_B],
                                       smudge_bucket[SMUDGE_A]};
              float brush_color[4] = {*color_r, *color_g, *color_b, 1.0};
              float* color_new = mix_colors(smudge_color, brush_color, smudge_factor, paint_factor);
              *color_r = color_new[SMUDGE_R];
              *color_g = color_new[SMUDGE_G];
              *color_b = color_new[SMUDGE_B];
          }
      } else {
          // we are only erasing; the color does (should) not matter
          // we set the color to a clear red to see bugs easier.
          *color_r = 1.0;
          *color_g = 0.0;
          *color_b = 0.0;
      }
      return eraser_target_alpha;
  }

  // Called only from stroke_to(). Calculate everything needed to
  // draw the dab, then let the surface do the actual drawing.
  //
  // This is only gets called right after update_states_and_setting_values().
  // Returns TRUE if the surface was modified.
gboolean prepare_and_draw_dab (MyPaintBrush *self, MyPaintSurface * surface, gboolean legacy, gboolean linear)
  {
    const float opaque_fac = SETTING(self, OPAQUE_MULTIPLY);
    // ensure we don't get a positive result with two negative opaque values
    float opaque = MAX(0.0, SETTING(self, OPAQUE));
    opaque = CLAMP(opaque * opaque_fac, 0.0, 1.0);

    const float opaque_linearize = BASEVAL(self, OPAQUE_LINEARIZE);

    if (opaque_linearize) {
      // OPTIMIZE: no need to recalculate this for each dab
      float alpha, beta, alpha_dab, beta_dab;
      float dabs_per_pixel;
      // dabs_per_pixel is just estimated roughly, I didn't think hard
      // about the case when the radius changes during the stroke
      if (legacy) {
        dabs_per_pixel = BASEVAL(self, DABS_PER_ACTUAL_RADIUS) + BASEVAL(self, DABS_PER_BASIC_RADIUS);
      } else {
        dabs_per_pixel = STATE(self, DABS_PER_ACTUAL_RADIUS) + STATE(self, DABS_PER_BASIC_RADIUS);
      }
      dabs_per_pixel *= 2.0;

      // the correction is probably not wanted if the dabs don't overlap
      if (dabs_per_pixel < 1.0) dabs_per_pixel = 1.0;

      // interpret the user-setting smoothly
      dabs_per_pixel = 1.0 + opaque_linearize * (dabs_per_pixel-1.0);

      // see doc/brushdab_saturation.png
      //      beta = beta_dab^dabs_per_pixel
      // <==> beta_dab = beta^(1/dabs_per_pixel)
      alpha = opaque;
      beta = 1.0-alpha;
      beta_dab = powf(beta, 1.0/dabs_per_pixel);
      alpha_dab = 1.0-beta_dab;
      opaque = alpha_dab;
    }

    float x = STATE(self, ACTUAL_X);
    float y = STATE(self, ACTUAL_Y);

    float base_radius = expf(BASEVAL(self, RADIUS_LOGARITHMIC));

    // Directional offsets
    Offsets offs = directional_offsets(self, base_radius, (int)STATE(self, FLIP));
    x += offs.x;
    y += offs.y;

    const float view_zoom = STATE(self, VIEWZOOM);
    const float offset_by_speed = SETTING(self, OFFSET_BY_SPEED);
    if (offset_by_speed) {
        x += STATE(self, NORM_DX_SLOW) * offset_by_speed * 0.1 / view_zoom;
        y += STATE(self, NORM_DY_SLOW) * offset_by_speed * 0.1 / view_zoom;
    }

    const float offset_by_random = SETTING(self, OFFSET_BY_RANDOM);
    if (offset_by_random) {
        float amp = MAX(0.0, offset_by_random);
        x += rand_gauss (self->rng) * amp * base_radius;
        y += rand_gauss (self->rng) * amp * base_radius;
    }

    float radius = STATE(self, ACTUAL_RADIUS);
    const float radius_by_random = SETTING(self, RADIUS_BY_RANDOM);
    if (radius_by_random) {
        const float noise = rand_gauss(self->rng) * radius_by_random;
        float radius_log = SETTING(self, RADIUS_LOGARITHMIC) + noise;
        radius = CLAMP(expf(radius_log), ACTUAL_RADIUS_MIN, ACTUAL_RADIUS_MAX);
        float alpha_correction = SQR(STATE(self, ACTUAL_RADIUS) / radius);
        if (alpha_correction <= 1.0) {
            opaque *= alpha_correction;
        }
    }

    const float paint_factor = legacy ? 0.0 : SETTING(self, PAINT_MODE);
    const gboolean paint_setting_constant =
        legacy ? TRUE : mypaint_mapping_is_constant(self->settings[MYPAINT_BRUSH_SETTING_PAINT_MODE]);
    const gboolean legacy_smudge = paint_factor <= 0.0 && paint_setting_constant;

    //convert to RGB here instead of later
    // color part
    float color_h = BASEVAL(self, COLOR_H);
    float color_s = BASEVAL(self, COLOR_S);
    float color_v = BASEVAL(self, COLOR_V);
    hsv_to_rgb_float (&color_h, &color_s, &color_v);

    // update smudge color
    const float smudge_length = SETTING(self, SMUDGE_LENGTH);
    if (smudge_length < 1.0 && // default smudge length is 0.5, so the smudge factor is checked as well
        (SETTING(self, SMUDGE) != 0.0 || !mypaint_mapping_is_constant(self->settings[MYPAINT_BRUSH_SETTING_SMUDGE]))) {
        float* const bucket = fetch_smudge_bucket(self);
        gboolean return_early = update_smudge_color(
          self, surface, bucket, smudge_length, ROUND(x), ROUND(y), radius, legacy_smudge, paint_factor, legacy);
        if (return_early) {
          return FALSE;
        }
    }

    float eraser_target_alpha = 1.0;
    const float smudge_value = SETTING(self, SMUDGE);

    if (smudge_value > 0.0) {
      float* const bucket = fetch_smudge_bucket(self);
      eraser_target_alpha =
          apply_smudge(bucket, smudge_value, legacy_smudge, paint_factor, &color_h, &color_s, &color_v);
    }

    // eraser
    if (SETTING(self, ERASER)) {
      eraser_target_alpha *= (1.0-SETTING(self, ERASER));
    }

    /*
      If the colors are stored as linear sRGB, they need to be transformed to
      the form compatible with the hsv/hsl conversion functions, and then
      transformed back after the adjustments.
    */
    // Check whether the dynamics are used before the conditionals, to skip needless transformations.
    gboolean using_hsv_dynamics =
        SETTING(self, CHANGE_COLOR_H) || SETTING(self, CHANGE_COLOR_HSV_S) || SETTING(self, CHANGE_COLOR_V);
    gboolean using_hsl_dynamics = SETTING(self, CHANGE_COLOR_L) || SETTING(self, CHANGE_COLOR_HSL_S);
    gboolean using_color_dynamics = using_hsv_dynamics || using_hsl_dynamics;

    // delinearize
    if (linear && using_color_dynamics) {
      color_h = powf(color_h, 1 / 2.2);
      color_s = powf(color_s, 1 / 2.2);
      color_v = powf(color_v, 1 / 2.2);
    }

    // HSV color change
    if (using_hsv_dynamics) {
        rgb_to_hsv_float(&color_h, &color_s, &color_v);
        color_h += SETTING(self, CHANGE_COLOR_H);
        color_s += color_s * color_v * SETTING(self, CHANGE_COLOR_HSV_S);
        color_v += SETTING(self, CHANGE_COLOR_V);
        hsv_to_rgb_float(&color_h, &color_s, &color_v);
    }

    // HSL color change
    if (using_hsl_dynamics) {
      // (calculating way too much here, can be optimized if necessary)
      // this function will CLAMP the inputs
      rgb_to_hsl_float (&color_h, &color_s, &color_v);
      color_v += SETTING(self, CHANGE_COLOR_L);
      color_s += color_s * MIN(fabsf(1.0f - color_v), fabsf(color_v)) * 2.0f
        * SETTING(self, CHANGE_COLOR_HSL_S);
      hsl_to_rgb_float (&color_h, &color_s, &color_v);
    }

    // linearize
    if (linear && using_color_dynamics) {
      color_h = powf(color_h, 2.2);
      color_s = powf(color_s, 2.2);
      color_v = powf(color_v, 2.2);
    }

    float hardness = CLAMP(SETTING(self, HARDNESS), 0.0f, 1.0f);

    // anti-aliasing attempt (works surprisingly well for ink brushes)
    float current_fadeout_in_pixels = radius * (1.0 - hardness);
    float min_fadeout_in_pixels = SETTING(self, ANTI_ALIASING);
    if (current_fadeout_in_pixels < min_fadeout_in_pixels) {
      // need to soften the brush (decrease hardness), but keep optical radius
      // so we tune both radius and hardness, to get the desired fadeout_in_pixels
      float current_optical_radius = radius - (1.0 - hardness)*radius/2.0;

      // Equation 1: (new fadeout must be equal to min_fadeout)
      //   min_fadeout_in_pixels = radius_new*(1.0 - hardness_new)
      // Equation 2: (optical radius must remain unchanged)
      //   current_optical_radius = radius_new - (1.0-hardness_new)*radius_new/2.0
      //
      // Solved Equation 1 for hardness_new, using Equation 2: (thanks to mathomatic)
      float hardness_new = ((current_optical_radius - (min_fadeout_in_pixels/2.0))/(current_optical_radius + (min_fadeout_in_pixels/2.0)));
      // Using Equation 1:
      float radius_new = (min_fadeout_in_pixels/(1.0 - hardness_new));

      hardness = hardness_new;
      radius = radius_new;
    }

    // snap to pixel
    float snapToPixel = SETTING(self, SNAP_TO_PIXEL);
    if (snapToPixel > 0.0)
    {
      // linear interpolation between non-snapped and snapped
      const float snapped_x = floor(x) + 0.5;
      const float snapped_y = floor(y) + 0.5;
      x = x + (snapped_x - x) * snapToPixel;
      y = y + (snapped_y - y) * snapToPixel;

      float snapped_radius = roundf(radius * 2.0) / 2.0;
      if (snapped_radius < 0.5)
        snapped_radius = 0.5;

      if (snapToPixel > 0.9999 )
      {
        snapped_radius -= 0.0001; // this fixes precision issues where
                                  // neighbor pixels could be wrongly painted
      }

      radius = radius + (snapped_radius - radius) * snapToPixel;
    }

    const float dab_ratio = STATE(self, ACTUAL_ELLIPTICAL_DAB_RATIO);
    const float dab_angle = STATE(self, ACTUAL_ELLIPTICAL_DAB_ANGLE);
    const float lock_alpha = SETTING(self, LOCK_ALPHA);
    const float colorize = SETTING(self, COLORIZE);

    if (legacy) {
    return mypaint_surface_draw_dab (
        surface, x, y, radius, color_h, color_s, color_v, opaque, hardness, eraser_target_alpha,
        dab_ratio, dab_angle, lock_alpha, colorize);
    }
    else {
    const float posterize = SETTING(self, POSTERIZE);
    const float posterize_num = SETTING(self, POSTERIZE_NUM);
    return mypaint_surface2_draw_dab (
        (MyPaintSurface2*)surface, x, y, radius, color_h, color_s, color_v, opaque, hardness, eraser_target_alpha,
        dab_ratio, dab_angle, lock_alpha, colorize, posterize, posterize_num, paint_factor);
    }
  }

  static inline float
  legacy_dab_count(float dist, float base_radius, float dt, MyPaintBrush* self)
  {
      const float num_from_actual_radius = dist / STATE(self, ACTUAL_RADIUS) * BASEVAL(self, DABS_PER_ACTUAL_RADIUS);
      const float num_from_basic_radius = dist / base_radius * BASEVAL(self, DABS_PER_BASIC_RADIUS);
      const float num_from_seconds = dt * BASEVAL(self, DABS_PER_SECOND);
      return num_from_actual_radius + num_from_basic_radius + num_from_seconds;
  }

  static inline float
  state_based_dab_count(float dist, float base_radius, float dt, MyPaintBrush* self)
  {

      const float dpar_state = STATE(self, DABS_PER_ACTUAL_RADIUS);
      const float num_by_actual_radius = dist / STATE(self, ACTUAL_RADIUS) *
        (dpar_state && !isnan(dpar_state) ? dpar_state : BASEVAL(self, DABS_PER_ACTUAL_RADIUS));

      const float dpbr_state = STATE(self, DABS_PER_BASIC_RADIUS);
      const float num_by_basic_radius =
        dist / base_radius * (dpbr_state && !isnan(dpbr_state) ? dpbr_state : BASEVAL(self, DABS_PER_BASIC_RADIUS));

      const float dps_state = STATE(self, DABS_PER_SECOND);
      const float num_by_time_delta = dt * (!isnan(dps_state) ? dps_state : BASEVAL(self, DABS_PER_SECOND));
      return num_by_actual_radius + num_by_basic_radius + num_by_time_delta;
  }

  // How many dabs will be drawn between the current and the next (x, y, +dt) position?
  float count_dabs_to (MyPaintBrush *self, float x, float y, float dt, gboolean legacy)
  {
    const float base_radius_log = BASEVAL(self, RADIUS_LOGARITHMIC);
    const float base_radius = CLAMP(expf(base_radius_log), ACTUAL_RADIUS_MIN, ACTUAL_RADIUS_MAX);

    if (STATE(self, ACTUAL_RADIUS) == 0.0) {
      STATE(self, ACTUAL_RADIUS) = base_radius;
    }

    const float dx = x - STATE(self, X);
    const float dy = y - STATE(self, Y);

    float dist;

    if (STATE(self, ACTUAL_ELLIPTICAL_DAB_RATIO) > 1.0) {
      // code duplication, see calculate_rr in mypaint-tiled-surface.c
      float angle_rad = RADIANS(STATE(self, ACTUAL_ELLIPTICAL_DAB_ANGLE));
      float cs = cos(angle_rad);
      float sn = sin(angle_rad);
      float yyr = (dy * cs - dx * sn) * STATE(self, ACTUAL_ELLIPTICAL_DAB_RATIO);
      float xxr = dy * sn + dx * cs;
      dist = sqrt(yyr * yyr + xxr * xxr);
    } else {
      dist = hypotf(dx, dy);
    }

    if (legacy) {
      return legacy_dab_count(dist, base_radius, dt, self);
    } else {
      return state_based_dab_count(dist, base_radius, dt, self);
    }
  }

int
mypaint_brush_stroke_to_internal(
    MyPaintBrush* self, MyPaintSurface* surface, float x, float y, float pressure, float xtilt, float ytilt,
    double dtime, float viewzoom, float viewrotation, float barrel_rotation, gboolean legacy, gboolean linear);


/**
 * mypaint_brush_stroke_to:
 * @dtime: Time since last motion event, in seconds.
 *
 * Should be called once for each motion event.
 *
 * Returns: non-0 if the stroke is finished or empty, else 0.
*/
int
mypaint_brush_stroke_to(
    MyPaintBrush* self, MyPaintSurface* surface, float x, float y, float pressure, float xtilt, float ytilt,
    double dtime)
{
  const float viewzoom = 1.0;
  const float viewrotation = 0.0;
  const float barrel_rotation = 0.0;
  return mypaint_brush_stroke_to_internal(
      self, surface, x, y, pressure, xtilt, ytilt, dtime, viewzoom, viewrotation, barrel_rotation, TRUE, FALSE);
}

/**
 * mypaint_brush_stroke_to_2:
 * @dtime: Time since last motion event, in seconds.
 * @viewzoom: Canvas zoom; 1.0 = 100% zoom. Zoom value v *must* be in range:
 * 0.0 < v < FLOAT_MAX (reasonable max is probably always below 100). Zoom value
 * cannot be 0!
 *
 * Should be called once for each motion event.
 *
 * Returns: non-0 if the stroke is finished or empty, else 0.
 */
int
mypaint_brush_stroke_to_2(
    MyPaintBrush* self, MyPaintSurface2* surface, float x, float y, float pressure, float xtilt, float ytilt,
    double dtime, float viewzoom, float viewrotation, float barrel_rotation)
{
    return mypaint_brush_stroke_to_internal(
      self, mypaint_surface2_to_surface(surface), x, y, pressure, xtilt, ytilt, dtime, viewzoom, viewrotation, barrel_rotation, FALSE, FALSE);
}

/**
 * mypaint_brush_stroke_to_2_linearsRGB:
 *
 * Same as mypaint_brush_stroke_to_2, but color _dynamics_ operate in linear sRGB,
 * i.e. settings that change the hue/value/lightness/saturation of the brush color.
 */
int
mypaint_brush_stroke_to_2_linearsRGB(
    MyPaintBrush* self, MyPaintSurface2* surface, float x, float y, float pressure, float xtilt, float ytilt,
    double dtime, float viewzoom, float viewrotation, float barrel_rotation)
{
    return mypaint_brush_stroke_to_internal(
      self, mypaint_surface2_to_surface(surface), x, y, pressure, xtilt, ytilt, dtime, viewzoom, viewrotation, barrel_rotation, FALSE, TRUE);
}

int
mypaint_brush_stroke_to_internal(
    MyPaintBrush* self, MyPaintSurface* surface, float x, float y, float pressure, float xtilt, float ytilt,
    double dtime, float viewzoom, float viewrotation, float barrel_rotation, gboolean legacy, gboolean linear)
{
    const float max_dtime = 5;

    float tilt_ascension = 0.0;
    float tilt_declination = 90.0;
    float tilt_declinationx = 90.0;
    float tilt_declinationy = 90.0;
    if (xtilt != 0 || ytilt != 0) {
      // shield us from insane tilt input
      xtilt = CLAMP(xtilt, -1.0, 1.0);
      ytilt = CLAMP(ytilt, -1.0, 1.0);
      assert(isfinite(xtilt) && isfinite(ytilt));

      tilt_ascension = DEGREES(atan2(-xtilt, ytilt));
      const float rad = hypot(xtilt, ytilt);
      tilt_declination = 90-(rad*60);
      tilt_declinationx = (xtilt * 60);
      tilt_declinationy = (ytilt * 60);

      assert(isfinite(tilt_ascension));
      assert(isfinite(tilt_declination));
      assert(isfinite(tilt_declinationx));
      assert(isfinite(tilt_declinationy));
    }

    if (pressure <= 0.0) pressure = 0.0;
    if (!isfinite(x) || !isfinite(y) ||
        (x > 1e10 || y > 1e10 || x < -1e10 || y < -1e10)) {
      // workaround attempt for https://gna.org/bugs/?14372
      printf("Warning: ignoring brush::stroke_to with insane inputs (x = %f, y = %f)\n", (double)x, (double)y);
      x = 0.0;
      y = 0.0;
      pressure = 0.0;
      viewzoom = 0.0;
      viewrotation = 0.0;
      barrel_rotation = 0.0;
    }
    // the assertion below is better than out-of-memory later at save time
    assert(x < 1e8 && y < 1e8 && x > -1e8 && y > -1e8);

    if (dtime < 0) printf("Time jumped backwards by dtime=%f seconds!\n", dtime);
    if (dtime <= 0) dtime = 0.0001; // protect against possible division by zero bugs

    if (dtime > 0.100 && pressure && STATE(self, PRESSURE) == 0) {
      // Workaround for tablets that don't report motion events without pressure.
      // This is to avoid linear interpolation of the pressure between two events.
      mypaint_brush_stroke_to_internal(
          self, surface, x, y, 0.0, 90.0, 0.0, dtime - 0.0001, viewzoom, viewrotation, barrel_rotation, legacy, linear);
      dtime = 0.0001;
    }

    // skip some length of input if requested (for stable tracking noise)
    if (self->skip > 0.001) {
      float dist = hypotf(self->skip_last_x-x, self->skip_last_y-y);
      self->skip_last_x = x;
      self->skip_last_y = y;
      self->skipped_dtime += dtime;
      self->skip -= dist;
      dtime = self->skipped_dtime;

      if (self->skip > 0.001 && !(dtime > max_dtime || self->reset_requested))
        return FALSE;

      // skipped
      self->skip = 0;
      self->skip_last_x = 0;
      self->skip_last_y = 0;
      self->skipped_dtime = 0;
    }

    { // calculate the actual "virtual" cursor position

      // noise first
      if (BASEVAL(self, TRACKING_NOISE)) {
        // OPTIMIZE: expf() called too often
        const float base_radius = expf(BASEVAL(self, RADIUS_LOGARITHMIC));
        const float noise = base_radius * BASEVAL(self, TRACKING_NOISE);

        if (noise > 0.001) {
          // we need to skip some length of input to make
          // tracking noise independent from input frequency
          self->skip = 0.5*noise;
          self->skip_last_x = x;
          self->skip_last_y = y;

          // add noise
          x += noise * rand_gauss(self->rng);
          y += noise * rand_gauss(self->rng);
        }
      }

      const float fac = 1.0 - exp_decay(BASEVAL(self, SLOW_TRACKING), 100.0 * dtime);
      x = STATE(self, X) + (x - STATE(self, X)) * fac;
      y = STATE(self, Y) + (y - STATE(self, Y)) * fac;
    }

    if (dtime > max_dtime || self->reset_requested) {
      self->reset_requested = FALSE;

      brush_reset(self);

      // reset value of random input
      self->random_input = rng_double_next(self->rng);

      STATE(self, X) = x;
      STATE(self, Y) = y;
      STATE(self, PRESSURE) = pressure;

      // not resetting, because they will get overwritten below:
      //dx, dy, dpress, dtime

      STATE(self, ACTUAL_X) = STATE(self, X);
      STATE(self, ACTUAL_Y) = STATE(self, Y);
      STATE(self, STROKE) = 1.0; // start in a state as if the stroke was long finished

      return TRUE;
    }

    enum { UNKNOWN, YES, NO } painted = UNKNOWN;
    double dtime_left = dtime;

    float step_ddab, step_dx, step_dy, step_dpressure, step_dtime;
    float step_declination, step_ascension, step_declinationx, step_declinationy, step_barrel_rotation;

    // draw many (or zero) dabs to the next position
    // see doc/images/stroke2dabs.png
    float dabs_moved = STATE(self, PARTIAL_DABS);
    float dabs_todo = count_dabs_to (self, x, y, dtime, legacy);
    while (dabs_moved + dabs_todo >= 1.0) { // there are dabs pending
      { // linear interpolation (nonlinear variant was too slow, see SVN log)
        float frac; // fraction of the remaining distance to move
        if (dabs_moved > 0) {
          // "move" the brush exactly to the first dab
          step_ddab = 1.0 - dabs_moved; // the step "moves" the brush by a fraction of one dab
          dabs_moved = 0;
        } else {
          step_ddab = 1.0; // the step "moves" the brush by exactly one dab
        }
        frac = step_ddab / dabs_todo;
        step_dx        = frac * (x - STATE(self, X));
        step_dy        = frac * (y - STATE(self, Y));
        step_dpressure = frac * (pressure - STATE(self, PRESSURE));
        step_dtime     = frac * (dtime_left - 0.0);
        // Though it looks different, time is interpolated exactly like x/y/pressure.
        step_declination = frac * (tilt_declination - STATE(self, DECLINATION));
        step_declinationx = frac * (tilt_declinationx - STATE(self, DECLINATIONX));
        step_declinationy = frac * (tilt_declinationy - STATE(self, DECLINATIONY));
        step_ascension   = frac * smallest_angular_difference(STATE(self, ASCENSION), tilt_ascension);
        //converts barrel_ration to degrees,
        step_barrel_rotation = frac * smallest_angular_difference(STATE(self, BARREL_ROTATION),barrel_rotation * 360);

        update_states_and_setting_values (self, step_ddab, step_dx, step_dy,
                                          step_dpressure, step_declination,
                                          step_ascension, step_dtime, viewzoom,
                                          viewrotation, step_declinationx,
                                          step_declinationy, step_barrel_rotation);
      }

      // Flips between 1 and -1, used for "mirrored" offsets.
      STATE(self, FLIP) *= -1;
      gboolean painted_now = prepare_and_draw_dab (self, surface, legacy, linear);
      if (painted_now) {
        painted = YES;
      } else if (painted == UNKNOWN) {
        painted = NO;
      }

      // update value of random input only when drawing the dab
      self->random_input = rng_double_next(self->rng);

      dtime_left -= step_dtime;
      dabs_todo = count_dabs_to(self, x, y, dtime_left, legacy);
    }

    {
      // "move" the brush to the current time (no more dab will happen)
      // Important to do this at least once every event, because
      // brush_count_dabs_to depends on the radius and the radius can
      // depend on something that changes much faster than just every
      // dab.
      step_ddab = dabs_todo; // the step "moves" the brush by a fraction of one dab
      step_dx        = x - STATE(self, X);
      step_dy        = y - STATE(self, Y);
      step_dpressure = pressure - STATE(self, PRESSURE);
      step_declination = tilt_declination - STATE(self, DECLINATION);
      step_declinationx = tilt_declinationx - STATE(self, DECLINATIONX);
      step_declinationy = tilt_declinationy - STATE(self, DECLINATIONY);
      step_ascension = smallest_angular_difference(STATE(self, ASCENSION), tilt_ascension);
      step_dtime     = dtime_left;
      step_barrel_rotation = smallest_angular_difference(STATE(self, BARREL_ROTATION), barrel_rotation * 360);

      update_states_and_setting_values (self, step_ddab, step_dx, step_dy, step_dpressure, step_declination, step_ascension, step_dtime, viewzoom, viewrotation, step_declinationx, step_declinationy, step_barrel_rotation);
    }

    // save the fraction of a dab that is already done now
    STATE(self, PARTIAL_DABS) = dabs_moved + dabs_todo;

    /* not working any more with the new rng...
    // next seed for the RNG (GRand has no get_state() and states[] must always contain our full state)
    STATE(self, RNG_SEED) = rng_double_next(self->rng);
    */

    // stroke separation logic (for undo/redo)

    if (painted == UNKNOWN) {
      if (self->stroke_current_idling_time > 0 || self->stroke_total_painting_time == 0) {
        // still idling
        painted = NO;
      } else {
        // probably still painting (we get more events than brushdabs)
        painted = YES;
        //if (pressure == 0) g_print ("info: assuming 'still painting' while there is no pressure\n");
      }
    }
    if (painted == YES) {
      //if (stroke_current_idling_time > 0) g_print ("idling ==> painting\n");
      self->stroke_total_painting_time += dtime;
      self->stroke_current_idling_time = 0;
      // force a stroke split after some time
      if (self->stroke_total_painting_time > 4 + 3*pressure) {
        // but only if pressure is not being released
        // FIXME: use some smoothed state for dpressure, not the output of the interpolation code
        //        (which might easily wrongly give dpressure == 0)
        if (step_dpressure >= 0) {
          return TRUE;
        }
      }
    } else if (painted == NO) {
      //if (stroke_current_idling_time == 0) g_print ("painting ==> idling\n");
      self->stroke_current_idling_time += dtime;
      if (self->stroke_total_painting_time == 0) {
        // not yet painted, start a new stroke if we have accumulated a lot of irrelevant motion events
        if (self->stroke_current_idling_time > 1.0) {
          return TRUE;
        }
      } else {
        // Usually we have pressure==0 here. But some brushes can paint
        // nothing at full pressure (eg gappy lines, or a stroke that
        // fades out). In either case this is the preferred moment to split.
        if (self->stroke_total_painting_time+self->stroke_current_idling_time > 0.9 + 5*pressure) {
          return TRUE;
        }
      }
    }
    return FALSE;
  }

// Compat wrapper, for supporting libjson
static gboolean
obj_get(json_object *self, const gchar *key, json_object **obj_out) {
#if JSON_C_MINOR_VERSION >= 10
    return json_object_object_get_ex(self, key, obj_out)
        && (obj_out ? (*obj_out != NULL) : TRUE);
#else
    json_object *o = json_object_object_get(self, key);
    if (obj_out) {
        *obj_out = o;
    }
    return (o != NULL);
#endif
}

static gboolean
update_brush_setting_from_json_object(MyPaintBrush *self,
                                      char *setting_name,
                                      json_object *setting_obj)
{
    MyPaintBrushSetting setting_id = mypaint_brush_setting_from_cname(setting_name);

    if (setting_id >= MYPAINT_BRUSH_SETTINGS_COUNT) {
        fprintf(stderr, "Warning: Unknown setting_id: %d for setting: %s\n",
                setting_id, setting_name);
        return FALSE;
    }

    if (!json_object_is_type(setting_obj, json_type_object)) {
        fprintf(stderr, "Warning: Wrong type for setting: %s\n", setting_name);
        return FALSE;
    }

    // Base value
    json_object *base_value_obj = NULL;
    if (! obj_get(setting_obj, "base_value", &base_value_obj)) {
        fprintf(stderr, "Warning: No 'base_value' field for setting: %s\n", setting_name);
        return FALSE;
    }
    const double base_value = json_object_get_double(base_value_obj);
    mypaint_brush_set_base_value(self, setting_id, base_value);

    // Inputs
    json_object *inputs = NULL;
    if (! obj_get(setting_obj, "inputs", &inputs)) {
        fprintf(stderr, "Warning: No 'inputs' field for setting: %s\n", setting_name);
        return FALSE;
    }
    json_object_object_foreach(inputs, input_name, input_obj) {
        MyPaintBrushInput input_id = mypaint_brush_input_from_cname(input_name);

        if (!json_object_is_type(input_obj, json_type_array)) {
            fprintf(stderr, "Warning: Wrong inputs type for setting: %s\n", setting_name);
            return FALSE;
        }

        if (input_id >= MYPAINT_BRUSH_INPUTS_COUNT) {
            fprintf(stderr, "Warning: Unknown input_id: %d for input: %s\n",
                    input_id, input_name);
            return FALSE;
        }

        const int number_of_mapping_points = json_object_array_length(input_obj);

        mypaint_brush_set_mapping_n(self, setting_id, input_id, number_of_mapping_points);

        for (int i=0; i<number_of_mapping_points; i++) {
            json_object *mapping_point = json_object_array_get_idx(input_obj, i);

            json_object *x_obj = json_object_array_get_idx(mapping_point, 0);
            const float x = json_object_get_double(x_obj);
            json_object *y_obj = json_object_array_get_idx(mapping_point, 1);
            const float y = json_object_get_double(y_obj);

            mypaint_brush_set_mapping_point(self, setting_id, input_id, i, x, y);
        }
    }

    return TRUE;
}

static gboolean
update_brush_from_json_object(MyPaintBrush *self)
{
    // Check version
    json_object *version_object = NULL;
    if (! obj_get(self->brush_json, "version", &version_object)) {
        fprintf(stderr, "Error: No 'version' field for brush\n");
        return FALSE;
    }
    const int version = json_object_get_int(version_object);
    if (version != 3) {
        fprintf(stderr, "Error: Unsupported brush setting version: %d\n", version);
        return FALSE;
    }

    // Set settings
    json_object *settings = NULL;
    if (! obj_get(self->brush_json, "settings", &settings)) {
        fprintf(stderr, "Error: No 'settings' field for brush\n");
        return FALSE;
    }

    gboolean updated_any = FALSE;
    json_object_object_foreach(settings, setting_name, setting_obj) {
      updated_any |= update_brush_setting_from_json_object(self, setting_name, setting_obj);
    }
    return updated_any;
}

gboolean
mypaint_brush_from_string(MyPaintBrush *self, const char *string)
{
    json_object *brush_json = NULL;

    if (self->brush_json) {
        // Free
        json_object_put(self->brush_json);
        self->brush_json = NULL;
    }
    if (string) {
        brush_json = json_tokener_parse(string);
    }

    if (brush_json) {
        self->brush_json = brush_json;
        return update_brush_from_json_object(self);
    }
    else {
        self->brush_json = json_object_new_object();
        return FALSE;
    }
}


void
mypaint_brush_from_defaults(MyPaintBrush *self) {
    for (int s = 0; s < MYPAINT_BRUSH_SETTINGS_COUNT; s++) {
        for (int i = 0; i < MYPAINT_BRUSH_INPUTS_COUNT; i++) {
            mypaint_brush_set_mapping_n(self, s, i, 0);
        }

        const float def = mypaint_brush_setting_info(s)->def;
        mypaint_brush_set_base_value(self, s, def);
    }

    mypaint_brush_set_mapping_n(self, MYPAINT_BRUSH_SETTING_OPAQUE_MULTIPLY, MYPAINT_BRUSH_INPUT_PRESSURE, 2);
    mypaint_brush_set_mapping_point(self, MYPAINT_BRUSH_SETTING_OPAQUE_MULTIPLY, MYPAINT_BRUSH_INPUT_PRESSURE, 0, 0.0, 0.0);
    mypaint_brush_set_mapping_point(self, MYPAINT_BRUSH_SETTING_OPAQUE_MULTIPLY, MYPAINT_BRUSH_INPUT_PRESSURE, 1, 1.0, 1.0);
}
