When debugging isoline generations using `StepsViewer`, use different colors for polylines at different stages.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PathBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PathBuilder.java
index 3977234..f4ae2b2 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PathBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/PathBuilder.java
@@ -221,7 +221,7 @@
      * The {@link #createPolyline(boolean)} method should be invoked before this method
      * for making sure that there are no pending polylines.
      *
-     * @return the polyline, polygon or collector of polylines.
+     * @return the polyline, polygon or collection of polylines.
      *         May be {@code null} if no polyline or polygon has been created.
      */
     public final Shape build() {
@@ -233,6 +233,17 @@
     }
 
     /**
+     * Returns a snapshot of currently added polylines or polygons without modifying the state of this builder.
+     * It is safe to continue building the shape and invoke this method again later for progressive rendering.
+     *
+     * @return the polyline, polygon or collection of polylines added so far.
+     *         May be {@code null} if no polyline or polygon has been created.
+     */
+    public final Shape snapshot() {
+        return build();
+    }
+
+    /**
      * Returns a string representation of the polyline under construction for debugging purposes.
      * Current implementation formats only the first and last points, and tells how many points are between.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Isolines.java b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Isolines.java
index a1e22aa..abb8792 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Isolines.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Isolines.java
@@ -19,6 +19,8 @@
 import java.util.AbstractList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.EnumMap;
 import java.util.TreeMap;
 import java.util.NavigableMap;
 import java.util.function.BiConsumer;
@@ -49,7 +51,7 @@
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
  *
  * @see <a href="https://en.wikipedia.org/wiki/Marching_squares">Marching squares on Wikipedia</a>
  *
@@ -69,7 +71,7 @@
      * by step.
      */
     @Debug
-    private static final BiConsumer<String,Path2D> LISTENER = null;
+    private static final BiConsumer<String,Isolines> LISTENER = null;
 
     /**
      * Creates an initially empty set of isolines for the given levels. The given {@code values}
@@ -399,8 +401,7 @@
                 if (LISTENER != null) {
                     final int y = tracer.y;
                     final int h = iterator.getDomain().height;
-                    LISTENER.accept(String.format("After row %d of %d (%3.1f%%)", y, h, 100f*y/h),
-                                    isolines[b].toRawPath());
+                    LISTENER.accept(String.format("After row %d of %d (%3.1f%%)", y, h, 100f*y/h), isolines[b]);
                 }
             }
             tracer.x = 0;
@@ -414,7 +415,7 @@
                 level.finish();
             }
             if (LISTENER != null) {
-                LISTENER.accept("Finished band " + b, isolines[b].toRawPath());
+                LISTENER.accept("Finished band " + b, isolines[b]);
             }
         }
         return isolines;
@@ -526,18 +527,18 @@
     }
 
     /**
-     * Appends the pixel coordinates of all level to the given path, for debugging purposes only.
+     * Returns the pixel coordinates of all level, for debugging purposes only.
      * The {@link #gridToCRS} transform is <em>not</em> applied by this method.
      * For avoiding confusing behavior, that transform should be null.
      *
-     * @param  appendTo  where to append the coordinates.
+     * @return the pixel coordinates.
      */
     @Debug
-    private Path2D toRawPath() {
-        final Path2D path = new Path2D.Float();
+    final Map<PolylineStage,Path2D> toRawPath() {
+        final Map<PolylineStage,Path2D> appendTo = new EnumMap<>(PolylineStage.class);
         for (final Tracer.Level level : levels) {
-            level.toRawPath(path);
+            level.toRawPath(appendTo);
         }
-        return path;
+        return appendTo;
     }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineBuffer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineBuffer.java
index 5da2768..be7b42e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineBuffer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineBuffer.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.processing.isoline;
 
+import java.util.Map;
 import java.util.Arrays;
 import java.awt.geom.Path2D;
 import org.apache.sis.internal.feature.j2d.PathBuilder;
@@ -200,15 +201,16 @@
      *
      * @param  appendTo  where to append the coordinates.
      *
-     * @see Tracer.Level#toRawPath(Path2D)
+     * @see Tracer.Level#toRawPath(Map)
      */
     @Debug
-    final void toRawPath(final Path2D appendTo) {
+    final void toRawPath(final Map<PolylineStage,Path2D> appendTo) {
         int i = 0;
         if (i < size) {
-            appendTo.moveTo(coordinates[i++], coordinates[i++]);
+            final Path2D p = PolylineStage.BUFFER.destination(appendTo);
+            p.moveTo(coordinates[i++], coordinates[i++]);
             while (i < size) {
-                appendTo.lineTo(coordinates[i++], coordinates[i++]);
+                p.lineTo(coordinates[i++], coordinates[i++]);
             }
         }
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineStage.java b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineStage.java
new file mode 100644
index 0000000..7ad1733
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/PolylineStage.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.processing.isoline;
+
+import java.util.Map;
+import java.awt.Shape;
+import java.awt.geom.Path2D;
+import org.apache.sis.util.Debug;
+
+
+/**
+ * Tells at which stage are the polylines represented by a Java2D {@link Shape}.
+ * A set of polylines way still be under construction in {@link PolylineBuffer}
+ * during iteration over pixel values, or the polylines may have been classified
+ * as incomplete after iteration over a row, or the polylines may be final result.
+ *
+ * <p>This is used only for debugging purposes because end users should see only the final result.
+ * This information allows {@code StepsViewer} (in test package) to use different colors for different stages.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.3
+ * @since   1.3
+ * @module
+ */
+@Debug
+enum PolylineStage {
+    /**
+     * The polylines are under construction in various {@link PolylineBuffer} instances.
+     * This is the first stage, which happens during iteration over pixel values.
+     */
+    BUFFER,
+
+    /**
+     * The polylines are no longer in the buffers filled by the iteration over pixel values,
+     * but are still incomplete. It happens when, after finishing iteration over a row, some
+     * polylines will not be continued by iteration on the next row and those polylines have
+     * not yet been closed as polygons. Those polyline fragments are moved to a "pending" list,
+     * as they may be closed later after more polylines fragments become available.
+     */
+    FRAGMENT,
+
+    /**
+     * The polylines are final result to be show to user.
+     */
+    FINAL;
+
+    /**
+     * Returns the destination where to write polylines for this stage.
+     *
+     * @param  appendTo  map of path for different stages.
+     * @return the path to use for writing polylines at this stage.
+     */
+    final Path2D destination(final Map<PolylineStage,Path2D> appendTo) {
+        return appendTo.computeIfAbsent(this, (k) -> new Path2D.Float());
+    }
+
+    /**
+     * Adds polylines to the specified map.
+     *
+     * @param  appendTo   where to append the polylines.
+     * @param  polylines  the polylines to append to the map, or {@code null} if none.
+     */
+    final void add(final Map<PolylineStage,Path2D> appendTo, final Shape polylines) {
+        if (polylines != null) {
+            destination(appendTo).append(polylines, false);
+        }
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Tracer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Tracer.java
index 5bfbbac..d6e3e6c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Tracer.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/processing/isoline/Tracer.java
@@ -222,15 +222,20 @@
 
         /**
          * Builder of isolines as a Java2D shape, created when first needed.
-         * The {@link PolylineBuffer} coordinates are copied in this path when a geometry is closed.
+         * The {@link PolylineBuffer} coordinates are copied in this path when a geometry is closed
+         * and transformed using {@link #gridToCRS}. This is almost final result; the only difference
+         * compared to {@link #shape} is that the coordinates are not yet wrapped in a {@link Shape}.
          *
          * @see #writeTo(Joiner, PolylineBuffer[], boolean)
+         * @see PolylineStage#FINAL
          */
         private Joiner path;
 
         /**
          * The isolines as a Java2D shape, created by {@link #finish()}.
          * This is the shape to be returned to user for this level after we finished to process all cells.
+         *
+         * @see PolylineStage#FINAL
          */
         Shape shape;
 
@@ -688,9 +693,8 @@
          * @see Isolines#toRawPath()
          */
         @Debug
-        final void toRawPath(final Path2D appendTo) {
-            final Shape s = (path != null) ? path.build() : shape;
-            if (s != null) appendTo.append(s, false);
+        final void toRawPath(final Map<PolylineStage,Path2D> appendTo) {
+            PolylineStage.FINAL.add(appendTo, (path != null) ? path.snapshot() : shape);
             polylineOnLeft.toRawPath(appendTo);
             for (final PolylineBuffer p : polylinesOnTop) {
                 if (p != null) p.toRawPath(appendTo);
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/processing/isoline/StepsViewer.java b/core/sis-feature/src/test/java/org/apache/sis/internal/processing/isoline/StepsViewer.java
index 0618d21..4f54021 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/processing/isoline/StepsViewer.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/processing/isoline/StepsViewer.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.internal.processing.isoline;
 
+import java.util.Map;
+import java.util.EnumMap;
 import java.awt.Shape;
 import java.awt.Color;
 import java.awt.Graphics;
@@ -58,7 +60,7 @@
  * @module
  */
 @SuppressWarnings("serial")
-public final class StepsViewer extends JComponent implements BiConsumer<String,Path2D>, ChangeListener, ActionListener {
+public final class StepsViewer extends JComponent implements BiConsumer<String,Isolines>, ChangeListener, ActionListener {
     /**
      * Sets the component to be notified after each row of isolines generated from the rendered image.
      * The body of this method is commented-out because {@link Isolines#LISTENER} is private and final.
@@ -116,7 +118,13 @@
     /**
      * The isolines to show.
      */
-    private Path2D isolines;
+    private final Map<PolylineStage,Path2D> isolines;
+
+    /**
+     * The colors to associate to the isoline for each stage.
+     * Array indices are {@link PolylineStage#ordinal()} values.
+     */
+    private final Color[] stageColors;
 
     /**
      * Bounds of {@link #isolines}, slightly expanded for making easier to see.
@@ -136,6 +144,10 @@
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")
     private StepsViewer(final RenderedImage data, final Container pane) {
+        isolines    = new EnumMap<>(PolylineStage.class);
+        stageColors = new Color[] {Color.YELLOW, Color.CYAN, Color.GRAY};
+        setBackground(Color.BLACK);
+        setOpaque(true);
         final double scaleX = (CANVAS_WIDTH  - 2*PADDING) / (double) data.getWidth();
         final double scaleY = (CANVAS_HEIGHT - 2*PADDING) / (double) data.getHeight();
         sourceToCanvas = new AffineTransform2D(
@@ -186,7 +198,6 @@
         for (final Shape shape : iso.polylines().values()) {
             path.append(shape, false);
         }
-        viewer.accept("Final result", path);
     }
 
     /**
@@ -196,15 +207,18 @@
     protected void paintComponent(final Graphics g) {
         super.paintComponent(g);
         final Graphics2D gh = (Graphics2D) g;
+        gh.setColor(getBackground());
+        gh.fillRect(0, 0, getWidth(), getHeight());
         if (bounds != null) {
             gh.setStroke(new BasicStroke(2));
             gh.setColor(Color.RED);
             gh.draw(bounds);
         }
-        if (isolines != null) {
-            gh.setStroke(new BasicStroke(1));
-            gh.setColor(Color.BLUE);
-            gh.draw(isolines);
+        for (final Map.Entry<PolylineStage,Path2D> entry : isolines.entrySet()) {
+            final int stage = entry.getKey().ordinal();
+            gh.setStroke(new BasicStroke(stageColors.length - stage));
+            gh.setColor(stageColors[stage]);
+            gh.draw(entry.getValue());
         }
     }
 
@@ -246,27 +260,48 @@
      * Invoked after a row has been processed during the isoline generation.
      * This is invoked from the main thread (<strong>not</strong> the Swing thread).
      *
-     * @param  title   description of current state.
-     * @param  update  new isolines to show.
+     * @param  title      description of current state.
+     * @param  generator  new generator of isolines.
      */
     @Override
-    public void accept(final String title, final Path2D update) {
-        update.transform(sourceToCanvas);
-        final Rectangle b = update.getBounds();
-        b.x      -= PADDING;
-        b.y      -= PADDING;
-        b.width  += PADDING * 2;
-        b.height += PADDING * 2;
+    public void accept(final String title, final Isolines generator) {
+        final Map<PolylineStage, Path2D> paths = generator.toRawPath();
+        for (final Map.Entry<PolylineStage,Path2D> entry : paths.entrySet()) {
+            entry.getValue().transform(sourceToCanvas);
+        }
         try {
             final CountDownLatch c = new CountDownLatch(1);
             EventQueue.invokeLater(() -> {
-                if (isolines != null && equal(isolines.getPathIterator(null), update.getPathIterator(null))) {
+                Rectangle b = null;
+                boolean unchanged = true;
+                for (final PolylineStage stage : PolylineStage.values()) {
+                    final Path2D current = isolines.get(stage);
+                    final Path2D update  = paths.get(stage);
+                    if (unchanged && current != update && !(current != null && update != null &&
+                            equal(current.getPathIterator(null), update.getPathIterator(null))))
+                    {
+                        unchanged = false;
+                    }
+                    if (update == null) {
+                        isolines.remove(stage);
+                    } else {
+                        isolines.put(stage, update);
+                        if (stage == PolylineStage.BUFFER) {
+                            b = update.getBounds();
+                            b.x      -= PADDING;
+                            b.y      -= PADDING;
+                            b.width  += PADDING * 2;
+                            b.height += PADDING * 2;
+                            bounds = b;
+                        }
+                    }
+                }
+                bounds = b;
+                if (unchanged) {
                     stepTitle.setText(title + " (no change)");
                     c.countDown();
                 } else {
                     stepTitle.setText(title);
-                    isolines = update;
-                    bounds = b;
                     repaint();
                     assertNull(blocker);
                     if (next.getModel().isPressed()) {