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);
+ }
+}