Paint: 2D paint brush stroke antialiasing
authorPablo Dobarro <pablodp606@gmail.com>
Mon, 7 Oct 2019 17:34:15 +0000 (19:34 +0200)
committerPablo Dobarro <pablodp606@gmail.com>
Mon, 7 Oct 2019 17:47:40 +0000 (19:47 +0200)
This commit enables antialiasing in 2D painting.
It also includes some fixes related to line drawing in the stroke spacing code.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D5833

source/blender/editors/sculpt_paint/paint_image.c
source/blender/editors/sculpt_paint/paint_image_2d.c
source/blender/editors/sculpt_paint/paint_stroke.c

index 24c2dfb6c6b14e977b3aa82f8de21e0340912d0b..b4388f6c3246349a2d3cc59a7b10d655e2e9b0e8 100644 (file)
@@ -540,7 +540,7 @@ static void paint_stroke_update_step(bContext *C, struct PaintStroke *stroke, Po
   RNA_float_get_array(itemptr, "mouse", mouse);
   pressure = RNA_float_get(itemptr, "pressure");
   eraser = RNA_boolean_get(itemptr, "pen_flip");
-  size = max_ff(1.0f, RNA_float_get(itemptr, "size"));
+  size = RNA_float_get(itemptr, "size");
 
   /* stroking with fill tool only acts on stroke end */
   if (brush->imagepaint_tool == PAINT_TOOL_FILL) {
index 4f1ae10aa6246abe44da536c197ea0c8aecaed4a..9c95a3cee4dd4ef32ffdb8c6392d81df58a05315 100644 (file)
@@ -374,25 +374,65 @@ static void brush_painter_mask_imbuf_partial_update(BrushPainter *painter,
 /* create a mask with the falloff strength */
 static unsigned short *brush_painter_curve_mask_new(BrushPainter *painter,
                                                     int diameter,
-                                                    float radius)
+                                                    float radius,
+                                                    const float pos[2])
 {
   Brush *brush = painter->brush;
 
-  int xoff = -radius;
-  int yoff = -radius;
+  int offset = (int)floorf(diameter / 2.0f);
 
   unsigned short *mask, *m;
-  int x, y;
 
   mask = MEM_mallocN(sizeof(unsigned short) * diameter * diameter, "brush_painter_mask");
   m = mask;
 
-  for (y = 0; y < diameter; y++) {
-    for (x = 0; x < diameter; x++, m++) {
-      float xy[2] = {x + xoff, y + yoff};
-      float len = len_v2(xy);
+  int aa_samples = 1.0f / (radius * 0.20f);
+  aa_samples = clamp_i(aa_samples, 3, 16);
 
-      *m = (unsigned short)(65535.0f * BKE_brush_curve_strength_clamped(brush, len, radius));
+  /* Temporal until we have the brush properties */
+  const float hardness = 1.0f;
+  const float rotation = 0.0f;
+
+  float aa_offset = 1.0f / (2.0f * (float)aa_samples);
+  float aa_step = 1.0f / (float)aa_samples;
+
+  float bpos[2];
+  bpos[0] = pos[0] - floorf(pos[0]) + offset + aa_offset;
+  bpos[1] = pos[1] - floorf(pos[1]) + offset + aa_offset;
+
+  const float co = cosf(DEG2RADF(rotation));
+  const float si = sinf(DEG2RADF(rotation));
+
+  float norm_factor = 65535.0f / (float)(aa_samples * aa_samples);
+
+  for (int y = 0; y < diameter; y++) {
+    for (int x = 0; x < diameter; x++, m++) {
+      float total_samples = 0;
+      for (int i = 0; i < aa_samples; i++) {
+        for (int j = 0; j < aa_samples; j++) {
+          float pixel_xy[2] = {x + (aa_step * i), y + (aa_step * j)};
+          float xy_rot[2];
+          sub_v2_v2(pixel_xy, bpos);
+
+          xy_rot[0] = co * pixel_xy[0] - si * pixel_xy[1];
+          xy_rot[1] = si * pixel_xy[0] + co * pixel_xy[1];
+
+          float len = len_v2(xy_rot);
+          float p = len / radius;
+          if (hardness < 1.0f) {
+            p = (p - hardness) / (1 - hardness);
+            p = 1.0f - p;
+            CLAMP(p, 0, 1);
+          }
+          else {
+            p = 1.0;
+          }
+          float hardness_factor = 3.0f * p * p - 2.0f * p * p * p;
+          float curve = BKE_brush_curve_strength_clamped(brush, len, radius);
+          total_samples += curve * hardness_factor;
+        }
+      }
+      *m = (unsigned short)(total_samples * norm_factor);
     }
   }
 
@@ -721,7 +761,8 @@ static void brush_painter_2d_refresh_cache(ImagePaintState *s,
   UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
   Brush *brush = painter->brush;
   BrushPainterCache *cache = &painter->cache;
-  const int diameter = 2 * size;
+  /* Adding 4 pixels of padding for brush antialiasing */
+  const int diameter = MAX2(1, size * 2) + 4;
 
   bool do_random = false;
   bool do_partial_update = false;
@@ -802,15 +843,13 @@ static void brush_painter_2d_refresh_cache(ImagePaintState *s,
   }
 
   /* curve mask can only change if the size changes */
-  if (diameter != cache->lastdiameter) {
-    if (cache->curve_mask) {
-      MEM_freeN(cache->curve_mask);
-      cache->curve_mask = NULL;
-    }
-
-    cache->curve_mask = brush_painter_curve_mask_new(painter, diameter, size);
+  if (cache->curve_mask) {
+    MEM_freeN(cache->curve_mask);
+    cache->curve_mask = NULL;
   }
 
+  cache->curve_mask = brush_painter_curve_mask_new(painter, diameter, size, pos);
+
   /* detect if we need to recreate image brush buffer */
   if ((diameter != cache->lastdiameter) || (tex_rotation != cache->last_tex_rotation) ||
       do_random || update_color) {
index a310810e9978c7eb932ecf7364b0a947bf2131fe..3641804555169ecdbf77c466c2ad8323cd8600f4 100644 (file)
@@ -669,7 +669,7 @@ static float paint_space_stroke_spacing(bContext *C,
     return max_ff(0.001f, size_clamp * spacing / 50.f);
   }
   else {
-    return max_ff(1.0, size_clamp * spacing / 50.0f);
+    return max_ff(stroke->zoom_2d, size_clamp * spacing / 50.0f);
   }
 }