SkPdf: smaller color serialization

SkPDFUtils now has a special function (SkPDFUtils::AppendColorComponent)
just for writing out (color/255) as a decimal with three digits of
precision.

SkPDFUnion now has a type to represent a color component.  It holds a
utint_8, but calls into AppendColorComponent to serialize.

Added a unit test that tests all possible input values.
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2151863003

Review-Url: https://codereview.chromium.org/2151863003
diff --git a/bench/PDFBench.cpp b/bench/PDFBench.cpp
index 8f5d2db..19f8e2f 100644
--- a/bench/PDFBench.cpp
+++ b/bench/PDFBench.cpp
@@ -164,6 +164,21 @@
     }
 };
 
+struct PDFColorComponentBench : public Benchmark {
+    bool isSuitableFor(Backend b) override {
+        return b == kNonRendering_Backend;
+    }
+    const char* onGetName() override { return "PDFColorComponent"; }
+    void onDraw(int loops, SkCanvas*) override {
+        char dst[5];
+        while (loops-- > 0) {
+            for (int i = 0; i < 256; ++i) {
+                (void)SkPDFUtils::ColorToDecimal(SkToU8(i), dst);
+            }
+        }
+    }
+};
+
 struct PDFShaderBench : public Benchmark {
     sk_sp<SkShader> fShader;
     const char* onGetName() final { return "PDFShader"; }
@@ -232,6 +247,7 @@
 DEF_BENCH(return new PDFJpegImageBench;)
 DEF_BENCH(return new PDFCompressionBench;)
 DEF_BENCH(return new PDFScalarBench;)
+DEF_BENCH(return new PDFColorComponentBench;)
 DEF_BENCH(return new PDFShaderBench;)
 DEF_BENCH(return new WStreamWriteTextBenchmark;)
 DEF_BENCH(return new WritePDFTextBenchmark;)
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 8e76c44..973ebea 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -51,12 +51,11 @@
 
 static void emit_pdf_color(SkColor color, SkWStream* result) {
     SkASSERT(SkColorGetA(color) == 0xFF);  // We handle alpha elsewhere.
-    SkScalar colorScale = SkScalarInvert(0xFF);
-    SkPDFUtils::AppendScalar(SkColorGetR(color) * colorScale, result);
+    SkPDFUtils::AppendColorComponent(SkColorGetR(color), result);
     result->writeText(" ");
-    SkPDFUtils::AppendScalar(SkColorGetG(color) * colorScale, result);
+    SkPDFUtils::AppendColorComponent(SkColorGetG(color), result);
     result->writeText(" ");
-    SkPDFUtils::AppendScalar(SkColorGetB(color) * colorScale, result);
+    SkPDFUtils::AppendColorComponent(SkColorGetB(color), result);
     result->writeText(" ");
 }
 
diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp
index 942fe65..37df296 100644
--- a/src/pdf/SkPDFShader.cpp
+++ b/src/pdf/SkPDFShader.cpp
@@ -40,6 +40,9 @@
     matrix->postTranslate(pts[0].fX, pts[0].fY);
 }
 
+static const int kColorComponents = 3;
+typedef uint8_t ColorTuple[kColorComponents];
+
 /* Assumes t + startOffset is on the stack and does a linear interpolation on t
    between startOffset and endOffset from prevColor to curColor (for each color
    component), leaving the result in component order on the stack. It assumes
@@ -49,16 +52,16 @@
    @param prevColor[components]  The previous color components.
    @param result                 The result ps function.
  */
-static void interpolateColorCode(SkScalar range, SkScalar* curColor,
-                                 SkScalar* prevColor,
+static void interpolateColorCode(SkScalar range, const ColorTuple& curColor,
+                                 const ColorTuple& prevColor,
                                  SkDynamicMemoryWStream* result) {
     SkASSERT(range != SkIntToScalar(0));
-    static const int kColorComponents = 3;
 
     // Figure out how to scale each color component.
     SkScalar multiplier[kColorComponents];
     for (int i = 0; i < kColorComponents; i++) {
-        multiplier[i] = (curColor[i] - prevColor[i]) / range;
+        static const SkScalar kColorScale = SkScalarInvert(255);
+        multiplier[i] = kColorScale * (curColor[i] - prevColor[i]) / range;
     }
 
     // Calculate when we no longer need to keep a copy of the input parameter t.
@@ -82,7 +85,7 @@
         }
 
         if (multiplier[i] == 0) {
-            SkPDFUtils::AppendScalar(prevColor[i], result);
+            SkPDFUtils::AppendColorComponent(prevColor[i], result);
             result->writeText(" ");
         } else {
             if (multiplier[i] != 1) {
@@ -90,7 +93,7 @@
                 result->writeText(" mul ");
             }
             if (prevColor[i] != 0) {
-                SkPDFUtils::AppendScalar(prevColor[i], result);
+                SkPDFUtils::AppendColorComponent(prevColor[i], result);
                 result->writeText(" add ");
             }
         }
@@ -122,8 +125,6 @@
            }
        }
  */
