Added A2B support to colorspaceinfo tool
authorMatt Sarett <msarett@google.com>
Mon, 1 May 2017 21:42:20 +0000 (17:42 -0400)
committerSkia Commit-Bot <skia-commit-bot@chromium.org>
Mon, 1 May 2017 22:10:58 +0000 (22:10 +0000)
Tool now parses A2B images/profiles.
Tool can now display a cross-section based visualization for the color
look-up tables in A2B profiles.

BUG=skia:

Change-Id: I68abb3e947b080c533e283783d7859feea8d35d6
Reviewed-on: https://skia-review.googlesource.com/6119
Commit-Queue: Matt Sarett <msarett@google.com>
Reviewed-by: Matt Sarett <msarett@google.com>
src/core/SkColorLookUpTable.h
src/core/SkColorSpace_Base.h
tools/colorspaceinfo.cpp

index 0cde767..cc5415b 100644 (file)
@@ -36,7 +36,7 @@ public:
     int inputChannels() const { return fInputChannels; }
 
     int outputChannels() const { return kOutputChannels; }
-    
+
     int gridPoints(int dimension) const {
         SkASSERT(dimension >= 0 && dimension < inputChannels());
         return fGridPoints[dimension];
index 61f65a5..9055cce 100644 (file)
@@ -110,7 +110,7 @@ struct SkGammas : SkRefCnt {
         SkASSERT(i >= 0 && i < fChannels);
         return fType[i];
     }
-    
+
     uint8_t channels() const { return fChannels; }
 
     SkGammas(uint8_t channels)
index d605d50..91445db 100644 (file)
 
 #include "sk_tool_utils.h"
 
+#include <sstream>
+#include <string>
+#include <vector>
+
 DEFINE_string(input, "input.png", "A path to the input image or icc profile.");
-DEFINE_string(gamut_output, "gamut_output.png", "A path to the output gamut image.");
-DEFINE_string(gamma_output, "gamma_output.png", "A path to the output gamma image.");
+DEFINE_string(output, ".", "A path to the output image directory.");
 DEFINE_bool(sRGB_gamut, false, "Draws the sRGB gamut on the gamut visualization.");
 DEFINE_bool(adobeRGB, false, "Draws the Adobe RGB gamut on the gamut visualization.");
 DEFINE_bool(sRGB_gamma, false, "Draws the sRGB gamma on all gamma output images.");
 DEFINE_string(uncorrected, "", "A path to reencode the uncorrected input image.");
 
+
+//-------------------------------------------------------------------------------------------------
+//------------------------------------ Gamma visualizations ---------------------------------------
+
 static const char* kRGBChannelNames[3] = {
-    "Red  ", "Green", "Blue "
+    "Red  ",
+    "Green",
+    "Blue "
 };
-
 static const SkColor kRGBChannelColors[3] = {
-    SkColorSetARGB(164, 255, 32, 32),
-    SkColorSetARGB(164, 32, 255, 32),
-    SkColorSetARGB(164, 32, 32, 255)
+    SkColorSetARGB(128, 255, 0, 0),
+    SkColorSetARGB(128, 0, 255, 0),
+    SkColorSetARGB(128, 0, 0, 255)
+};
+
+static const char* kGrayChannelNames[1] = { "Gray"};
+static const SkColor kGrayChannelColors[1] = { SkColorSetRGB(128, 128, 128) };
+
+static const char* kCMYKChannelNames[4] = {
+    "Cyan   ",
+    "Magenta",
+    "Yellow ",
+    "Black  "
+};
+static const SkColor kCMYKChannelColors[4] = {
+    SkColorSetARGB(128, 0, 255, 255),
+    SkColorSetARGB(128, 255, 0, 255),
+    SkColorSetARGB(128, 255, 255, 0),
+    SkColorSetARGB(128, 16, 16, 16)
+};
+
+static const char*const*const kChannelNames[4] = {
+    kGrayChannelNames,
+    kRGBChannelNames,
+    kRGBChannelNames,
+    kCMYKChannelNames
+};
+static const SkColor*const kChannelColors[4] = {
+    kGrayChannelColors,
+    kRGBChannelColors,
+    kRGBChannelColors,
+    kCMYKChannelColors
 };
 
 static void dump_transfer_fn(SkGammaNamed gammaNamed) {
@@ -55,35 +92,38 @@ static void dump_transfer_fn(SkGammaNamed gammaNamed) {
 
 }
 
+static constexpr int kGammaImageWidth = 500;
+static constexpr int kGammaImageHeight = 500;
+
 static void dump_transfer_fn(const SkGammas& gammas) {
-    SkASSERT(gammas.channels() == 3);
+    SkASSERT(gammas.channels() <= 4);
+    const char*const*const channels = kChannelNames[gammas.channels() - 1];
     for (int i = 0; i < gammas.channels(); i++) {
         if (gammas.isNamed(i)) {
             switch (gammas.data(i).fNamed) {
                 case kSRGB_SkGammaNamed:
-                    SkDebugf("%s Transfer Function: sRGB\n", kRGBChannelNames[i]);
+                    SkDebugf("%s Transfer Function: sRGB\n", channels[i]);
                     return;
                 case k2Dot2Curve_SkGammaNamed:
-                    SkDebugf("%s Transfer Function: Exponent 2.2\n", kRGBChannelNames[i]);
+                    SkDebugf("%s Transfer Function: Exponent 2.2\n", channels[i]);
                     return;
                 case kLinear_SkGammaNamed:
-                    SkDebugf("%s Transfer Function: Linear\n", kRGBChannelNames[i]);
+                    SkDebugf("%s Transfer Function: Linear\n", channels[i]);
                     return;
                 default:
                     SkASSERT(false);
                     continue;
             }
         } else if (gammas.isValue(i)) {
-            SkDebugf("%s Transfer Function: Exponent %.3f\n", kRGBChannelNames[i],
-                     gammas.data(i).fValue);
+            SkDebugf("%s Transfer Function: Exponent %.3f\n", channels[i], gammas.data(i).fValue);
         } else if (gammas.isParametric(i)) {
             const SkColorSpaceTransferFn& fn = gammas.data(i).params(&gammas);
             SkDebugf("%s Transfer Function: Parametric A = %.3f, B = %.3f, C = %.3f, D = %.3f, "
-                     "E = %.3f, F = %.3f, G = %.3f\n", kRGBChannelNames[i], fn.fA, fn.fB, fn.fC,
-                     fn.fD, fn.fE, fn.fF, fn.fG);
+                     "E = %.3f, F = %.3f, G = %.3f\n", channels[i], fn.fA, fn.fB, fn.fC, fn.fD,
+                     fn.fE, fn.fF, fn.fG);
         } else {
             SkASSERT(gammas.isTable(i));
-            SkDebugf("%s Transfer Function: Table (%d entries)\n", kRGBChannelNames[i],
+            SkDebugf("%s Transfer Function: Table (%d entries)\n", channels[i],
                     gammas.data(i).fTable.fSize);
         }
     }
@@ -95,7 +135,7 @@ static inline float parametric(const SkColorSpaceTransferFn& fn, float x) {
 }
 
 static void draw_transfer_fn(SkCanvas* canvas, SkGammaNamed gammaNamed, const SkGammas* gammas,
-                             SkColor color, int col) {
+                             SkColor color) {
     SkColorSpaceTransferFn fn[4];
     struct TableInfo {
         const float* fTable;
@@ -142,16 +182,14 @@ static void draw_transfer_fn(SkCanvas* canvas, SkGammaNamed gammaNamed, const Sk
     // note: gamma has positive values going up in this image so this origin is
     //       the bottom left and we must subtract y instead of adding.
     const float gap         = 16.0f;
-    const float cellWidth   = 500.0f;
-    const float cellHeight  = 500.0f;
-    const float gammaWidth  = cellWidth - 2 * gap;
-    const float gammaHeight = cellHeight - 2 * gap;
+    const float gammaWidth  = kGammaImageWidth - 2 * gap;
+    const float gammaHeight = kGammaImageHeight - 2 * gap;
     // gamma origin point
-    const float ox = gap + cellWidth * col;
+    const float ox = gap;
     const float oy = gap + gammaHeight;
     for (int i = 0; i < channels; ++i) {
         if (kNonStandard_SkGammaNamed == gammaNamed) {
-            paint.setColor(kRGBChannelColors[i]);
+            paint.setColor(kChannelColors[channels - 1][i]);
         } else {
             paint.setColor(color);
         }
@@ -183,6 +221,98 @@ static void draw_transfer_fn(SkCanvas* canvas, SkGammaNamed gammaNamed, const Sk
     canvas->drawRect({ ox, oy - gammaHeight, ox + gammaWidth, oy }, paint);
 }
 
+//-------------------------------------------------------------------------------------------------
+//------------------------------------ CLUT visualizations ----------------------------------------
+static void dump_clut(const SkColorLookUpTable& clut) {
+    SkDebugf("CLUT: ");
+    for (int i = 0; i < clut.inputChannels(); ++i) {
+        SkDebugf("[%d]", clut.gridPoints(i));
+    }
+    SkDebugf(" -> [%d]\n", clut.outputChannels());
+}
+
+constexpr int kClutGap = 8;
+constexpr float kClutCanvasSize = 2000;
+
+static inline int usedGridPoints(const SkColorLookUpTable& clut, int dimension) {
+    const int gp = clut.gridPoints(dimension);
+    return gp <= 16 ? gp : 16;
+}
+
+// how many rows of cross-section cuts to display
+static inline int cut_rows(const SkColorLookUpTable& clut, int dimOrder[4]) {
+    // and vertical ones for the 4th dimension (if applicable)
+    return clut.inputChannels() >= 4 ? usedGridPoints(clut, dimOrder[3]) : 1;
+}
+
+// how many columns of cross-section cuts to display
+static inline int cut_cols(const SkColorLookUpTable& clut, int dimOrder[4]) {
+    // do horizontal cuts for the 3rd dimension (if applicable)
+    return clut.inputChannels() >= 3 ? usedGridPoints(clut, dimOrder[2]) : 1;
+}
+
+// gets the width/height to use for cross-sections of a CLUT
+static int cut_size(const SkColorLookUpTable& clut, int dimOrder[4]) {
+    const int rows = cut_rows(clut, dimOrder);
+    const int cols = cut_cols(clut, dimOrder);
+    // make sure the cross-section CLUT cuts are square still by using the
+    // smallest of the width/height, then adjust the gaps between accordingly
+    const int cutWidth = (kClutCanvasSize - kClutGap * (1 + cols)) / cols;
+    const int cutHeight = (kClutCanvasSize - kClutGap * (1 + rows)) / rows;
+    return cutWidth < cutHeight ? cutWidth : cutHeight;
+}
+
+static void draw_clut(SkCanvas* canvas, const SkColorLookUpTable& clut, int dimOrder[4]) {
+    dump_clut(clut);
+
+    const int cutSize = cut_size(clut, dimOrder);
+    const int rows = cut_rows(clut, dimOrder);
+    const int cols = cut_cols(clut, dimOrder);
+    const int cutHorizGap = (kClutCanvasSize - cutSize * cols) / (1 + cols);
+    const int cutVertGap = (kClutCanvasSize - cutSize * rows) / (1 + rows);
+
+    SkPaint paint;
+    for (int row = 0; row < rows; ++row) {
+        for (int col = 0; col < cols; ++col) {
+            // make sure to move at least one pixel, but otherwise move per-gridpoint
+            const float xStep = 1.0f / (SkTMin(cutSize, clut.gridPoints(dimOrder[0])) - 1);
+            const float yStep = 1.0f / (SkTMin(cutSize, clut.gridPoints(dimOrder[1])) - 1);
+            const float ox = clut.inputChannels() >= 3 ? (1 + col) * cutHorizGap + col * cutSize
+                                                       : kClutGap;
+            const float oy = clut.inputChannels() >= 4 ? (1 + row) * cutVertGap + row * cutSize
+                                                       : kClutGap;
+            // for each cross-section cut, draw a bunch of squares whose colour is the top-left's
+            // colour in the CLUT (usually this will just draw the gridpoints)
+            for (float x = 0.0f; x < 1.0f; x += xStep) {
+                for (float y = 0.0f; y < 1.0f; y += yStep) {
+                    const float z = col / (cols - 1.0f);
+                    const float w = row / (rows - 1.0f);
+                    const float input[4] = {x, y, z, w};
+                    float output[3];
+                    clut.interp(output, input);
+                    paint.setColor(SkColorSetRGB(255*output[0], 255*output[1], 255*output[2]));
+                    canvas->drawRect(SkRect::MakeLTRB(ox + cutSize * x, oy + cutSize * y,
+                                                      ox + cutSize * (x + xStep),
+                                                      oy + cutSize * (y + yStep)), paint);
+                }
+            }
+        }
+    }
+}
+
+
+//-------------------------------------------------------------------------------------------------
+//------------------------------------ Gamut visualizations ---------------------------------------
+static void dump_matrix(const SkMatrix44& m) {
+    for (int r = 0; r < 4; ++r) {
+        SkDebugf("|");
+        for (int c = 0; c < 4; ++c) {
+            SkDebugf(" %f ", m.get(r, c));
+        }
+        SkDebugf("|\n");
+    }
+}
+
 /**
  *  Loads the triangular gamut as a set of three points.
  */
@@ -258,22 +388,63 @@ static void draw_gamut(SkCanvas* canvas, const SkMatrix44& xyz, const char* name
     }
 }
 
+
+//-------------------------------------------------------------------------------------------------
+//----------------------------------------- Main code ---------------------------------------------
+static SkBitmap transparentBitmap(int width, int height) {
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(width, height);
+    bitmap.eraseColor(SkColorSetARGB(0, 0, 0, 0));
+    return bitmap;
+}
+
+class OutputCanvas {
+public:
+    OutputCanvas(SkBitmap&& bitmap)
+        :fBitmap(bitmap)
+        ,fCanvas(fBitmap)
+    {}
+
+    bool save(std::vector<std::string>* output, const std::string& filename) {
+        // Finally, encode the result to the output file.
+        sk_sp<SkData> out = sk_tool_utils::EncodeImageToData(fBitmap, SkEncodedImageFormat::kPNG,
+                                                             100);
+        if (!out) {
+            SkDebugf("Failed to encode %s output.\n", filename.c_str());
+            return false;
+        }
+        SkFILEWStream stream(filename.c_str());
+        if (!stream.write(out->data(), out->size())) {
+            SkDebugf("Failed to write %s output.\n", filename.c_str());
+            return false;
+        }
+        // record name of canvas
+        output->push_back(filename);
+        return true;
+    }
+
+    SkCanvas* canvas() { return &fCanvas; }
+
+private:
+    SkBitmap fBitmap;
+    SkCanvas fCanvas;
+};
+
 int main(int argc, char** argv) {
     SkCommandLineFlags::SetUsage(
             "Usage: colorspaceinfo --input <path to input image or icc profile> "
-                                  "--gamma_output <path to output gamma image> "
-                                  "--gamut_output <path to output gamut image>"
-                                  "--sRGB <draw canonical sRGB gamut> "
+                                  "--output <directory to output images> "
+                                  "--sRGB_gamut <draw canonical sRGB gamut> "
                                   "--adobeRGB <draw canonical Adobe RGB gamut> "
+                                  "--sRGB_gamma <draw sRGB gamma> "
                                   "--uncorrected <path to reencoded, uncorrected input image>\n"
             "Description: Writes visualizations of the color space to the output image(s)  ."
                          "Also, if a path is provided, writes uncorrected bytes to an unmarked "
                          "png, for comparison with the input image.\n");
     SkCommandLineFlags::Parse(argc, argv);
     const char* input = FLAGS_input[0];
-    const char* gamut_output = FLAGS_gamut_output[0];
-    const char* gamma_output = FLAGS_gamma_output[0];
-    if (!input || !gamut_output || !gamma_output) {
+    const char* output = FLAGS_output[0];
+    if (!input || !output) {
         SkCommandLineFlags::PrintUsage();
         return -1;
     }
@@ -297,81 +468,149 @@ int main(int argc, char** argv) {
         return -1;
     }
 
-    // Load a graph of the CIE XYZ color gamut.
-    SkBitmap gamutCanvasBitmap;
-    if (!GetResourceAsBitmap("gamut.png", &gamutCanvasBitmap)) {
-        SkDebugf("Program failure.\n");
-        return -1;
-    }
-    SkCanvas gamutCanvas(gamutCanvasBitmap);
-
-    SkBitmap gammaCanvasBitmap;
-    gammaCanvasBitmap.allocN32Pixels(500, 500);
-    SkCanvas gammaCanvas(gammaCanvasBitmap);
+    // TODO: command line tweaking of this order
+    int dimOrder[4] = {0, 1, 2, 3};
 
-    // Draw the sRGB gamut if requested.
-    if (FLAGS_sRGB_gamut) {
-        sk_sp<SkColorSpace> sRGBSpace = SkColorSpace::MakeSRGB();
-        const SkMatrix44* mat = as_CSB(sRGBSpace)->toXYZD50();
-        SkASSERT(mat);
-        draw_gamut(&gamutCanvas, *mat, "sRGB", 0xFFFF9394, false);
-    }
+    std::vector<std::string> outputFilenames;
 
-    // Draw the Adobe RGB gamut if requested.
-    if (FLAGS_adobeRGB) {
-        sk_sp<SkColorSpace> adobeRGBSpace =
-                SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kAdobeRGB_Named);
-        const SkMatrix44* mat = as_CSB(adobeRGBSpace)->toXYZD50();
-        SkASSERT(mat);
-        draw_gamut(&gamutCanvas, *mat, "Adobe RGB", 0xFF31a9e1, false);
-    }
+    auto createOutputFilename = [output](const char* category, int index) -> std::string {
+        std::stringstream ss;
+        ss << output << '/' << category << '_' << index << ".png";
+        return ss.str();
+    };
 
-    int gammaCol = 0;
     if (SkColorSpace_Base::Type::kXYZ == as_CSB(colorSpace)->type()) {
+        SkDebugf("XYZ/TRC color space\n");
+
+        // Load a graph of the CIE XYZ color gamut.
+        SkBitmap gamutCanvasBitmap;
+        if (!GetResourceAsBitmap("gamut.png", &gamutCanvasBitmap)) {
+            SkDebugf("Program failure (could not load gamut.png).\n");
+            return -1;
+        }
+        OutputCanvas gamutCanvas(std::move(gamutCanvasBitmap));
+        // Draw the sRGB gamut if requested.
+        if (FLAGS_sRGB_gamut) {
+            sk_sp<SkColorSpace> sRGBSpace = SkColorSpace::MakeSRGB();
+            const SkMatrix44* mat = as_CSB(sRGBSpace)->toXYZD50();
+            SkASSERT(mat);
+            draw_gamut(gamutCanvas.canvas(), *mat, "sRGB", 0xFFFF9394, false);
+        }
+
+        // Draw the Adobe RGB gamut if requested.
+        if (FLAGS_adobeRGB) {
+            sk_sp<SkColorSpace> adobeRGBSpace = SkColorSpace::MakeRGB(
+                    SkColorSpace::kSRGB_RenderTargetGamma, SkColorSpace::kAdobeRGB_Gamut);
+            const SkMatrix44* mat = as_CSB(adobeRGBSpace)->toXYZD50();
+            SkASSERT(mat);
+            draw_gamut(gamutCanvas.canvas(), *mat, "Adobe RGB", 0xFF31a9e1, false);
+        }
         const SkMatrix44* mat = as_CSB(colorSpace)->toXYZD50();
         SkASSERT(mat);
         auto xyz = static_cast<SkColorSpace_XYZ*>(colorSpace.get());
-        draw_gamut(&gamutCanvas, *mat, input, 0xFF000000, true);
+        draw_gamut(gamutCanvas.canvas(), *mat, input, 0xFF000000, true);
+        if (!gamutCanvas.save(&outputFilenames, createOutputFilename("gamut", 0))) {
+            return -1;
+        }
+
+        OutputCanvas gammaCanvas(transparentBitmap(kGammaImageWidth, kGammaImageHeight));
         if (FLAGS_sRGB_gamma) {
-            draw_transfer_fn(&gammaCanvas, kSRGB_SkGammaNamed, nullptr, 0xFFFF9394, gammaCol);
+            draw_transfer_fn(gammaCanvas.canvas(), kSRGB_SkGammaNamed, nullptr, 0xFFFF9394);
+        }
+        draw_transfer_fn(gammaCanvas.canvas(), xyz->gammaNamed(), xyz->gammas(), 0xFF000000);
+        if (!gammaCanvas.save(&outputFilenames, createOutputFilename("gamma", 0))) {
+            return -1;
         }
-        draw_transfer_fn(&gammaCanvas, xyz->gammaNamed(), xyz->gammas(), 0xFF000000, gammaCol++);
     } else {
-        SkDebugf("Color space is defined using an A2B tag.  It cannot be represented by "
-                 "a transfer function and to D50 matrix.\n");
-        return -1;
-    }
+        SkDebugf("A2B color space");
+        SkColorSpace_A2B* a2b = static_cast<SkColorSpace_A2B*>(colorSpace.get());
+        SkDebugf("Conversion type: ");
+        switch (a2b->iccType()) {
+            case SkColorSpace_Base::kRGB_ICCTypeFlag:
+                SkDebugf("RGB");
+                break;
+            case SkColorSpace_Base::kCMYK_ICCTypeFlag:
+                SkDebugf("CMYK");
+                break;
+            case SkColorSpace_Base::kGray_ICCTypeFlag:
+                SkDebugf("Gray");
+                break;
+            default:
+                SkASSERT(false);
+                break;
 
-    // marker to tell the web-tool the names of all images output
-    SkDebugf("=========\n");
-    auto saveCanvasBitmap = [](const SkBitmap& bitmap, const char *fname) {
-        // Finally, encode the result to the output file.
-        sk_sp<SkData> out = sk_tool_utils::EncodeImageToData(bitmap, SkEncodedImageFormat::kPNG,
-                                                             100);
-        if (!out) {
-            SkDebugf("Failed to encode %s output.\n", fname);
-            return false;
         }
-        SkFILEWStream stream(fname);
-        if (!stream.write(out->data(), out->size())) {
-            SkDebugf("Failed to write %s output.\n", fname);
-            return false;
+        SkDebugf(" -> ");
+        switch (a2b->pcs()) {
+            case SkColorSpace_A2B::PCS::kXYZ:
+                SkDebugf("XYZ\n");
+                break;
+            case SkColorSpace_A2B::PCS::kLAB:
+                SkDebugf("LAB\n");
+                break;
+        }
+        int clutCount = 0;
+        int gammaCount = 0;
+        for (int i = 0; i < a2b->count(); ++i) {
+            const SkColorSpace_A2B::Element& e = a2b->element(i);
+            switch (e.type()) {
+                case SkColorSpace_A2B::Element::Type::kGammaNamed: {
+                    OutputCanvas gammaCanvas(transparentBitmap(kGammaImageWidth,
+                                                               kGammaImageHeight));
+                    if (FLAGS_sRGB_gamma) {
+                        draw_transfer_fn(gammaCanvas.canvas(), kSRGB_SkGammaNamed, nullptr,
+                                         0xFFFF9394);
+                    }
+                    draw_transfer_fn(gammaCanvas.canvas(), e.gammaNamed(), nullptr,
+                                     0xFF000000);
+                    if (!gammaCanvas.save(&outputFilenames,
+                                          createOutputFilename("gamma", gammaCount++))) {
+                        return -1;
+                    }
+                }
+                break;
+                case SkColorSpace_A2B::Element::Type::kGammas: {
+                    OutputCanvas gammaCanvas(transparentBitmap(kGammaImageWidth,
+                                                               kGammaImageHeight));
+                    if (FLAGS_sRGB_gamma) {
+                        draw_transfer_fn(gammaCanvas.canvas(), kSRGB_SkGammaNamed, nullptr,
+                                         0xFFFF9394);
+                    }
+                    draw_transfer_fn(gammaCanvas.canvas(), kNonStandard_SkGammaNamed,
+                                     &e.gammas(), 0xFF000000);
+                    if (!gammaCanvas.save(&outputFilenames,
+                                          createOutputFilename("gamma", gammaCount++))) {
+                        return -1;
+                    }
+                }
+                break;
+                case SkColorSpace_A2B::Element::Type::kCLUT: {
+                    const SkColorLookUpTable& clut = e.colorLUT();
+                    const int cutSize = cut_size(clut, dimOrder);
+                    const int clutWidth = clut.inputChannels() >= 3 ? kClutCanvasSize
+                                                                    : 2 * kClutGap + cutSize;
+                    const int clutHeight = clut.inputChannels() >= 4 ? kClutCanvasSize
+                                                                     : 2 * kClutGap + cutSize;
+                    OutputCanvas clutCanvas(transparentBitmap(clutWidth, clutHeight));
+                    draw_clut(clutCanvas.canvas(), e.colorLUT(), dimOrder);
+                    if (!clutCanvas.save(&outputFilenames,
+                                         createOutputFilename("clut", clutCount++))) {
+                        return -1;
+                    }
+                }
+                break;
+                case SkColorSpace_A2B::Element::Type::kMatrix:
+                    dump_matrix(e.matrix());
+                    break;
+            }
         }
-        // record name of canvas
-        SkDebugf("%s\n", fname);
-        return true;
-    };
-
-    // only XYZ images have a gamut visualization since the matrix in A2B is not
-    // a gamut adjustment from RGB->XYZ always (or ever)
-    if (SkColorSpace_Base::Type::kXYZ == as_CSB(colorSpace)->type() &&
-        !saveCanvasBitmap(gamutCanvasBitmap, gamut_output)) {
-        return -1;
-    }
-    if (gammaCol > 0 && !saveCanvasBitmap(gammaCanvasBitmap, gamma_output)) {
-        return -1;
     }
 
+    // marker to tell the web-tool the names of all images output
+    SkDebugf("=========\n");
+    for (const std::string& filename : outputFilenames) {
+        SkDebugf("%s\n", filename.c_str());
+    }
     if (isImage) {
         SkDebugf("%s\n", input);
     }