aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/SkMaskGamma.cpp91
-rw-r--r--src/core/SkMaskGamma.h211
-rw-r--r--src/core/SkPaint.cpp197
-rw-r--r--src/core/SkScalerContext.cpp167
4 files changed, 506 insertions, 160 deletions
diff --git a/src/core/SkMaskGamma.cpp b/src/core/SkMaskGamma.cpp
new file mode 100644
index 0000000000..47903fbdbb
--- /dev/null
+++ b/src/core/SkMaskGamma.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+
+#include "SkColor.h"
+#include "SkFloatingPoint.h"
+#include "SkMaskGamma.h"
+
+SkScalar SkSRGBLuminance::toLuma(SkScalar luminance) const {
+ //The magic numbers are derived from the sRGB specification.
+ //See http://www.color.org/chardata/rgb/srgb.xalter .
+ if (luminance <= SkFloatToScalar(0.04045f)) {
+ return luminance / SkFloatToScalar(12.92f);
+ }
+ return SkScalarPow((luminance + SkFloatToScalar(0.055f)) / SkFloatToScalar(1.055f),
+ SkFloatToScalar(2.4f));
+}
+
+SkScalar SkSRGBLuminance::fromLuma(SkScalar luma) const {
+ //The magic numbers are derived from the sRGB specification.
+ //See http://www.color.org/chardata/rgb/srgb.xalter .
+ if (luma <= SkFloatToScalar(0.0031308f)) {
+ return luma * SkFloatToScalar(12.92f);
+ }
+ return SkFloatToScalar(1.055f) * SkScalarPow(luma, SkScalarInvert(SkFloatToScalar(2.4f)))
+ - SkFloatToScalar(0.055f);
+}
+
+SkGammaLuminance::SkGammaLuminance(SkScalar gamma)
+ : fGamma(gamma)
+ , fGammaInverse(SkScalarInvert(gamma)) {
+}
+
+float SkGammaLuminance::toLuma(SkScalar luminance) const {
+ return SkScalarPow(luminance, fGamma);
+}
+
+float SkGammaLuminance::fromLuma(SkScalar luma) const {
+ return SkScalarPow(luma, fGammaInverse);
+}
+
+static float apply_contrast(float srca, float contrast) {
+ return srca + ((1.0f - srca) * contrast * srca);
+}
+
+void SkTMaskGamma_build_correcting_lut(uint8_t table[256], U8CPU srcI, SkScalar contrast,
+ const SkColorSpaceLuminance& srcConvert,
+ const SkColorSpaceLuminance& dstConvert) {
+ const float src = (float)srcI / 255.0f;
+ const float linSrc = srcConvert.toLuma(src);
+ //Guess at the dst.
+ const float linDst = 1.0f - linSrc;
+ const float dst = dstConvert.fromLuma(linDst);
+
+ //Contrast value tapers off to 0 as the src luminance becomes white
+ const float adjustedContrast = SkScalarToFloat(contrast) * linDst;
+ const float step = 1.0f / 255.0f;
+
+ //Remove discontinuity and instability when src is close to dst.
+ //The value 1/256 is arbitrary and appears to contain the instability.
+ if (fabs(src - dst) < (1.0f / 256.0f)) {
+ float rawSrca = 0.0f;
+ for (int i = 0; i < 256; ++i, rawSrca += step) {
+ float srca = apply_contrast(rawSrca, adjustedContrast);
+ table[i] = SkToU8(sk_float_round2int(255.0f * srca));
+ }
+ } else {
+ float rawSrca = 0.0f;
+ for (int i = 0; i < 256; ++i, rawSrca += step) {
+ float srca = apply_contrast(rawSrca, adjustedContrast);
+ SkASSERT(srca <= 1.0f);
+ float dsta = 1.0f - srca;
+
+ //Calculate the output we want.
+ float linOut = (linSrc * srca + dsta * linDst);
+ SkASSERT(linOut <= 1.0f);
+ float out = dstConvert.fromLuma(linOut);
+
+ //Undo what the blit blend will do.
+ float result = (out - dst) / (src - dst);
+ SkASSERT(sk_float_round2int(255.0f * result) <= 255);
+
+ table[i] = SkToU8(sk_float_round2int(255.0f * result));
+ }
+ }
+}
diff --git a/src/core/SkMaskGamma.h b/src/core/SkMaskGamma.h
new file mode 100644
index 0000000000..e8fa703f36
--- /dev/null
+++ b/src/core/SkMaskGamma.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkMaskGamma_DEFINED
+#define SkMaskGamma_DEFINED
+
+#include "SkTypes.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkRefCnt.h"
+
+/**
+ * SkColorSpaceLuminance is used to convert luminances to and from linear and
+ * perceptual color spaces.
+ *
+ * Luma is used to specify a linear luminance value [0.0, 1.0].
+ * Luminance is used to specify a luminance value in an arbitrary color space [0.0, 1.0].
+ */
+class SkColorSpaceLuminance : SkNoncopyable {
+public:
+ /** Converts a color component luminance in the color space to a linear luma. */
+ virtual SkScalar toLuma(SkScalar luminance) const = 0;
+ /** Converts a linear luma to a color component luminance in the color space. */
+ virtual SkScalar fromLuma(SkScalar luma) const = 0;
+
+ /** Converts a color to a luminance value. */
+ U8CPU computeLuminance(SkColor c) const {
+ SkScalar r = toLuma(SkIntToScalar(SkColorGetR(c)) / 255);
+ SkScalar g = toLuma(SkIntToScalar(SkColorGetG(c)) / 255);
+ SkScalar b = toLuma(SkIntToScalar(SkColorGetB(c)) / 255);
+ SkScalar luma = r * SkFloatToScalar(SK_LUM_COEFF_R) +
+ g * SkFloatToScalar(SK_LUM_COEFF_G) +
+ b * SkFloatToScalar(SK_LUM_COEFF_B);
+ SkASSERT(luma <= SK_Scalar1);
+ return SkScalarRoundToInt(fromLuma(luma) * 255);
+ }
+};
+
+class SkSRGBLuminance : public SkColorSpaceLuminance {
+public:
+ SkScalar toLuma(SkScalar luminance) const SK_OVERRIDE;
+ SkScalar fromLuma(SkScalar luma) const SK_OVERRIDE;
+};
+
+class SkGammaLuminance : public SkColorSpaceLuminance {
+public:
+ SkGammaLuminance(SkScalar gamma);
+ SkScalar toLuma(SkScalar luminance) const SK_OVERRIDE;
+ SkScalar fromLuma(SkScalar luma) const SK_OVERRIDE;
+private:
+ SkScalar fGamma;
+ SkScalar fGammaInverse;
+};
+
+///@{
+/**
+ * Scales base <= 2^N-1 to 2^8-1
+ * @param N [1, 8] the number of bits used by base.
+ * @param base the number to be scaled to [0, 255].
+ */
+template<U8CPU N> static inline U8CPU sk_t_scale255(U8CPU base) {
+ base <<= (8 - N);
+ U8CPU lum = base;
+ for (unsigned int i = N; i < 8; i += N) {
+ lum |= base >> i;
+ }
+ return lum;
+}
+template<> /*static*/ inline U8CPU sk_t_scale255<1>(U8CPU base) {
+ return base * 0xFF;
+}
+template<> /*static*/ inline U8CPU sk_t_scale255<2>(U8CPU base) {
+ return base * 0x55;
+}
+template<> /*static*/ inline U8CPU sk_t_scale255<4>(U8CPU base) {
+ return base * 0x11;
+}
+template<> /*static*/ inline U8CPU sk_t_scale255<8>(U8CPU base) {
+ return base;
+}
+///@}
+
+template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> class SkTMaskPreBlend;
+
+void SkTMaskGamma_build_correcting_lut(uint8_t table[256], U8CPU srcI, SkScalar contrast,
+ const SkColorSpaceLuminance& srcConvert,
+ const SkColorSpaceLuminance& dstConvert);
+
+/**
+ * A regular mask contains linear alpha values. A gamma correcting mask
+ * contains non-linear alpha values in an attempt to create gamma correct blits
+ * in the presence of a gamma incorrect (linear) blend in the blitter.
+ *
+ * SkMaskGamma creates and maintains tables which convert linear alpha values
+ * to gamma correcting alpha values.
+ * @param R The number of luminance bits to use [1, 8] from the red channel.
+ * @param G The number of luminance bits to use [1, 8] from the green channel.
+ * @param B The number of luminance bits to use [1, 8] from the blue channel.
+ */
+template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> class SkTMaskGamma : public SkRefCnt {
+public:
+ /**
+ * Creates tables to convert linear alpha values to gamma correcting alpha
+ * values.
+ *
+ * @param contrast A value in the range [0.0, 1.0] which indicates the
+ * amount of artificial contrast to add.
+ * @param paint The color space in which the paint color was chosen.
+ * @param device The color space of the target device.
+ */
+ SkTMaskGamma(SkScalar contrast,
+ const SkColorSpaceLuminance& paint,
+ const SkColorSpaceLuminance& device) {
+ for (U8CPU i = 0; i < (1 << kLuminanceBits_Max); ++i) {
+ U8CPU lum = sk_t_scale255<kLuminanceBits_Max>(i);
+ SkTMaskGamma_build_correcting_lut(fGammaTables[i], lum, contrast, paint, device);
+ }
+ }
+
+ /** Given a color, returns the closest cannonical color. */
+ SkColor cannonicalColor(SkColor color) {
+ return SkColorSetRGB(
+ sk_t_scale255<kLuminanceBits_R>(SkColorGetR(color) >> (8 - kLuminanceBits_R)),
+ sk_t_scale255<kLuminanceBits_G>(SkColorGetG(color) >> (8 - kLuminanceBits_G)),
+ sk_t_scale255<kLuminanceBits_B>(SkColorGetB(color) >> (8 - kLuminanceBits_B)));
+ }
+
+ /** The type of the mask pre-blend which will be returned from preBlend(SkColor). */
+ typedef SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS> PreBlend;
+
+ /**
+ * Provides access to the tables appropriate for converting linear alpha
+ * values into gamma correcting alpha values when drawing the given color
+ * through the mask. The destination color will be approximated.
+ */
+ PreBlend preBlend(SkColor color);
+
+private:
+ enum LuminanceBits {
+ kLuminanceBits_R = R_LUM_BITS,
+ kLuminanceBits_G = G_LUM_BITS,
+ kLuminanceBits_B = B_LUM_BITS,
+ kLuminanceBits_Max = B_LUM_BITS > (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS)
+ ? B_LUM_BITS
+ : (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS)
+ };
+ uint8_t fGammaTables[1 << kLuminanceBits_Max][256];
+};
+
+/**
+ * SkTMaskPreBlend is a tear-off of SkTMaskGamma. It provides the tables to
+ * convert a linear alpha value for a given channel to a gamma correcting alpha
+ * value for that channel. This class is immutable.
+ */
+template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS> class SkTMaskPreBlend {
+private:
+ SkTMaskPreBlend(SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>* parent,
+ const uint8_t* r,
+ const uint8_t* g,
+ const uint8_t* b)
+ : fParent(parent), fR(r), fG(g), fB(b) {
+ parent->ref();
+ }
+ SkAutoTUnref<SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS> > fParent;
+ friend class SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>;
+public:
+ /**
+ * This copy contructor exists for correctness, but should never be called
+ * when return value optimization is enabled.
+ */
+ SkTMaskPreBlend(const SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>& that)
+ : fParent(that.fParent.get()), fR(that.fR), fG(that.fG), fB(that.fB) {
+ fParent.get()->ref();
+ }
+ ~SkTMaskPreBlend() { }
+ const uint8_t* fR;
+ const uint8_t* fG;
+ const uint8_t* fB;
+};
+
+template <int R_LUM_BITS, int G_LUM_BITS, int B_LUM_BITS>
+SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>
+SkTMaskGamma<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>::preBlend(SkColor color) {
+ return SkTMaskPreBlend<R_LUM_BITS, G_LUM_BITS, B_LUM_BITS>(
+ this,
+ fGammaTables[SkColorGetR(color) >> (8 - kLuminanceBits_Max)],
+ fGammaTables[SkColorGetG(color) >> (8 - kLuminanceBits_Max)],
+ fGammaTables[SkColorGetB(color) >> (8 - kLuminanceBits_Max)]);
+}
+
+///@{
+/**
+ * If APPLY_LUT is false, returns component unchanged.
+ * If APPLY_LUT is true, returns lut[component].
+ * @param APPLY_LUT whether or not the look-up table should be applied to component.
+ * @component the initial component.
+ * @lut a look-up table which transforms the component.
+ */
+template<bool APPLY_LUT> static inline U8CPU sk_apply_lut_if(U8CPU component, const uint8_t*) {
+ return component;
+}
+template<> /*static*/ inline U8CPU sk_apply_lut_if<true>(U8CPU component, const uint8_t* lut) {
+ return lut[component];
+}
+///@}
+
+#endif
diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp
index f240737f7e..04a8f5a756 100644
--- a/src/core/SkPaint.cpp
+++ b/src/core/SkPaint.cpp
@@ -12,6 +12,7 @@
#include "SkFontHost.h"
#include "SkImageFilter.h"
#include "SkMaskFilter.h"
+#include "SkMaskGamma.h"
#include "SkPathEffect.h"
#include "SkRasterizer.h"
#include "SkShader.h"
@@ -1432,7 +1433,6 @@ static bool justAColor(const SkPaint& paint, SkColor* color) {
return true;
}
-#ifdef SK_USE_COLOR_LUMINANCE
static SkColor computeLuminanceColor(const SkPaint& paint) {
SkColor c;
if (!justAColor(paint, &c)) {
@@ -1443,53 +1443,6 @@ static SkColor computeLuminanceColor(const SkPaint& paint) {
#define assert_byte(x) SkASSERT(0 == ((x) >> 8))
-static U8CPU reduce_lumbits(U8CPU x) {
- static const uint8_t gReduceBits[] = {
- 0x0, 0x55, 0xAA, 0xFF
- };
- assert_byte(x);
- return gReduceBits[x >> 6];
-}
-
-static unsigned computeLuminance(SkColor c) {
- int r = SkColorGetR(c);
- int g = SkColorGetG(c);
- int b = SkColorGetB(c);
- // compute luminance
- // R=0.2126 G=0.7152 B=0.0722
- // scaling by 127 yields 27, 92, 9
- int luminance = r * 27 + g * 92 + b * 9;
- luminance >>= 7;
- assert_byte(luminance);
- return luminance;
-}
-
-#else
-// returns 0..kLuminance_Max
-static unsigned computeLuminance(const SkPaint& paint) {
- SkColor c;
- if (justAColor(paint, &c)) {
- int r = SkColorGetR(c);
- int g = SkColorGetG(c);
- int b = SkColorGetB(c);
- // compute luminance
- // R=0.2126 G=0.7152 B=0.0722
- // scaling by 127 yields 27, 92, 9
-#if 1
- int luminance = r * 27 + g * 92 + b * 9;
- luminance >>= 15 - SkScalerContext::kLuminance_Bits;
-#else
- int luminance = r * 2 + g * 5 + b * 1;
- luminance >>= 11 - SkScalerContext::kLuminance_Bits;
-#endif
- SkASSERT(luminance <= SkScalerContext::kLuminance_Max);
- return luminance;
- }
- // if we're not a single color, return the middle of the luminance range
- return SkScalerContext::kLuminance_Max >> 1;
-}
-#endif
-
// Beyond this size, LCD doesn't appreciably improve quality, but it always
// cost more RAM and draws slower, so we set a cap.
#ifndef SK_MAX_SIZE_FOR_LCDTEXT
@@ -1518,6 +1471,20 @@ static SkScalar sk_relax(SkScalar x) {
#endif
}
+//#define SK_GAMMA_SRGB
+#ifndef SK_GAMMA_CONTRAST
+ /**
+ * A value of 0.5 for SK_GAMMA_CONTRAST appears to be a good compromise.
+ * With lower values small text appears washed out (though correctly so).
+ * With higher values lcd fringing is worse and the smoothing effect of
+ * partial coverage is diminished.
+ */
+ #define SK_GAMMA_CONTRAST (0.5f)
+#endif
+#ifndef SK_GAMMA_EXPONENT
+ #define SK_GAMMA_EXPONENT (2.2f)
+#endif
+
void SkScalerContext::MakeRec(const SkPaint& paint,
const SkMatrix* deviceMatrix, Rec* rec) {
SkASSERT(deviceMatrix == NULL || !deviceMatrix->hasPerspective());
@@ -1619,12 +1586,17 @@ void SkScalerContext::MakeRec(const SkPaint& paint,
// these modify fFlags, so do them after assigning fFlags
rec->setHinting(computeHinting(paint));
-#ifdef SK_USE_COLOR_LUMINANCE
+
rec->setLuminanceColor(computeLuminanceColor(paint));
+#ifdef SK_GAMMA_SRGB
+ rec->setDeviceGamma(0);
+ rec->setPaintGamma(0);
#else
- rec->setLuminanceBits(computeLuminance(paint));
+ rec->setDeviceGamma(SkFloatToScalar(SK_GAMMA_EXPONENT));
+ rec->setPaintGamma(SkFloatToScalar(SK_GAMMA_EXPONENT));
#endif
-
+ rec->setContrast(SkFloatToScalar(SK_GAMMA_CONTRAST));
+
/* Allow the fonthost to modify our rec before we use it as a key into the
cache. This way if we're asking for something that they will ignore,
they can modify our rec up front, so we don't create duplicate cache
@@ -1636,46 +1608,111 @@ void SkScalerContext::MakeRec(const SkPaint& paint,
}
/**
- * We ensure that the rec is self-consistent and efficient (where possible)
+ * In order to call cachedDeviceLuminance, cachedPaintLuminance, or
+ * cachedMaskGamma the caller must hold the gMaskGammaCacheMutex and continue
+ * to hold it until the returned pointer is refed or forgotten.
*/
-void SkScalerContext::PostMakeRec(SkScalerContext::Rec* rec) {
+SK_DECLARE_STATIC_MUTEX(gMaskGammaCacheMutex);
+/**
+ * The caller must hold the gMaskGammaCacheMutex and continue to hold it until
+ * the returned SkColorSpaceLuminance pointer is refed or forgotten.
+ */
+static SkColorSpaceLuminance* cachedDeviceLuminance(SkScalar gammaExponent) {
+ static SkColorSpaceLuminance* gDeviceLuminance = NULL;
+ static SkScalar gGammaExponent = SK_ScalarMin;
+ if (gGammaExponent != gammaExponent) {
+ if (0 == gammaExponent) {
+ gDeviceLuminance = SkNEW(SkSRGBLuminance);
+ } else {
+ gDeviceLuminance = SkNEW_ARGS(SkGammaLuminance, (gammaExponent));
+ }
+ gGammaExponent = gammaExponent;
+ }
+ return gDeviceLuminance;
+}
+
+/**
+ * The caller must hold the gMaskGammaCacheMutex and continue to hold it until
+ * the returned SkColorSpaceLuminance pointer is refed or forgotten.
+ */
+static SkColorSpaceLuminance* cachedPaintLuminance(SkScalar gammaExponent) {
+ static SkColorSpaceLuminance* gPaintLuminance = NULL;
+ static SkScalar gGammaExponent = SK_ScalarMin;
+ if (gGammaExponent != gammaExponent) {
+ if (0 == gammaExponent) {
+ gPaintLuminance = SkNEW(SkSRGBLuminance);
+ } else {
+ gPaintLuminance = SkNEW_ARGS(SkGammaLuminance, (gammaExponent));
+ }
+ gGammaExponent = gammaExponent;
+ }
+ return gPaintLuminance;
+}
+
+/**
+ * The caller must hold the gMaskGammaCacheMutex and continue to hold it until
+ * the returned SkMaskGamma pointer is refed or forgotten.
+ */
+static SkMaskGamma* cachedMaskGamma(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma) {
+ static SkMaskGamma* gMaskGamma = NULL;
+ static SkScalar gContrast = SK_ScalarMin;
+ static SkScalar gPaintGamma = SK_ScalarMin;
+ static SkScalar gDeviceGamma = SK_ScalarMin;
+ if (gContrast != contrast || gPaintGamma != paintGamma || gDeviceGamma != deviceGamma) {
+ SkSafeUnref(gMaskGamma);
+ SkColorSpaceLuminance* paintLuminance = cachedPaintLuminance(paintGamma);
+ SkColorSpaceLuminance* deviceLuminance = cachedDeviceLuminance(deviceGamma);
+ gMaskGamma = SkNEW_ARGS(SkMaskGamma, (contrast, *paintLuminance, *deviceLuminance));
+ gContrast = contrast;
+ gPaintGamma = paintGamma;
+ gDeviceGamma = deviceGamma;
+ }
+ return gMaskGamma;
+}
+
+/**
+ * We ensure that the rec is self-consistent and efficient (where possible)
+ */
+void SkScalerContext::PostMakeRec(const SkPaint& paint, SkScalerContext::Rec* rec) {
/**
* If we're asking for A8, we force the colorlum to be gray, since that
- * that limits the number of unique entries, and the scaler will only
- * look at the lum of one of them.
+ * limits the number of unique entries, and the scaler will only look at
+ * the lum of one of them.
*/
switch (rec->fMaskFormat) {
case SkMask::kLCD16_Format:
case SkMask::kLCD32_Format: {
-#ifdef SK_USE_COLOR_LUMINANCE
// filter down the luminance color to a finite number of bits
- SkColor c = rec->getLuminanceColor();
- c = SkColorSetRGB(reduce_lumbits(SkColorGetR(c)),
- reduce_lumbits(SkColorGetG(c)),
- reduce_lumbits(SkColorGetB(c)));
- rec->setLuminanceColor(c);
-#endif
+ SkColor color = rec->getLuminanceColor();
+ SkAutoMutexAcquire ama(gMaskGammaCacheMutex);
+ SkMaskGamma* maskGamma = cachedMaskGamma(rec->getContrast(),
+ rec->getPaintGamma(),
+ rec->getDeviceGamma());
+ rec->setLuminanceColor(maskGamma->cannonicalColor(color));
break;
}
case SkMask::kA8_Format: {
-#ifdef SK_USE_COLOR_LUMINANCE
// filter down the luminance to a single component, since A8 can't
// use per-component information
- unsigned lum = computeLuminance(rec->getLuminanceColor());
+
+ SkColor color = rec->getLuminanceColor();
+ SkAutoMutexAcquire ama(gMaskGammaCacheMutex);
+ U8CPU lum = cachedPaintLuminance(rec->getPaintGamma())->computeLuminance(color);
+ // HACK: Prevents green from being pre-blended as white.
+ lum -= ((255 - lum) * lum) / 255;
+
// reduce to our finite number of bits
- lum = reduce_lumbits(lum);
- rec->setLuminanceColor(SkColorSetRGB(lum, lum, lum));
-#endif
+ SkMaskGamma* maskGamma = cachedMaskGamma(rec->getContrast(),
+ rec->getPaintGamma(),
+ rec->getDeviceGamma());
+ color = SkColorSetRGB(lum, lum, lum);
+ rec->setLuminanceColor(maskGamma->cannonicalColor(color));
break;
}
case SkMask::kBW_Format:
// No need to differentiate gamma if we're BW
-#ifdef SK_USE_COLOR_LUMINANCE
rec->setLuminanceColor(0);
-#else
- rec->setLuminanceBits(0);
-#endif
break;
}
}
@@ -1700,11 +1737,7 @@ void SkPaint::descriptorProc(const SkMatrix* deviceMatrix,
SkScalerContext::MakeRec(*this, deviceMatrix, &rec);
if (ignoreGamma) {
-#ifdef SK_USE_COLOR_LUMINANCE
rec.setLuminanceColor(0);
-#else
- rec.setLuminanceBits(0);
-#endif
}
size_t descSize = sizeof(rec);
@@ -1739,7 +1772,7 @@ void SkPaint::descriptorProc(const SkMatrix* deviceMatrix,
///////////////////////////////////////////////////////////////////////////
// Now that we're done tweaking the rec, call the PostMakeRec cleanup
- SkScalerContext::PostMakeRec(&rec);
+ SkScalerContext::PostMakeRec(*this, &rec);
descSize += SkDescriptor::ComputeOverhead(entryCount);
@@ -1814,6 +1847,18 @@ SkGlyphCache* SkPaint::detachCache(const SkMatrix* deviceMatrix) const {
return cache;
}
+/**
+ * Expands fDeviceGamma, fPaintGamma, fContrast, and fLumBits into a mask pre-blend.
+ */
+//static
+SkMaskGamma::PreBlend SkScalerContext::GetMaskPreBlend(const SkScalerContext::Rec& rec) {
+ SkAutoMutexAcquire ama(gMaskGammaCacheMutex);
+ SkMaskGamma* maskGamma = cachedMaskGamma(rec.getContrast(),
+ rec.getPaintGamma(),
+ rec.getDeviceGamma());
+ return maskGamma->preBlend(rec.getLuminanceColor());
+}
+
///////////////////////////////////////////////////////////////////////////////
#include "SkStream.h"
diff --git a/src/core/SkScalerContext.cpp b/src/core/SkScalerContext.cpp
index 7c26921bfb..9d99713c40 100644
--- a/src/core/SkScalerContext.cpp
+++ b/src/core/SkScalerContext.cpp
@@ -14,6 +14,7 @@
#include "SkFontHost.h"
#include "SkGlyph.h"
#include "SkMaskFilter.h"
+#include "SkMaskGamma.h"
#include "SkOrderedReadBuffer.h"
#include "SkPathEffect.h"
#include "SkRasterizer.h"
@@ -74,16 +75,16 @@ static SkFlattenable* load_flattenable(const SkDescriptor* desc, uint32_t tag) {
}
SkScalerContext::SkScalerContext(const SkDescriptor* desc)
- : fPathEffect(NULL), fMaskFilter(NULL)
+ : fRec(*static_cast<const Rec*>(desc->findEntry(kRec_SkDescriptorTag, NULL)))
+ , fBaseGlyphCount(0)
+ , fPathEffect(static_cast<SkPathEffect*>(load_flattenable(desc, kPathEffect_SkDescriptorTag)))
+ , fMaskFilter(static_cast<SkMaskFilter*>(load_flattenable(desc, kMaskFilter_SkDescriptorTag)))
+ , fRasterizer(static_cast<SkRasterizer*>(load_flattenable(desc, kRasterizer_SkDescriptorTag)))
+ // initialize based on our settings. subclasses can also force this
+ , fGenerateImageFromPath(fRec.fFrameWidth > 0 || fPathEffect != NULL || fRasterizer != NULL)
+ , fNextContext(NULL)
+ , fMaskPreBlend(SkScalerContext::GetMaskPreBlend(fRec))
{
- fBaseGlyphCount = 0;
- fNextContext = NULL;
-
- const Rec* rec = (const Rec*)desc->findEntry(kRec_SkDescriptorTag, NULL);
- SkASSERT(rec);
-
- fRec = *rec;
-
#ifdef DUMP_REC
desc->assertChecksum();
SkDebugf("SkScalarContext checksum %x count %d length %d\n",
@@ -98,14 +99,6 @@ SkScalerContext::SkScalerContext(const SkDescriptor* desc)
desc->findEntry(kPathEffect_SkDescriptorTag, NULL),
desc->findEntry(kMaskFilter_SkDescriptorTag, NULL));
#endif
-
- fPathEffect = (SkPathEffect*)load_flattenable(desc, kPathEffect_SkDescriptorTag);
- fMaskFilter = (SkMaskFilter*)load_flattenable(desc, kMaskFilter_SkDescriptorTag);
- fRasterizer = (SkRasterizer*)load_flattenable(desc, kRasterizer_SkDescriptorTag);
-
- // initialize based on our settings. subclasses can also force this
- fGenerateImageFromPath = fRec.fFrameWidth > 0 || fPathEffect != NULL ||
- fRasterizer != NULL;
}
SkScalerContext::~SkScalerContext() {
@@ -342,77 +335,39 @@ SK_ERROR:
glyph->fMaskFormat = fRec.fMaskFormat;
}
-#if 0 // UNUSED
-static bool isLCD(const SkScalerContext::Rec& rec) {
- return SkMask::kLCD16_Format == rec.fMaskFormat ||
- SkMask::kLCD32_Format == rec.fMaskFormat;
-}
-#endif
-
-#if 0 // UNUSED
-static uint16_t a8_to_rgb565(unsigned a8) {
- return SkPackRGB16(a8 >> 3, a8 >> 2, a8 >> 3);
-}
-
-static void copyToLCD16(const SkBitmap& src, const SkMask& dst) {
+template<bool APPLY_PREBLEND>
+static void pack3xHToLCD16(const SkBitmap& src, const SkMask& dst, SkMaskGamma::PreBlend* maskPreBlend) {
SkASSERT(SkBitmap::kA8_Config == src.config());
SkASSERT(SkMask::kLCD16_Format == dst.fFormat);
-
+
const int width = dst.fBounds.width();
const int height = dst.fBounds.height();
- const uint8_t* srcP = src.getAddr8(0, 0);
- size_t srcRB = src.rowBytes();
uint16_t* dstP = (uint16_t*)dst.fImage;
size_t dstRB = dst.fRowBytes;
- for (int y = 0; y < height; ++y) {
- for (int x = 0; x < width; ++x) {
- dstP[x] = a8_to_rgb565(srcP[x]);
- }
- srcP += srcRB;
- dstP = (uint16_t*)((char*)dstP + dstRB);
- }
-}
-#endif
-
-#define SK_FREETYPE_LCD_LERP 160
-
-static int lerp(int start, int end) {
- SkASSERT((unsigned)SK_FREETYPE_LCD_LERP <= 256);
- return start + ((end - start) * (SK_FREETYPE_LCD_LERP) >> 8);
-}
-
-static uint16_t packLCD16(unsigned r, unsigned g, unsigned b) {
- if (SK_FREETYPE_LCD_LERP) {
- // want (a+b+c)/3, but we approx to avoid the divide
- unsigned ave = (5 * (r + g + b) + g) >> 4;
- r = lerp(r, ave);
- g = lerp(g, ave);
- b = lerp(b, ave);
+
+ const uint8_t* maskPreBlendR = NULL;
+ const uint8_t* maskPreBlendG = NULL;
+ const uint8_t* maskPreBlendB = NULL;
+ if (APPLY_PREBLEND) {
+ maskPreBlendR = maskPreBlend->fR;
+ maskPreBlendG = maskPreBlend->fG;
+ maskPreBlendB = maskPreBlend->fB;
}
- return SkPackRGB16(r >> 3, g >> 2, b >> 3);
-}
-static void pack3xHToLCD16(const SkBitmap& src, const SkMask& dst) {
- SkASSERT(SkBitmap::kA8_Config == src.config());
- SkASSERT(SkMask::kLCD16_Format == dst.fFormat);
-
- const int width = dst.fBounds.width();
- const int height = dst.fBounds.height();
- uint16_t* dstP = (uint16_t*)dst.fImage;
- size_t dstRB = dst.fRowBytes;
for (int y = 0; y < height; ++y) {
const uint8_t* srcP = src.getAddr8(0, y);
for (int x = 0; x < width; ++x) {
- unsigned r = *srcP++;
- unsigned g = *srcP++;
- unsigned b = *srcP++;
- dstP[x] = packLCD16(r, g, b);
+ U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendR);
+ U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendG);
+ U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendB);
+ dstP[x] = SkPack888ToRGB16(r, g, b);
}
dstP = (uint16_t*)((char*)dstP + dstRB);
}
}
-static void pack3xHToLCD32(const SkBitmap& src, const SkMask& dst) {
+template<bool APPLY_PREBLEND>
+static void pack3xHToLCD32(const SkBitmap& src, const SkMask& dst, SkMaskGamma::PreBlend* maskPreBlend) {
SkASSERT(SkBitmap::kA8_Config == src.config());
SkASSERT(SkMask::kLCD32_Format == dst.fFormat);
@@ -420,20 +375,29 @@ static void pack3xHToLCD32(const SkBitmap& src, const SkMask& dst) {
const int height = dst.fBounds.height();
SkPMColor* dstP = (SkPMColor*)dst.fImage;
size_t dstRB = dst.fRowBytes;
+
+ const uint8_t* maskPreBlendR = NULL;
+ const uint8_t* maskPreBlendG = NULL;
+ const uint8_t* maskPreBlendB = NULL;
+ if (APPLY_PREBLEND) {
+ maskPreBlendR = maskPreBlend->fR;
+ maskPreBlendG = maskPreBlend->fG;
+ maskPreBlendB = maskPreBlend->fB;
+ }
+
for (int y = 0; y < height; ++y) {
const uint8_t* srcP = src.getAddr8(0, y);
for (int x = 0; x < width; ++x) {
- unsigned r = *srcP++;
- unsigned g = *srcP++;
- unsigned b = *srcP++;
- unsigned a = SkMax32(SkMax32(r, g), b);
- dstP[x] = SkPackARGB32(a, r, g, b);
+ U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendR);
+ U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendG);
+ U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlendB);
+ dstP[x] = SkPackARGB32(0xFF, r, g, b);
}
dstP = (SkPMColor*)((char*)dstP + dstRB);
}
}
-static void generateMask(const SkMask& mask, const SkPath& path) {
+static void generateMask(const SkMask& mask, const SkPath& path, SkMaskGamma::PreBlend* maskPreBlend) {
SkBitmap::Config config;
SkPaint paint;
@@ -493,10 +457,18 @@ static void generateMask(const SkMask& mask, const SkPath& path) {
if (0 == dstRB) {
switch (mask.fFormat) {
case SkMask::kLCD16_Format:
- pack3xHToLCD16(bm, mask);
+ if (maskPreBlend) {
+ pack3xHToLCD16<true>(bm, mask, maskPreBlend);
+ } else {
+ pack3xHToLCD16<false>(bm, mask, maskPreBlend);
+ }
break;
case SkMask::kLCD32_Format:
- pack3xHToLCD32(bm, mask);
+ if (maskPreBlend) {
+ pack3xHToLCD32<true>(bm, mask, maskPreBlend);
+ } else {
+ pack3xHToLCD32<false>(bm, mask, maskPreBlend);
+ }
break;
default:
SkDEBUGFAIL("bad format for copyback");
@@ -504,10 +476,23 @@ static void generateMask(const SkMask& mask, const SkPath& path) {
}
}
+static void applyLUTToA8Glyph(const SkGlyph& glyph, const uint8_t* lut) {
+ uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage;
+ unsigned rowBytes = glyph.rowBytes();
+
+ for (int y = glyph.fHeight - 1; y >= 0; --y) {
+ for (int x = glyph.fWidth - 1; x >= 0; --x) {
+ dst[x] = lut[dst[x]];
+ }
+ dst += rowBytes;
+ }
+}
+
void SkScalerContext::getImage(const SkGlyph& origGlyph) {
const SkGlyph* glyph = &origGlyph;
SkGlyph tmpGlyph;
-
+ SkMaskGamma::PreBlend* maskPreBlend = &fMaskPreBlend;
+
if (fMaskFilter) { // restore the prefilter bounds
tmpGlyph.init(origGlyph.fID);
@@ -523,6 +508,7 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) {
SkASSERT(tmpGlyph.fWidth <= origGlyph.fWidth);
SkASSERT(tmpGlyph.fHeight <= origGlyph.fHeight);
glyph = &tmpGlyph;
+ maskPreBlend = NULL;
}
if (fGenerateImageFromPath) {
@@ -542,11 +528,19 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) {
SkMask::kJustRenderImage_CreateMode)) {
return;
}
+ //apply maskPreBlend to a8 (if not NULL)
+ if (maskPreBlend) {
+ applyLUTToA8Glyph(*glyph, maskPreBlend->fG);
+ }
} else {
- generateMask(mask, devPath);
+ generateMask(mask, devPath, maskPreBlend);
+ //apply maskPreBlend to a8 (if not NULL) -- already applied to lcd.
+ if (maskPreBlend && mask.fFormat == SkMask::kA8_Format) {
+ applyLUTToA8Glyph(*glyph, maskPreBlend->fG);
+ }
}
} else {
- this->getGlyphContext(*glyph)->generateImage(*glyph);
+ this->getGlyphContext(*glyph)->generateImage(*glyph, maskPreBlend);
}
if (fMaskFilter) {
@@ -581,6 +575,11 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) {
dst += dstRB;
}
SkMask::FreeImage(dstM.fImage);
+
+ /* Pre-blend is not currently applied to filtered text.
+ The primary filter is blur, for which contrast makes no sense,
+ and for which the destination guess error is more visible. */
+ //applyLUTToA8Glyph(origGlyph, fMaskPreBlend.fG);
}
}
}
@@ -749,7 +748,7 @@ protected:
virtual void generateMetrics(SkGlyph* glyph) {
glyph->zeroMetrics();
}
- virtual void generateImage(const SkGlyph& glyph) {}
+ virtual void generateImage(const SkGlyph& glyph, SkMaskGamma::PreBlend* maskPreBlend) {}
virtual void generatePath(const SkGlyph& glyph, SkPath* path) {}
virtual void generateFontMetrics(SkPaint::FontMetrics* mx,
SkPaint::FontMetrics* my) {