-static const int kColorComponents = 3;
-typedef SkScalar ColorTuple[kColorComponents];
 static void gradientFunctionCode(const SkShader::GradientInfo& info,
                                  SkDynamicMemoryWStream* result) {
     /* We want to linearly interpolate from the previous color to the next.
@@ -134,20 +135,19 @@
 
     SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(info.fColorCount);
     ColorTuple *colorData = colorDataAlloc.get();
-    const SkScalar scale = SkScalarInvert(SkIntToScalar(255));
     for (int i = 0; i < info.fColorCount; i++) {
-        colorData[i][0] = SkScalarMul(SkColorGetR(info.fColors[i]), scale);
-        colorData[i][1] = SkScalarMul(SkColorGetG(info.fColors[i]), scale);
-        colorData[i][2] = SkScalarMul(SkColorGetB(info.fColors[i]), scale);
+        colorData[i][0] = SkColorGetR(info.fColors[i]);
+        colorData[i][1] = SkColorGetG(info.fColors[i]);
+        colorData[i][2] = SkColorGetB(info.fColors[i]);
     }
 
     // Clamp the initial color.
     result->writeText("dup 0 le {pop ");
-    SkPDFUtils::AppendScalar(colorData[0][0], result);
+    SkPDFUtils::AppendColorComponent(colorData[0][0], result);
     result->writeText(" ");
-    SkPDFUtils::AppendScalar(colorData[0][1], result);
+    SkPDFUtils::AppendColorComponent(colorData[0][1], result);
     result->writeText(" ");
-    SkPDFUtils::AppendScalar(colorData[0][2], result);
+    SkPDFUtils::AppendColorComponent(colorData[0][2], result);
     result->writeText(" }\n");
 
     // The gradient colors.
@@ -173,11 +173,11 @@
 
     // Clamp the final color.
     result->writeText("{pop ");
-    SkPDFUtils::AppendScalar(colorData[info.fColorCount - 1][0], result);
+    SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][0], result);
     result->writeText(" ");
-    SkPDFUtils::AppendScalar(colorData[info.fColorCount - 1][1], result);
+    SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][1], result);
     result->writeText(" ");
-    SkPDFUtils::AppendScalar(colorData[info.fColorCount - 1][2], result);
+    SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][2], result);
 
     for (int i = 0 ; i < gradients + 1; i++) {
         result->writeText("} ifelse\n");
@@ -189,15 +189,15 @@
     auto retval = sk_make_sp<SkPDFDict>();
 
     auto c0 = sk_make_sp<SkPDFArray>();
-    c0->appendScalar(color1[0]);
-    c0->appendScalar(color1[1]);
-    c0->appendScalar(color1[2]);
+    c0->appendColorComponent(color1[0]);
+    c0->appendColorComponent(color1[1]);
+    c0->appendColorComponent(color1[2]);
     retval->insertObject("C0", std::move(c0));
 
     auto c1 = sk_make_sp<SkPDFArray>();
-    c1->appendScalar(color2[0]);
-    c1->appendScalar(color2[1]);
-    c1->appendScalar(color2[2]);
+    c1->appendColorComponent(color2[0]);
+    c1->appendColorComponent(color2[1]);
+    c1->appendColorComponent(color2[2]);
     retval->insertObject("C1", std::move(c1));
 
     auto domain = sk_make_sp<SkPDFArray>();
@@ -248,11 +248,10 @@
 
     SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(colorCount);
     ColorTuple *colorData = colorDataAlloc.get();
-    const SkScalar scale = SkScalarInvert(SkIntToScalar(255));
     for (int i = 0; i < colorCount; i++) {
-        colorData[i][0] = SkScalarMul(SkColorGetR(colors[i]), scale);
-        colorData[i][1] = SkScalarMul(SkColorGetG(colors[i]), scale);
-        colorData[i][2] = SkScalarMul(SkColorGetB(colors[i]), scale);
+        colorData[i][0] = SkColorGetR(colors[i]);
+        colorData[i][1] = SkColorGetG(colors[i]);
+        colorData[i][2] = SkColorGetB(colors[i]);
     }
 
     // no need for a stitch function if there are only 2 stops.
diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp
index f9ab5e4..8c59a4a 100644
--- a/src/pdf/SkPDFTypes.cpp
+++ b/src/pdf/SkPDFTypes.cpp
@@ -118,6 +118,9 @@
         case Type::kInt:
             stream->writeDecAsText(fIntValue);
             return;
+        case Type::kColorComponent:
+            SkPDFUtils::AppendColorComponent(SkToU8(fIntValue), stream);
+            return;
         case Type::kBool:
             stream->writeText(fBoolValue ? "true" : "false");
             return;
@@ -159,6 +162,7 @@
                               const SkPDFSubstituteMap& substituteMap) const {
     switch (fType) {
         case Type::kInt:
+        case Type::kColorComponent:
         case Type::kBool:
         case Type::kScalar:
         case Type::kName:
@@ -185,6 +189,12 @@
     return u;
 }
 
+SkPDFUnion SkPDFUnion::ColorComponent(uint8_t value) {
+    SkPDFUnion u(Type::kColorComponent);
+    u.fIntValue = value;
+    return u;
+}
+
 SkPDFUnion SkPDFUnion::Bool(bool value) {
     SkPDFUnion u(Type::kBool);
     u.fBoolValue = value;
@@ -300,6 +310,10 @@
     this->append(SkPDFUnion::Int(value));
 }
 
+void SkPDFArray::appendColorComponent(uint8_t value) {
+    this->append(SkPDFUnion::ColorComponent(value));
+}
+
 void SkPDFArray::appendBool(bool value) {
     this->append(SkPDFUnion::Bool(value));
 }
diff --git a/src/pdf/SkPDFTypes.h b/src/pdf/SkPDFTypes.h
index cff60e5..5e4de3a 100644
--- a/src/pdf/SkPDFTypes.h
+++ b/src/pdf/SkPDFTypes.h
@@ -90,6 +90,8 @@
 
     static SkPDFUnion Scalar(SkScalar);
 
+    static SkPDFUnion ColorComponent(uint8_t);
+
     /** These two functions do NOT take ownership of char*, and do NOT
         copy the string.  Suitable for passing in static const
         strings. For example:
@@ -139,6 +141,7 @@
             kDestroyed object. */
         kDestroyed = 0,
         kInt,
+        kColorComponent,
         kBool,
         kScalar,
         kName,
@@ -212,6 +215,7 @@
      *  @param value The value to add to the array.
      */
     void appendInt(int32_t);
+    void appendColorComponent(uint8_t);
     void appendBool(bool);
     void appendScalar(SkScalar);
     void appendName(const char[]);
diff --git a/src/pdf/SkPDFUtils.cpp b/src/pdf/SkPDFUtils.cpp
index b8d6509..9e4ac51 100644
--- a/src/pdf/SkPDFUtils.cpp
+++ b/src/pdf/SkPDFUtils.cpp
@@ -7,6 +7,7 @@
 
 
 #include "SkData.h"
+#include "SkFixed.h"
 #include "SkGeometry.h"
 #include "SkPDFResourceDict.h"
 #include "SkPDFUtils.h"
@@ -251,6 +252,29 @@
     content->writeText(" scn\n");
 }
 
