Esempio n. 1
0
  // 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.
  bool prepare_and_draw_dab (Surface * surface)
  {
    float x, y, opaque;
    float radius;

    // ensure we don't get a positive result with two negative opaque values
    if (settings_value[BRUSH_OPAQUE] < 0) settings_value[BRUSH_OPAQUE] = 0;
    opaque = settings_value[BRUSH_OPAQUE] * settings_value[BRUSH_OPAQUE_MULTIPLY];
    opaque = CLAMP(opaque, 0.0, 1.0);
    //if (opaque == 0.0) return false; <-- cannot do that, since we need to update smudge state.
    if (settings_value[BRUSH_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
      dabs_per_pixel = (
                        settings[BRUSH_DABS_PER_ACTUAL_RADIUS]->base_value + 
                        settings[BRUSH_DABS_PER_BASIC_RADIUS]->base_value
                        ) * 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 + settings[BRUSH_OPAQUE_LINEARIZE]->base_value*(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;
    }

    x = states[STATE_ACTUAL_X];
    y = states[STATE_ACTUAL_Y];

    float base_radius = expf(settings[BRUSH_RADIUS_LOGARITHMIC]->base_value);

    if (settings_value[BRUSH_OFFSET_BY_SPEED]) {
      x += states[STATE_NORM_DX_SLOW] * settings_value[BRUSH_OFFSET_BY_SPEED] * 0.1 * base_radius;
      y += states[STATE_NORM_DY_SLOW] * settings_value[BRUSH_OFFSET_BY_SPEED] * 0.1 * base_radius;
    }

    if (settings_value[BRUSH_OFFSET_BY_RANDOM]) {
      float amp = settings_value[BRUSH_OFFSET_BY_RANDOM];
      if (amp < 0.0) amp = 0.0;
      x += rand_gauss (rng) * amp * base_radius;
      y += rand_gauss (rng) * amp * base_radius;
    }

  
    radius = states[STATE_ACTUAL_RADIUS];
    if (settings_value[BRUSH_RADIUS_BY_RANDOM]) {
      float radius_log, alpha_correction;
      // go back to logarithmic radius to add the noise
      radius_log  = settings_value[BRUSH_RADIUS_LOGARITHMIC];
      radius_log += rand_gauss (rng) * settings_value[BRUSH_RADIUS_BY_RANDOM];
      radius = expf(radius_log);
      radius = CLAMP(radius, ACTUAL_RADIUS_MIN, ACTUAL_RADIUS_MAX);
      alpha_correction = states[STATE_ACTUAL_RADIUS] / radius;
      alpha_correction = SQR(alpha_correction);
      if (alpha_correction <= 1.0) {
        opaque *= alpha_correction;
      }
    }

    // color part

    float color_h = settings[BRUSH_COLOR_H]->base_value;
    float color_s = settings[BRUSH_COLOR_S]->base_value;
    float color_v = settings[BRUSH_COLOR_V]->base_value;
    float eraser_target_alpha = 1.0;
    if (settings_value[BRUSH_SMUDGE] > 0.0) {
      // mix (in RGB) the smudge color with the brush color
      hsv_to_rgb_float (&color_h, &color_s, &color_v);
      float fac = settings_value[BRUSH_SMUDGE];
      if (fac > 1.0) fac = 1.0;
      // If the smudge color somewhat transparent, then the resulting
      // dab will do erasing towards that transparency level.
      // see also ../doc/smudge_math.png
      eraser_target_alpha = (1-fac)*1.0 + fac*states[STATE_SMUDGE_A];
      // fix rounding errors (they really seem to happen in the previous line)
      eraser_target_alpha = CLAMP(eraser_target_alpha, 0.0, 1.0);
      if (eraser_target_alpha > 0) {
        color_h = (fac*states[STATE_SMUDGE_RA] + (1-fac)*color_h) / eraser_target_alpha;
        color_s = (fac*states[STATE_SMUDGE_GA] + (1-fac)*color_s) / eraser_target_alpha;
        color_v = (fac*states[STATE_SMUDGE_BA] + (1-fac)*color_v) / eraser_target_alpha;
      } else {
        // we are only erasing; the color does not matter
        color_h = 1.0;
        color_s = 0.0;
        color_v = 0.0;
      }
      rgb_to_hsv_float (&color_h, &color_s, &color_v);
    }

    if (settings_value[BRUSH_SMUDGE_LENGTH] < 1.0 and
        // optimization, since normal brushes have smudge_length == 0.5 without actually smudging
        (settings_value[BRUSH_SMUDGE] != 0.0 or not settings[BRUSH_SMUDGE]->is_constant())) {

      float fac = settings_value[BRUSH_SMUDGE_LENGTH];
      if (fac < 0.01) fac = 0.01;
      int px, py;
      px = ROUND(x);
      py = ROUND(y);

      // 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;
      states[STATE_LAST_GETCOLOR_RECENTNESS] *= fac;
      if (states[STATE_LAST_GETCOLOR_RECENTNESS] < 0.5*fac) {
        states[STATE_LAST_GETCOLOR_RECENTNESS] = 1.0;

        float smudge_radius = radius * expf(settings_value[BRUSH_SMUDGE_RADIUS_LOG]);
        smudge_radius = CLAMP(smudge_radius, ACTUAL_RADIUS_MIN, ACTUAL_RADIUS_MAX);
        surface->get_color (px, py, smudge_radius, &r, &g, &b, &a);

        states[STATE_LAST_GETCOLOR_R] = r;
        states[STATE_LAST_GETCOLOR_G] = g;
        states[STATE_LAST_GETCOLOR_B] = b;
        states[STATE_LAST_GETCOLOR_A] = a;
      } else {
        r = states[STATE_LAST_GETCOLOR_R];
        g = states[STATE_LAST_GETCOLOR_G];
        b = states[STATE_LAST_GETCOLOR_B];
        a = states[STATE_LAST_GETCOLOR_A];
      }

      // updated the smudge color (stored with premultiplied alpha)
      states[STATE_SMUDGE_A ] = fac*states[STATE_SMUDGE_A ] + (1-fac)*a;
      // fix rounding errors
      states[STATE_SMUDGE_A ] = CLAMP(states[STATE_SMUDGE_A], 0.0, 1.0);

      states[STATE_SMUDGE_RA] = fac*states[STATE_SMUDGE_RA] + (1-fac)*r*a;
      states[STATE_SMUDGE_GA] = fac*states[STATE_SMUDGE_GA] + (1-fac)*g*a;
      states[STATE_SMUDGE_BA] = fac*states[STATE_SMUDGE_BA] + (1-fac)*b*a;
    }

    // eraser
    if (settings_value[BRUSH_ERASER]) {
      eraser_target_alpha *= (1.0-settings_value[BRUSH_ERASER]);
    }

    // HSV color change
    color_h += settings_value[BRUSH_CHANGE_COLOR_H];
    color_s += settings_value[BRUSH_CHANGE_COLOR_HSV_S];
    color_v += settings_value[BRUSH_CHANGE_COLOR_V];

    // HSL color change
    if (settings_value[BRUSH_CHANGE_COLOR_L] || settings_value[BRUSH_CHANGE_COLOR_HSL_S]) {
      // (calculating way too much here, can be optimized if neccessary)
      // this function will CLAMP the inputs
      hsv_to_rgb_float (&color_h, &color_s, &color_v);
      rgb_to_hsl_float (&color_h, &color_s, &color_v);
      color_v += settings_value[BRUSH_CHANGE_COLOR_L];
      color_s += settings_value[BRUSH_CHANGE_COLOR_HSL_S];
      hsl_to_rgb_float (&color_h, &color_s, &color_v);
      rgb_to_hsv_float (&color_h, &color_s, &color_v);
    }

    float hardness = CLAMP(settings_value[BRUSH_HARDNESS], 0.0, 1.0);

    // anti-aliasing attempt (works surprisingly well for ink brushes)
    float current_fadeout_in_pixels = radius * (1.0 - hardness);
    float min_fadeout_in_pixels = settings_value[BRUSH_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;
    }

    // the functions below will CLAMP most inputs
    hsv_to_rgb_float (&color_h, &color_s, &color_v);
    return surface->draw_dab (x, y, radius, color_h, color_s, color_v, opaque, hardness, eraser_target_alpha,
                              states[STATE_ACTUAL_ELLIPTICAL_DAB_RATIO], states[STATE_ACTUAL_ELLIPTICAL_DAB_ANGLE],
                              settings_value[BRUSH_LOCK_ALPHA]);
  }
Esempio n. 2
0
    // 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.
    bool prepare_and_draw_dab (Surface * surface)
    {
        float x, y, opaque;
        float radius;

        opaque = settings_value[BRUSH_OPAQUE] * settings_value[BRUSH_OPAQUE_MULTIPLY];
        if (opaque >= 1.0) opaque = 1.0;
        if (opaque <= 0.0) opaque = 0.0;
        //if (opaque == 0.0) return false; <-- cannot do that, since we need to update smudge state.
        if (settings_value[BRUSH_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
            dabs_per_pixel = (
                    settings[BRUSH_DABS_PER_ACTUAL_RADIUS]->base_value +
                    settings[BRUSH_DABS_PER_BASIC_RADIUS]->base_value
                    ) * 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 + settings[BRUSH_OPAQUE_LINEARIZE]->base_value*(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;
        }

        x = states[STATE_ACTUAL_X];
        y = states[STATE_ACTUAL_Y];

        float base_radius = expf(settings[BRUSH_RADIUS_LOGARITHMIC]->base_value);

        if (settings_value[BRUSH_OFFSET_BY_SPEED]) {
            x += states[STATE_NORM_DX_SLOW] * settings_value[BRUSH_OFFSET_BY_SPEED] * 0.1 * base_radius;
            y += states[STATE_NORM_DY_SLOW] * settings_value[BRUSH_OFFSET_BY_SPEED] * 0.1 * base_radius;
        }

        if (settings_value[BRUSH_OFFSET_BY_RANDOM]) {
            x += rand_gauss (rng) * settings_value[BRUSH_OFFSET_BY_RANDOM] * base_radius;
            y += rand_gauss (rng) * settings_value[BRUSH_OFFSET_BY_RANDOM] * base_radius;
        }


        radius = states[STATE_ACTUAL_RADIUS];
        if (settings_value[BRUSH_RADIUS_BY_RANDOM]) {
            float radius_log, alpha_correction;
            // go back to logarithmic radius to add the noise
            radius_log  = settings_value[BRUSH_RADIUS_LOGARITHMIC];
            radius_log += rand_gauss (rng) * settings_value[BRUSH_RADIUS_BY_RANDOM];
            radius = expf(radius_log);
            if (radius < ACTUAL_RADIUS_MIN) radius = ACTUAL_RADIUS_MIN;
            if (radius > ACTUAL_RADIUS_MAX) radius = ACTUAL_RADIUS_MAX;
            alpha_correction = states[STATE_ACTUAL_RADIUS] / radius;
            alpha_correction = SQR(alpha_correction);
            if (alpha_correction <= 1.0) {
                opaque *= alpha_correction;
            }
        }

        // color part

        float color_h = settings[BRUSH_COLOR_H]->base_value;
        float color_s = settings[BRUSH_COLOR_S]->base_value;
        float color_v = settings[BRUSH_COLOR_V]->base_value;
        float eraser_target_alpha = 1.0;
        if (settings_value[BRUSH_SMUDGE] > 0.0) {
            // mix (in RGB) the smudge color with the brush color
            hsv_to_rgb_float (&color_h, &color_s, &color_v);
            float fac = settings_value[BRUSH_SMUDGE];
            if (fac > 1.0) fac = 1.0;
            // If the smudge color somewhat transparent, then the resulting
            // dab will do erasing towards that transparency level.
            // see also ../doc/smudge_math.png
            eraser_target_alpha = (1-fac)*1.0 + fac*states[STATE_SMUDGE_A];
            // fix rounding errors (they really seem to happen in the previous line)
            eraser_target_alpha = CLAMP(eraser_target_alpha, 0.0, 1.0);
            if (eraser_target_alpha > 0) {
                color_h = (fac*states[STATE_SMUDGE_RA] + (1-fac)*color_h) / eraser_target_alpha;
                color_s = (fac*states[STATE_SMUDGE_GA] + (1-fac)*color_s) / eraser_target_alpha;
                color_v = (fac*states[STATE_SMUDGE_BA] + (1-fac)*color_v) / eraser_target_alpha;
            } else {
                // we are only erasing; the color does not matter
                color_h = 1.0;
                color_s = 0.0;
                color_v = 0.0;
            }
            rgb_to_hsv_float (&color_h, &color_s, &color_v);
        }

        if (settings_value[BRUSH_SMUDGE_LENGTH] < 1.0 &&
            // optimization, since normal brushes have smudge_length == 0.5 without actually smudging
            (settings_value[BRUSH_SMUDGE] != 0.0 || !settings[BRUSH_SMUDGE]->is_constant())) {
            float fac = settings_value[BRUSH_SMUDGE_LENGTH];
            if (fac < 0.0) fac = 0;
            int px, py;
            px = ROUND(x);
            py = ROUND(y);
            float r, g, b, a;
            surface->get_color (px, py, radius, &r, &g, &b, &a);
            // updated the smudge color (stored with premultiplied alpha)
            states[STATE_SMUDGE_A ] = fac*states[STATE_SMUDGE_A ] + (1-fac)*a;
            // fix rounding errors
            states[STATE_SMUDGE_A ] = CLAMP(states[STATE_SMUDGE_A], 0.0, 1.0);

            states[STATE_SMUDGE_RA] = fac*states[STATE_SMUDGE_RA] + (1-fac)*r*a;
            states[STATE_SMUDGE_GA] = fac*states[STATE_SMUDGE_GA] + (1-fac)*g*a;
            states[STATE_SMUDGE_BA] = fac*states[STATE_SMUDGE_BA] + (1-fac)*b*a;
        }

        // eraser
        if (settings_value[BRUSH_ERASER]) {
            eraser_target_alpha *= (1.0-settings_value[BRUSH_ERASER]);
        }

        // HSV color change
        color_h += settings_value[BRUSH_CHANGE_COLOR_H];
        color_s += settings_value[BRUSH_CHANGE_COLOR_HSV_S];
        color_v += settings_value[BRUSH_CHANGE_COLOR_V];

        // HSL color change
        if (settings_value[BRUSH_CHANGE_COLOR_L] || settings_value[BRUSH_CHANGE_COLOR_HSL_S]) {
            // (calculating way too much here, can be optimized if neccessary)
            // this function will CLAMP the inputs
            hsv_to_rgb_float (&color_h, &color_s, &color_v);
            rgb_to_hsl_float (&color_h, &color_s, &color_v);
            color_v += settings_value[BRUSH_CHANGE_COLOR_L];
            color_s += settings_value[BRUSH_CHANGE_COLOR_HSL_S];
            hsl_to_rgb_float (&color_h, &color_s, &color_v);
            rgb_to_hsv_float (&color_h, &color_s, &color_v);
        }

        float hardness = settings_value[BRUSH_HARDNESS];

        // the functions below will CLAMP most inputs
        hsv_to_rgb_float (&color_h, &color_s, &color_v);
        return surface->draw_dab (x, y, radius, color_h, color_s, color_v, opaque, hardness, eraser_target_alpha,
                                  states[STATE_ACTUAL_ELLIPTICAL_DAB_RATIO], states[STATE_ACTUAL_ELLIPTICAL_DAB_ANGLE]);
    }