+size_t SkPDFUtils::ColorToDecimal(uint8_t value, char result[5]) {
+    if (value == 255 || value == 0) {
+        result[0] = value ? '1' : '0';
+        result[1] = '\0';
+        return 1;
+    }
+    // int x = 0.5 + (1000.0 / 255.0) * value;
+    int x = SkFixedRoundToInt((SK_Fixed1 * 1000 / 255) * value);
+    result[0] = '.';
+    for (int i = 3; i > 0; --i) {
+        result[i] = '0' + x % 10;
+        x /= 10;
+    }
+    int j;
+    for (j = 3; j > 1; --j) {
+        if (result[j] != '0') {
+            break;
+        }
+    }
+    result[j + 1] = '\0';
+    return j + 1;
+}
+
 void SkPDFUtils::AppendScalar(SkScalar value, SkWStream* stream) {
     char result[kMaximumFloatDecimalLength];
     size_t len = SkPDFUtils::FloatToDecimal(SkScalarToFloat(value), result);
diff --git a/src/pdf/SkPDFUtils.h b/src/pdf/SkPDFUtils.h
index 3ddd3d0..a9194f2 100644
--- a/src/pdf/SkPDFUtils.h
+++ b/src/pdf/SkPDFUtils.h
@@ -57,6 +57,15 @@
 void ApplyGraphicState(int objectIndex, SkWStream* content);
 void ApplyPattern(int objectIndex, SkWStream* content);
 
+// Converts (value / 255.0) with three significant digits of accuracy.
+// Writes value as string into result.  Returns strlen() of result.
+size_t ColorToDecimal(uint8_t value, char result[5]);
+inline void AppendColorComponent(uint8_t value, SkWStream* wStream) {
+    char buffer[5];
+    size_t len = SkPDFUtils::ColorToDecimal(value, buffer);
+    wStream->write(buffer, len);
+}
+
 // 3 = '-', '.', and '\0' characters.
 // 9 = number of significant digits
 // abs(FLT_MIN_10_EXP) = number of zeros in FLT_MIN
diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp
index 9e2a89e..d816b60 100644
--- a/tests/PDFPrimitivesTest.cpp
+++ b/tests/PDFPrimitivesTest.cpp
@@ -491,3 +491,16 @@
         check_pdf_scalar_serialization(reporter, inputFloat);
     }
 }
+
+// Test SkPDFUtils:: for accuracy.
+DEF_TEST(PDFPrimitives_Color, reporter) {
+    char buffer[5];
+    for (int i = 0; i < 256; ++i) {
+        size_t len = SkPDFUtils::ColorToDecimal(i, buffer);
+        REPORTER_ASSERT(reporter, len == strlen(buffer));
+        float f;
+        REPORTER_ASSERT(reporter, 1 == sscanf(buffer, "%f", &f));
+        int roundTrip = (int)(0.5 + f * 255);
+        REPORTER_ASSERT(reporter, roundTrip == i);
+    }
+}