Fix metrics on Windows.
With this change, Skia's metrics are much better in general, and
specifically can be made to match the metrics produced by current
Blink code. This allows Blink to use Skia's metrics.
This change will require a number of rebaselines in Skia, since
previous metrics were quite different. This will require about five
rebaslines in Blink, as the new code may cause GDI's matrix to differ
in the low bits.
Review URL: https://codereview.chromium.org/20585004
git-svn-id: http://skia.googlecode.com/svn/trunk@10399 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/core/SkScalerContext.cpp b/src/core/SkScalerContext.cpp
index d355d88..a7b4fa5 100644
--- a/src/core/SkScalerContext.cpp
+++ b/src/core/SkScalerContext.cpp
@@ -340,6 +340,18 @@
glyph->fTop = ir.fTop;
glyph->fWidth = SkToU16(ir.width());
glyph->fHeight = SkToU16(ir.height());
+
+ if (glyph->fWidth > 0) {
+ switch (fRec.fMaskFormat) {
+ case SkMask::kLCD16_Format:
+ case SkMask::kLCD32_Format:
+ glyph->fWidth += 2;
+ glyph->fLeft -= 1;
+ break;
+ default:
+ break;
+ }
+ }
}
}
@@ -386,6 +398,7 @@
glyph->fMaskFormat = fRec.fMaskFormat;
}
+#define SK_SHOW_TEXT_BLIT_COVERAGE 0
static void applyLUTToA8Mask(const SkMask& mask, const uint8_t* lut) {
uint8_t* SK_RESTRICT dst = (uint8_t*)mask.fImage;
@@ -400,30 +413,83 @@
}
template<bool APPLY_PREBLEND>
-static void pack3xHToLCD16(const SkBitmap& src, const SkMask& dst,
+static void pack4xHToLCD16(const SkBitmap& src, const SkMask& dst,
const SkMaskGamma::PreBlend& maskPreBlend) {
+#define SAMPLES_PER_PIXEL 4
+#define LCD_PER_PIXEL 3
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 int sample_width = src.width();
+ const int height = src.height();
+
uint16_t* dstP = (uint16_t*)dst.fImage;
size_t dstRB = dst.fRowBytes;
+ // An N tap FIR is defined by
+ // out[n] = coeff[0]*x[n] + coeff[1]*x[n-1] + ... + coeff[N]*x[n-N]
+ // or
+ // out[n] = sum(i, 0, N, coeff[i]*x[n-i])
+
+ // The strategy is to use one FIR (different coefficients) for each of r, g, and b.
+ // This means using every 4th FIR output value of each FIR and discarding the rest.
+ // The FIRs are aligned, and the coefficients reach 5 samples to each side of their 'center'.
+ // (For r and b this is technically incorrect, but the coeffs outside round to zero anyway.)
+
+ // These are in some fixed point repesentation.
+ // Adding up to more than one simulates ink spread.
+ // For implementation reasons, these should never add up to more than two.
+
+ // Coefficients determined by a gausian where 5 samples = 3 std deviations (0x110 'contrast').
+ // Calculated using tools/generate_fir_coeff.py
+ // With this one almost no fringing is ever seen, but it is imperceptibly blurry.
+ // The lcd smoothed text is almost imperceptibly different from gray,
+ // but is still sharper on small stems and small rounded corners than gray.
+ // This also seems to be about as wide as one can get and only have a three pixel kernel.
+ // TODO: caculate these at runtime so parameters can be adjusted (esp contrast).
+ static const unsigned int coefficients[LCD_PER_PIXEL][SAMPLES_PER_PIXEL*3] = {
+ //The red subpixel is centered inside the first sample (at 1/6 pixel), and is shifted.
+ { 0x03, 0x0b, 0x1c, 0x33, 0x40, 0x39, 0x24, 0x10, 0x05, 0x01, 0x00, 0x00, },
+ //The green subpixel is centered between two samples (at 1/2 pixel), so is symetric
+ { 0x00, 0x02, 0x08, 0x16, 0x2b, 0x3d, 0x3d, 0x2b, 0x16, 0x08, 0x02, 0x00, },
+ //The blue subpixel is centered inside the last sample (at 5/6 pixel), and is shifted.
+ { 0x00, 0x00, 0x01, 0x05, 0x10, 0x24, 0x39, 0x40, 0x33, 0x1c, 0x0b, 0x03, },
+ };
for (int y = 0; y < height; ++y) {
const uint8_t* srcP = src.getAddr8(0, y);
- for (int x = 0; x < width; ++x) {
- U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlend.fR);
- U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlend.fG);
- U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlend.fB);
- dstP[x] = SkPack888ToRGB16(r, g, b);
+
+ // TODO: this fir filter implementation is straight forward, but slow.
+ // It should be possible to make it much faster.
+ for (int sample_x = -4, pixel_x = 0; sample_x < sample_width + 4; sample_x += 4, ++pixel_x) {
+ int fir[LCD_PER_PIXEL] = { 0 };
+ for (int sample_index = SkMax32(0, sample_x - 4), coeff_index = sample_index - (sample_x - 4)
+ ; sample_index < SkMin32(sample_x + 8, sample_width)
+ ; ++sample_index, ++coeff_index)
+ {
+ int sample_value = srcP[sample_index];
+ for (int subpxl_index = 0; subpxl_index < LCD_PER_PIXEL; ++subpxl_index) {
+ fir[subpxl_index] += coefficients[subpxl_index][coeff_index] * sample_value;
+ }
+ }
+ for (int subpxl_index = 0; subpxl_index < LCD_PER_PIXEL; ++subpxl_index) {
+ fir[subpxl_index] /= 0x100;
+ fir[subpxl_index] = SkMin32(fir[subpxl_index], 255);
+ }
+
+ U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(fir[0], maskPreBlend.fR);
+ U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(fir[1], maskPreBlend.fG);
+ U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(fir[2], maskPreBlend.fB);
+#if SK_SHOW_TEXT_BLIT_COVERAGE
+ r = SkMax32(r, 10); g = SkMax32(g, 10); b = SkMax32(b, 10);
+#endif
+ dstP[pixel_x] = SkPack888ToRGB16(r, g, b);
}
dstP = (uint16_t*)((char*)dstP + dstRB);
}
}
template<bool APPLY_PREBLEND>
-static void pack3xHToLCD32(const SkBitmap& src, const SkMask& dst,
+static void pack4xHToLCD32(const SkBitmap& src, const SkMask& dst,
const SkMaskGamma::PreBlend& maskPreBlend) {
SkASSERT(SkBitmap::kA8_Config == src.config());
SkASSERT(SkMask::kLCD32_Format == dst.fFormat);
@@ -435,6 +501,8 @@
for (int y = 0; y < height; ++y) {
const uint8_t* srcP = src.getAddr8(0, y);
+
+ // TODO: need to use fir filter here as well.
for (int x = 0; x < width; ++x) {
U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlend.fR);
U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(*srcP++, maskPreBlend.fG);
@@ -472,8 +540,10 @@
case SkMask::kLCD16_Format:
case SkMask::kLCD32_Format:
// TODO: trigger off LCD orientation
- dstW *= 3;
- matrix.postScale(SkIntToScalar(3), SK_Scalar1);
+ dstW = 4*dstW - 8;
+ matrix.setTranslate(-SkIntToScalar(mask.fBounds.fLeft + 1),
+ -SkIntToScalar(mask.fBounds.fTop));
+ matrix.postScale(SkIntToScalar(4), SK_Scalar1);
dstRB = 0; // signals we need a copy
break;
default:
@@ -514,16 +584,16 @@
break;
case SkMask::kLCD16_Format:
if (maskPreBlend.isApplicable()) {
- pack3xHToLCD16<true>(bm, mask, maskPreBlend);
+ pack4xHToLCD16<true>(bm, mask, maskPreBlend);
} else {
- pack3xHToLCD16<false>(bm, mask, maskPreBlend);
+ pack4xHToLCD16<false>(bm, mask, maskPreBlend);
}
break;
case SkMask::kLCD32_Format:
if (maskPreBlend.isApplicable()) {
- pack3xHToLCD32<true>(bm, mask, maskPreBlend);
+ pack4xHToLCD32<true>(bm, mask, maskPreBlend);
} else {
- pack3xHToLCD32<false>(bm, mask, maskPreBlend);
+ pack4xHToLCD32<false>(bm, mask, maskPreBlend);
}
break;
default:
diff --git a/src/core/SkScalerContext.h b/src/core/SkScalerContext.h
index 06f5f94..52707a3 100644
--- a/src/core/SkScalerContext.h
+++ b/src/core/SkScalerContext.h
@@ -134,13 +134,14 @@
kHintingBit1_Flag = 0x0080,
kHintingBit2_Flag = 0x0100,
- // these should only ever be set if fMaskFormat is LCD16 or LCD32
+ // Pixel geometry information.
+ // only meaningful if fMaskFormat is LCD16 or LCD32
kLCD_Vertical_Flag = 0x0200, // else Horizontal
kLCD_BGROrder_Flag = 0x0400, // else RGB order
- // Generate A8 from LCD source (for GDI), only meaningful if fMaskFormat is kA8
- // Perhaps we can store this (instead) in fMaskFormat, in hight bit?
- kGenA8FromLCD_Flag = 0x0800,
+ // Generate A8 from LCD source (for GDI and CoreGraphics).
+ // only meaningful if fMaskFormat is kA8
+ kGenA8FromLCD_Flag = 0x0800, // could be 0x200 (bit meaning dependent on fMaskFormat)
};
// computed values
diff --git a/src/ports/SkFontHost_win.cpp b/src/ports/SkFontHost_win.cpp
index 31ecdff..98bd324 100755
--- a/src/ports/SkFontHost_win.cpp
+++ b/src/ports/SkFontHost_win.cpp
@@ -80,23 +80,11 @@
return true;
}
#endif
- // false means allow GDI to generate the bits
- return false;
+ return rec.getHinting() == SkPaint::kNo_Hinting || rec.getHinting() == SkPaint::kSlight_Hinting;
}
using namespace skia_advanced_typeface_metrics_utils;
-/**
- * Since LOGFONT wants its textsize as an int, and we support fractional sizes,
- * and since we have a cache of LOGFONTs for our tyepfaces, we always set the
- * lfHeight to a canonical size, and then we use the 2x2 matrix to achieve the
- * actual requested size.
- *
- * Not critical to match the font's upem, but we want it big enough to avoid
- * precision loss for GDI calls that return ints (e.g. GetOutlineFontMetrics).
- */
-static const int gCanonicalTextSize = 2048;
-
static void tchar_to_skstring(const TCHAR t[], SkString* s) {
#ifdef UNICODE
size_t sSize = WideCharToMultiByte(CP_UTF8, 0, t, -1, NULL, 0, NULL, NULL);
@@ -108,7 +96,7 @@
}
static void make_canonical(LOGFONT* lf) {
- lf->lfHeight = -gCanonicalTextSize;
+ lf->lfHeight = -2048;
lf->lfQuality = CLEARTYPE_QUALITY;//PROOF_QUALITY;
lf->lfCharSet = DEFAULT_CHARSET;
// lf->lfClipPrecision = 64;
@@ -453,12 +441,6 @@
int fWidth;
int fHeight;
bool fIsBW;
-
- enum {
- // will always trigger us to reset the color, since we
- // should only store 0 or 0x00FFFFFF or gray (0x007F7F7F)
- kInvalid_Color = 12345
- };
};
const void* HDCOffscreen::draw(const SkGlyph& glyph, bool isBW,
@@ -538,6 +520,7 @@
}
//////////////////////////////////////////////////////////////////////////////
+#define BUFFERSIZE (1 << 13)
class SkScalerContext_Windows : public SkScalerContext {
public:
@@ -559,8 +542,14 @@
SkPaint::FontMetrics* mY) SK_OVERRIDE;
private:
+ DWORD getGDIGlyphPath(const SkGlyph& glyph, UINT flags,
+ SkAutoSTMalloc<BUFFERSIZE, uint8_t>* glyphbuf);
+
HDCOffscreen fOffscreen;
- SkScalar fScale; // to get from canonical size to real size
+ /** fGsA is the non-rotational part of total matrix without the text height scale.
+ * Used to find the magnitude of advances.
+ */
+ MAT2 fGsA;
MAT2 fMat22;
HDC fDDC;
HFONT fSavefont;
@@ -569,32 +558,21 @@
SCRIPT_CACHE fSC;
int fGlyphCount;
- MAT2 fMat22Identity;
SkMatrix fHiResMatrix;
+ /** fG_inv is the inverse of the rotational part of the total matrix.
+ * Used to set the direction of advances.
+ */
+ SkMatrix fG_inv;
enum Type {
kTrueType_Type, kBitmap_Type,
} fType;
TEXTMETRIC fTM;
};
-static float mul2float(SkScalar a, SkScalar b) {
- return SkScalarToFloat(SkScalarMul(a, b));
-}
-
static FIXED float2FIXED(float x) {
return SkFixedToFIXED(SkFloatToFixed(x));
}
-#define HIRES_TEXTSIZE 2048
-#define HIRES_SHIFT 11
-static inline SkFixed HiResToFixed(int value) {
- return value << (16 - HIRES_SHIFT);
-}
-
-static bool needHiResMetrics(const SkScalar mat[2][2]) {
- return mat[1][0] || mat[0][1];
-}
-
static BYTE compute_quality(const SkScalerContext::Rec& rec) {
switch (rec.fMaskFormat) {
case SkMask::kBW_Format:
@@ -627,36 +605,84 @@
if (!fDDC) {
return;
}
-
SetGraphicsMode(fDDC, GM_ADVANCED);
SetBkMode(fDDC, TRANSPARENT);
+
+ SkPoint h = SkPoint::Make(SK_Scalar1, 0);
+ // A is the total matrix.
+ SkMatrix A;
+ fRec.getSingleMatrix(&A);
+ A.mapPoints(&h, 1);
- // Scaling by the DPI is inconsistent with how Skia draws elsewhere
- //SkScalar height = -(fRec.fTextSize * GetDeviceCaps(ddc, LOGPIXELSY) / 72);
+ // Find the Given's matrix [[c, -s],[s, c]] which rotates the baseline vector h
+ // (where the baseline is mapped to) to the positive horizontal axis.
+ const SkScalar& a = h.fX;
+ const SkScalar& b = h.fY;
+ SkScalar c, s;
+ if (0 == b) {
+ c = SkDoubleToScalar(_copysign(SK_Scalar1, a));
+ s = 0;
+ } else if (0 == a) {
+ c = 0;
+ s = SkDoubleToScalar(-_copysign(SK_Scalar1, b));
+ } else if (SkScalarAbs(b) > SkScalarAbs(a)) {
+ SkScalar t = a / b;
+ SkScalar u = SkDoubleToScalar(_copysign(SkScalarSqrt(SK_Scalar1 + t*t), b));
+ s = -1 / u;
+ c = -s * t;
+ } else {
+ SkScalar t = b / a;
+ SkScalar u = SkDoubleToScalar(_copysign(SkScalarSqrt(SK_Scalar1 + t*t), a));
+ c = 1 / u;
+ s = -c * t;
+ }
+
+ // G is the Given's Matrix for A (rotational matrix such that GA[0][1] == 0).
+ SkMatrix G;
+ G.setAll(c, -s, 0,
+ s, c, 0,
+ 0, 0, SkScalarToPersp(SK_Scalar1));
+
+ // GA is the matrix A with rotation removed.
+ SkMatrix GA(G);
+ GA.preConcat(A);
+
+ // textSize is the actual device size we want (as opposed to the size the user requested).
+ // If the scale is negative, this means the matrix will do the flip anyway.
+ SkScalar textSize = SkScalarAbs(SkScalarRoundToScalar(GA.get(SkMatrix::kMScaleY)));
+ if (textSize == 0) {
+ textSize = SK_Scalar1;
+ }
+
+ // sA is the total matrix A without the textSize (so GDI knows the text size separately).
+ // When this matrix is used with GetGlyphOutline, no further processing is needed.
+ SkMatrix sA(A);
+ SkScalar scale = SkScalarInvert(textSize);
+ sA.preScale(scale, scale); //remove text size
+
+ // GsA is the non-rotational part of A without the text height scale.
+ // This is what is used to find the magnitude of advances.
+ SkMatrix GsA(GA);
+ GsA.preScale(scale, scale); //remove text size, G is rotational so reorders with the scale.
+
+ fGsA.eM11 = SkScalarToFIXED(GsA.get(SkMatrix::kMScaleX));
+ fGsA.eM12 = SkScalarToFIXED(-GsA.get(SkMatrix::kMSkewY)); // This should be ~0.
+ fGsA.eM21 = SkScalarToFIXED(-GsA.get(SkMatrix::kMSkewX));
+ fGsA.eM22 = SkScalarToFIXED(GsA.get(SkMatrix::kMScaleY));
+
+ // fG_inv is G inverse, which is fairly simple since G is 2x2 rotational.
+ fG_inv.setAll(G.get(SkMatrix::kMScaleX), -G.get(SkMatrix::kMSkewX), G.get(SkMatrix::kMTransX),
+ -G.get(SkMatrix::kMSkewY), G.get(SkMatrix::kMScaleY), G.get(SkMatrix::kMTransY),
+ G.get(SkMatrix::kMPersp0), G.get(SkMatrix::kMPersp1), G.get(SkMatrix::kMPersp2));
+
LOGFONT lf = typeface->fLogFont;
- lf.lfHeight = -gCanonicalTextSize;
+ lf.lfHeight = -SkScalarTruncToInt(textSize);
lf.lfQuality = compute_quality(fRec);
fFont = CreateFontIndirect(&lf);
if (!fFont) {
return;
}
- // if we're rotated, or want fractional widths, create a hires font
- if (needHiResMetrics(fRec.fPost2x2)) {
- lf.lfHeight = -HIRES_TEXTSIZE;
- fHiResFont = CreateFontIndirect(&lf);
- if (!fHiResFont) {
- return;
- }
-
- fMat22Identity.eM11 = fMat22Identity.eM22 = SkFixedToFIXED(SK_Fixed1);
- fMat22Identity.eM12 = fMat22Identity.eM21 = SkFixedToFIXED(0);
-
- // construct a matrix to go from HIRES logical units to our device units
- fRec.getSingleMatrix(&fHiResMatrix);
- SkScalar scale = SkScalarInvert(SkIntToScalar(HIRES_TEXTSIZE));
- fHiResMatrix.preScale(scale, scale);
- }
fSavefont = (HFONT)SelectObject(fDDC, fFont);
if (0 == GetTextMetrics(fDDC, &fTM)) {
@@ -680,14 +706,13 @@
// Truetype or PostScript.
// Stroked FON also gets here (TMPF_VECTOR), but we don't handle it.
fType = SkScalerContext_Windows::kTrueType_Type;
- fScale = fRec.fTextSize / gCanonicalTextSize;
// fPost2x2 is column-major, left handed (y down).
// XFORM 2x2 is row-major, left handed (y down).
- xform.eM11 = mul2float(fScale, fRec.fPost2x2[0][0]);
- xform.eM12 = mul2float(fScale, fRec.fPost2x2[1][0]);
- xform.eM21 = mul2float(fScale, fRec.fPost2x2[0][1]);
- xform.eM22 = mul2float(fScale, fRec.fPost2x2[1][1]);
+ xform.eM11 = SkScalarToFloat(sA.get(SkMatrix::kMScaleX));
+ xform.eM12 = SkScalarToFloat(sA.get(SkMatrix::kMSkewY));
+ xform.eM21 = SkScalarToFloat(sA.get(SkMatrix::kMSkewX));
+ xform.eM22 = SkScalarToFloat(sA.get(SkMatrix::kMScaleY));
xform.eDx = 0;
xform.eDy = 0;
@@ -701,10 +726,31 @@
this->forceGenerateImageFromPath();
}
+ // Create a hires font if we need linear metrics.
+ if (this->isSubpixel()) {
+ OUTLINETEXTMETRIC otm;
+ UINT success = GetOutlineTextMetrics(fDDC, sizeof(otm), &otm);
+ if (0 == success) {
+ call_ensure_accessible(lf);
+ success = GetOutlineTextMetrics(fDDC, sizeof(otm), &otm);
+ }
+ if (0 != success) {
+ lf.lfHeight = -SkToS32(otm.otmEMSquare);
+ fHiResFont = CreateFontIndirect(&lf);
+ if (!fHiResFont) {
+ return;
+ }
+
+ // construct a matrix to go from HIRES logical units to our device units
+ fRec.getSingleMatrix(&fHiResMatrix);
+ SkScalar scale = SkScalarInvert(SkIntToScalar(otm.otmEMSquare));
+ fHiResMatrix.preScale(scale, scale);
+ }
+ }
+
} else {
// Assume bitmap
fType = SkScalerContext_Windows::kBitmap_Type;
- fScale = SK_Scalar1;
xform.eM11 = 1.0f;
xform.eM12 = 0.0f;
@@ -719,18 +765,6 @@
fMat22.eM12 = SkScalarToFIXED(-fRec.fPost2x2[1][0]);
fMat22.eM21 = SkScalarToFIXED(-fRec.fPost2x2[0][1]);
fMat22.eM22 = SkScalarToFIXED(fRec.fPost2x2[1][1]);
-
- lf.lfHeight = -SkScalarCeilToInt(fRec.fTextSize);
- HFONT bitmapFont = CreateFontIndirect(&lf);
- SelectObject(fDDC, bitmapFont);
- ::DeleteObject(fFont);
- fFont = bitmapFont;
-
- if (0 == GetTextMetrics(fDDC, &fTM)) {
- call_ensure_accessible(lf);
- //if the following fails, we'll just draw at gCanonicalTextSize.
- GetTextMetrics(fDDC, &fTM);
- }
}
fOffscreen.init(fFont, xform);
@@ -793,6 +827,7 @@
this->generateMetrics(glyph);
}
+static const MAT2 gMat2Identity = {{0, 1}, {0, 0}, {0, 0}, {0, 1}};
void SkScalerContext_Windows::generateMetrics(SkGlyph* glyph) {
SkASSERT(fDDC);
@@ -866,7 +901,7 @@
if (fHiResFont) {
SelectObject(fDDC, fHiResFont);
sk_bzero(&gm, sizeof(gm));
- status = GetGlyphOutlineW(fDDC, glyphId, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, NULL, &fMat22Identity);
+ status = GetGlyphOutlineW(fDDC, glyphId, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, NULL, &gMat2Identity);
if (GDI_ERROR != status) {
SkPoint advance;
fHiResMatrix.mapXY(SkIntToScalar(gm.gmCellIncX), SkIntToScalar(gm.gmCellIncY), &advance);
@@ -874,6 +909,14 @@
glyph->fAdvanceY = SkScalarToFixed(advance.fY);
}
SelectObject(fDDC, fFont);
+ } else if (!isAxisAligned(this->fRec)) {
+ status = GetGlyphOutlineW(fDDC, glyphId, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, NULL, &fGsA);
+ if (GDI_ERROR != status) {
+ SkPoint advance;
+ fG_inv.mapXY(SkIntToScalar(gm.gmCellIncX), SkIntToScalar(gm.gmCellIncY), &advance);
+ glyph->fAdvanceX = SkScalarToFixed(advance.fX);
+ glyph->fAdvanceY = SkScalarToFixed(advance.fY);
+ }
}
}
@@ -897,7 +940,7 @@
if (mx) {
mx->fTop = SkIntToScalar(-fTM.tmAscent);
mx->fAscent = SkIntToScalar(-fTM.tmAscent);
- mx->fDescent = -SkIntToScalar(fTM.tmDescent);
+ mx->fDescent = SkIntToScalar(fTM.tmDescent);
mx->fBottom = SkIntToScalar(fTM.tmDescent);
mx->fLeading = SkIntToScalar(fTM.tmExternalLeading);
}
@@ -905,7 +948,7 @@
if (my) {
my->fTop = SkIntToScalar(-fTM.tmAscent);
my->fAscent = SkIntToScalar(-fTM.tmAscent);
- my->fDescent = SkIntToScalar(-fTM.tmDescent);
+ my->fDescent = SkIntToScalar(fTM.tmDescent);
my->fBottom = SkIntToScalar(fTM.tmDescent);
my->fLeading = SkIntToScalar(fTM.tmExternalLeading);
my->fAvgCharWidth = SkIntToScalar(fTM.tmAveCharWidth);
@@ -922,40 +965,49 @@
OUTLINETEXTMETRIC otm;
uint32_t ret = GetOutlineTextMetrics(fDDC, sizeof(otm), &otm);
- if (GDI_ERROR == ret) {
+ if (0 == ret) {
LogFontTypeface::EnsureAccessible(this->getTypeface());
ret = GetOutlineTextMetrics(fDDC, sizeof(otm), &otm);
}
- if (sizeof(otm) != ret) {
+ if (0 == ret) {
return;
}
if (mx) {
- mx->fTop = -fScale * otm.otmrcFontBox.left;
- mx->fAscent = -fScale * otm.otmAscent;
- mx->fDescent = -fScale * otm.otmDescent;
- mx->fBottom = fScale * otm.otmrcFontBox.right;
- mx->fLeading = fScale * otm.otmLineGap;
+ mx->fTop = SkIntToScalar(-otm.otmrcFontBox.left);
+ mx->fAscent = SkIntToScalar(-otm.otmAscent);
+ mx->fDescent = SkIntToScalar(-otm.otmDescent);
+ mx->fBottom = SkIntToScalar(otm.otmrcFontBox.right);
+ mx->fLeading = SkIntToScalar(otm.otmLineGap);
}
if (my) {
#ifndef SK_GDI_ALWAYS_USE_TEXTMETRICS_FOR_FONT_METRICS
- my->fTop = -fScale * otm.otmrcFontBox.top;
- my->fAscent = -fScale * otm.otmAscent;
- my->fDescent = -fScale * otm.otmDescent;
- my->fBottom = -fScale * otm.otmrcFontBox.bottom;
- my->fLeading = fScale * otm.otmLineGap;
- my->fAvgCharWidth = fScale * otm.otmTextMetrics.tmAveCharWidth;
- my->fMaxCharWidth = fScale * otm.otmTextMetrics.tmMaxCharWidth;
- my->fXMin = fScale * otm.otmrcFontBox.left;
- my->fXMax = fScale * otm.otmrcFontBox.right;
+ my->fTop = SkIntToScalar(-otm.otmrcFontBox.top);
+ my->fAscent = SkIntToScalar(-otm.otmAscent);
+ my->fDescent = SkIntToScalar(-otm.otmDescent);
+ my->fBottom = SkIntToScalar(-otm.otmrcFontBox.bottom);
+ my->fLeading = SkIntToScalar(otm.otmLineGap);
+ my->fAvgCharWidth = SkIntToScalar(otm.otmTextMetrics.tmAveCharWidth);
+ my->fMaxCharWidth = SkIntToScalar(otm.otmTextMetrics.tmMaxCharWidth);
+ my->fXMin = SkIntToScalar(otm.otmrcFontBox.left);
+ my->fXMax = SkIntToScalar(otm.otmrcFontBox.right);
#endif
- my->fXHeight = fScale * otm.otmsXHeight;
+ my->fXHeight = SkIntToScalar(otm.otmsXHeight);
+
+ GLYPHMETRICS gm;
+ sk_bzero(&gm, sizeof(gm));
+ DWORD len = GetGlyphOutlineW(fDDC, 'x', GGO_METRICS, &gm, 0, 0, &gMat2Identity);
+ if (len != GDI_ERROR && gm.gmBlackBoxY > 0) {
+ my->fXHeight = SkIntToScalar(gm.gmBlackBoxY);
+ }
}
}
////////////////////////////////////////////////////////////////////////////////////////
+#define SK_SHOW_TEXT_BLIT_COVERAGE 0
+
static void build_power_table(uint8_t table[], float ee) {
for (int i = 0; i < 256; i++) {
float x = i / 255.f;
@@ -1050,6 +1102,9 @@
U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 16) & 0xFF, tableR);
U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 8) & 0xFF, tableG);
U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 0) & 0xFF, tableB);
+#if SK_SHOW_TEXT_BLIT_COVERAGE
+ r = SkMax32(r, 10); g = SkMax32(g, 10); b = SkMax32(b, 10);
+#endif
return SkPack888ToRGB16(r, g, b);
}
@@ -1060,6 +1115,9 @@
U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 16) & 0xFF, tableR);
U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 8) & 0xFF, tableG);
U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>((rgb >> 0) & 0xFF, tableB);
+#if SK_SHOW_TEXT_BLIT_COVERAGE
+ r = SkMax32(r, 10); g = SkMax32(g, 10); b = SkMax32(b, 10);
+#endif
return SkPackARGB32(0xFF, r, g, b);
}
@@ -1130,6 +1188,14 @@
src = SkTAddOffset<const SkGdiRGB>(src, srcRB);
dst -= dstRB;
}
+#if SK_SHOW_TEXT_BLIT_COVERAGE
+ if (glyph.fWidth > 0 && glyph.fHeight > 0) {
+ uint8_t* first = (uint8_t*)glyph.fImage;
+ uint8_t* last = (uint8_t*)((char*)glyph.fImage + glyph.fHeight * dstRB - 1);
+ *first |= 1 << 7;
+ *last |= bitCount == 0 ? 1 : 1 << (8 - bitCount);
+ }
+#endif
}
template<bool APPLY_PREBLEND>
@@ -1142,6 +1208,9 @@
for (int y = 0; y < glyph.fHeight; y++) {
for (int i = 0; i < width; i++) {
dst[i] = rgb_to_a8<APPLY_PREBLEND>(src[i], table8);
+#if SK_SHOW_TEXT_BLIT_COVERAGE
+ dst[i] = SkMax32(dst[i], 10);
+#endif
}
src = SkTAddOffset<const SkGdiRGB>(src, srcRB);
dst -= dstRB;
@@ -1238,6 +1307,15 @@
src += srcRB;
dst -= dstRB;
}
+#if SK_SHOW_TEXT_BLIT_COVERAGE
+ if (glyph.fWidth > 0 && glyph.fHeight > 0) {
+ int bitCount = width & 7;
+ uint8_t* first = (uint8_t*)glyph.fImage;
+ uint8_t* last = (uint8_t*)((char*)glyph.fImage + glyph.fHeight * dstRB - 1);
+ *first |= 1 << 7;
+ *last |= bitCount == 0 ? 1 : 1 << (8 - bitCount);
+ }
+#endif
} else if (isAA) {
// since the caller may require A8 for maskfilters, we can't check for BW
// ... until we have the caller tell us that explicitly
@@ -1275,54 +1353,146 @@
}
}
-void SkScalerContext_Windows::generatePath(const SkGlyph& glyph, SkPath* path) {
- SkASSERT(&glyph && path);
- SkASSERT(fDDC);
+class GDIGlyphbufferPointIter {
+public:
+ GDIGlyphbufferPointIter(const uint8_t* glyphbuf, DWORD total_size)
+ : fHeaderIter(glyphbuf, total_size), fCurveIter(), fPointIter()
+ { }
- path->reset();
-
- GLYPHMETRICS gm;
-
- // Out of all the fonts on a typical Windows box,
- // 25% of glyphs require more than 2KB.
- // 1% of glyphs require more than 4KB.
- // 0.01% of glyphs require more than 8KB.
- // 8KB is less than 1% of the normal 1MB stack on Windows.
- // Note that some web fonts glyphs require more than 20KB.
- static const DWORD BUFFERSIZE = (1 << 13);
- SkAutoSTMalloc<BUFFERSIZE, uint8_t> glyphbuf(BUFFERSIZE);
-
- const UINT flags = GGO_NATIVE | GGO_GLYPH_INDEX;
- DWORD total_size = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, BUFFERSIZE, glyphbuf, &fMat22);
- // Sometimes GetGlyphOutlineW returns a number larger than BUFFERSIZE even if BUFFERSIZE > 0.
- // It has been verified that this does not involve a buffer overrun.
- if (GDI_ERROR == total_size || total_size > BUFFERSIZE) {
- // GDI_ERROR because the BUFFERSIZE was too small, or because the data was not accessible.
- // When the data is not accessable GetGlyphOutlineW fails rather quickly,
- // so just try to get the size. If that fails then ensure the data is accessible.
- total_size = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, 0, NULL, &fMat22);
- if (GDI_ERROR == total_size) {
- LogFontTypeface::EnsureAccessible(this->getTypeface());
- total_size = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, 0, NULL, &fMat22);
- if (GDI_ERROR == total_size) {
- SkASSERT(false);
- return;
- }
+ POINTFX next() {
+nextHeader:
+ if (!fCurveIter.isSet()) {
+ const TTPOLYGONHEADER* header = fHeaderIter.next();
+ SkASSERT(header);
+ fCurveIter.set(header);
+ const TTPOLYCURVE* curve = fCurveIter.next();
+ SkASSERT(curve);
+ fPointIter.set(curve);
+ return header->pfxStart;
}
- glyphbuf.reset(total_size);
-
- DWORD ret = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, total_size, glyphbuf, &fMat22);
- if (GDI_ERROR == ret) {
- LogFontTypeface::EnsureAccessible(this->getTypeface());
- ret = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, total_size, glyphbuf, &fMat22);
- if (GDI_ERROR == ret) {
- SkASSERT(false);
- return;
+ const POINTFX* nextPoint = fPointIter.next();
+ if (NULL == nextPoint) {
+ const TTPOLYCURVE* curve = fCurveIter.next();
+ if (NULL == curve) {
+ fCurveIter.set();
+ goto nextHeader;
+ } else {
+ fPointIter.set(curve);
}
+ nextPoint = fPointIter.next();
+ SkASSERT(nextPoint);
}
+ return *nextPoint;
}
+ WORD currentCurveType() {
+ return fPointIter.fCurveType;
+ }
+
+private:
+ /** Iterates over all of the polygon headers in a glyphbuf. */
+ class GDIPolygonHeaderIter {
+ public:
+ GDIPolygonHeaderIter(const uint8_t* glyphbuf, DWORD total_size)
+ : fCurPolygon(reinterpret_cast<const TTPOLYGONHEADER*>(glyphbuf))
+ , fEndPolygon(SkTAddOffset<const TTPOLYGONHEADER>(glyphbuf, total_size))
+ { }
+
+ const TTPOLYGONHEADER* next() {
+ if (fCurPolygon >= fEndPolygon) {
+ return NULL;
+ }
+ const TTPOLYGONHEADER* thisPolygon = fCurPolygon;
+ fCurPolygon = SkTAddOffset<const TTPOLYGONHEADER>(fCurPolygon, fCurPolygon->cb);
+ return thisPolygon;
+ }
+ private:
+ const TTPOLYGONHEADER* fCurPolygon;
+ const TTPOLYGONHEADER* fEndPolygon;
+ };
+
+ /** Iterates over all of the polygon curves in a polygon header. */
+ class GDIPolygonCurveIter {
+ public:
+ GDIPolygonCurveIter() : fCurCurve(NULL), fEndCurve(NULL) { }
+
+ GDIPolygonCurveIter(const TTPOLYGONHEADER* curPolygon)
+ : fCurCurve(SkTAddOffset<const TTPOLYCURVE>(curPolygon, sizeof(TTPOLYGONHEADER)))
+ , fEndCurve(SkTAddOffset<const TTPOLYCURVE>(curPolygon, curPolygon->cb))
+ { }
+
+ bool isSet() { return fCurCurve != NULL; }
+
+ void set(const TTPOLYGONHEADER* curPolygon) {
+ fCurCurve = SkTAddOffset<const TTPOLYCURVE>(curPolygon, sizeof(TTPOLYGONHEADER));
+ fEndCurve = SkTAddOffset<const TTPOLYCURVE>(curPolygon, curPolygon->cb);
+ }
+ void set() {
+ fCurCurve = NULL;
+ fEndCurve = NULL;
+ }
+
+ const TTPOLYCURVE* next() {
+ if (fCurCurve >= fEndCurve) {
+ return NULL;
+ }
+ const TTPOLYCURVE* thisCurve = fCurCurve;
+ fCurCurve = SkTAddOffset<const TTPOLYCURVE>(fCurCurve, size_of_TTPOLYCURVE(*fCurCurve));
+ return thisCurve;
+ }
+ private:
+ size_t size_of_TTPOLYCURVE(const TTPOLYCURVE& curve) {
+ return 2*sizeof(WORD) + curve.cpfx*sizeof(POINTFX);
+ }
+ const TTPOLYCURVE* fCurCurve;
+ const TTPOLYCURVE* fEndCurve;
+ };
+
+ /** Iterates over all of the polygon points in a polygon curve. */
+ class GDIPolygonCurvePointIter {
+ public:
+ GDIPolygonCurvePointIter() : fCurveType(0), fCurPoint(NULL), fEndPoint(NULL) { }
+
+ GDIPolygonCurvePointIter(const TTPOLYCURVE* curPolygon)
+ : fCurveType(curPolygon->wType)
+ , fCurPoint(&curPolygon->apfx[0])
+ , fEndPoint(&curPolygon->apfx[curPolygon->cpfx])
+ { }
+
+ bool isSet() { return fCurPoint != NULL; }
+
+ void set(const TTPOLYCURVE* curPolygon) {
+ fCurveType = curPolygon->wType;
+ fCurPoint = &curPolygon->apfx[0];
+ fEndPoint = &curPolygon->apfx[curPolygon->cpfx];
+ }
+ void set() {
+ fCurPoint = NULL;
+ fEndPoint = NULL;
+ }
+
+ const POINTFX* next() {
+ if (fCurPoint >= fEndPoint) {
+ return NULL;
+ }
+ const POINTFX* thisPoint = fCurPoint;
+ ++fCurPoint;
+ return thisPoint;
+ }
+
+ WORD fCurveType;
+ private:
+ const POINTFX* fCurPoint;
+ const POINTFX* fEndPoint;
+ };
+
+ GDIPolygonHeaderIter fHeaderIter;
+ GDIPolygonCurveIter fCurveIter;
+ GDIPolygonCurvePointIter fPointIter;
+};
+
+static void sk_path_from_gdi_path(SkPath* path, const uint8_t* glyphbuf, DWORD total_size) {
const uint8_t* cur_glyph = glyphbuf;
const uint8_t* end_glyph = glyphbuf + total_size;
@@ -1371,6 +1541,148 @@
}
}
+static void sk_path_from_gdi_paths(SkPath* path, const uint8_t* glyphbuf, DWORD total_size,
+ GDIGlyphbufferPointIter hintedYs) {
+ const uint8_t* cur_glyph = glyphbuf;
+ const uint8_t* end_glyph = glyphbuf + total_size;
+
+ while (cur_glyph < end_glyph) {
+ const TTPOLYGONHEADER* th = (TTPOLYGONHEADER*)cur_glyph;
+
+ const uint8_t* end_poly = cur_glyph + th->cb;
+ const uint8_t* cur_poly = cur_glyph + sizeof(TTPOLYGONHEADER);
+
+ path->moveTo(SkFixedToScalar( SkFIXEDToFixed(th->pfxStart.x)),
+ SkFixedToScalar(-SkFIXEDToFixed(hintedYs.next().y)));
+
+ while (cur_poly < end_poly) {
+ const TTPOLYCURVE* pc = (const TTPOLYCURVE*)cur_poly;
+
+ if (pc->wType == TT_PRIM_LINE) {
+ for (uint16_t i = 0; i < pc->cpfx; i++) {
+ path->lineTo(SkFixedToScalar( SkFIXEDToFixed(pc->apfx[i].x)),
+ SkFixedToScalar(-SkFIXEDToFixed(hintedYs.next().y)));
+ }
+ }
+
+ if (pc->wType == TT_PRIM_QSPLINE) {
+ POINTFX currentPoint = pc->apfx[0];
+ POINTFX hintedY = hintedYs.next();
+ // only take the hinted y if it wasn't flipped
+ if (hintedYs.currentCurveType() == TT_PRIM_QSPLINE) {
+ currentPoint.y = hintedY.y;
+ }
+ for (uint16_t u = 0; u < pc->cpfx - 1; u++) { // Walk through points in spline
+ POINTFX pnt_b = currentPoint;//pc->apfx[u]; // B is always the current point
+ POINTFX pnt_c = pc->apfx[u+1];
+ POINTFX hintedY = hintedYs.next();
+ // only take the hinted y if it wasn't flipped
+ if (hintedYs.currentCurveType() == TT_PRIM_QSPLINE) {
+ pnt_c.y = hintedY.y;
+ }
+ currentPoint.x = pnt_c.x;
+ currentPoint.y = pnt_c.y;
+
+ if (u < pc->cpfx - 2) { // If not on last spline, compute C
+ pnt_c.x = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.x),
+ SkFIXEDToFixed(pnt_c.x)));
+ pnt_c.y = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.y),
+ SkFIXEDToFixed(pnt_c.y)));
+ }
+
+ path->quadTo(SkFixedToScalar( SkFIXEDToFixed(pnt_b.x)),
+ SkFixedToScalar(-SkFIXEDToFixed(pnt_b.y)),
+ SkFixedToScalar( SkFIXEDToFixed(pnt_c.x)),
+ SkFixedToScalar(-SkFIXEDToFixed(pnt_c.y)));
+ }
+ }
+ // Advance past this TTPOLYCURVE.
+ cur_poly += sizeof(WORD) * 2 + sizeof(POINTFX) * pc->cpfx;
+ }
+ cur_glyph += th->cb;
+ path->close();
+ }
+}
+
+DWORD SkScalerContext_Windows::getGDIGlyphPath(const SkGlyph& glyph, UINT flags,
+ SkAutoSTMalloc<BUFFERSIZE, uint8_t>* glyphbuf)
+{
+ GLYPHMETRICS gm;
+
+ DWORD total_size = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, BUFFERSIZE, glyphbuf->get(), &fMat22);
+ // Sometimes GetGlyphOutlineW returns a number larger than BUFFERSIZE even if BUFFERSIZE > 0.
+ // It has been verified that this does not involve a buffer overrun.
+ if (GDI_ERROR == total_size || total_size > BUFFERSIZE) {
+ // GDI_ERROR because the BUFFERSIZE was too small, or because the data was not accessible.
+ // When the data is not accessable GetGlyphOutlineW fails rather quickly,
+ // so just try to get the size. If that fails then ensure the data is accessible.
+ total_size = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, 0, NULL, &fMat22);
+ if (GDI_ERROR == total_size) {
+ LogFontTypeface::EnsureAccessible(this->getTypeface());
+ total_size = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, 0, NULL, &fMat22);
+ if (GDI_ERROR == total_size) {
+ SkASSERT(false);
+ return 0;
+ }
+ }
+
+ glyphbuf->reset(total_size);
+
+ DWORD ret = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, total_size, glyphbuf->get(), &fMat22);
+ if (GDI_ERROR == ret) {
+ LogFontTypeface::EnsureAccessible(this->getTypeface());
+ ret = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, total_size, glyphbuf->get(), &fMat22);
+ if (GDI_ERROR == ret) {
+ SkASSERT(false);
+ return 0;
+ }
+ }
+ }
+ return total_size;
+}
+
+void SkScalerContext_Windows::generatePath(const SkGlyph& glyph, SkPath* path) {
+ SkASSERT(&glyph && path);
+ SkASSERT(fDDC);
+
+ path->reset();
+
+ // Out of all the fonts on a typical Windows box,
+ // 25% of glyphs require more than 2KB.
+ // 1% of glyphs require more than 4KB.
+ // 0.01% of glyphs require more than 8KB.
+ // 8KB is less than 1% of the normal 1MB stack on Windows.
+ // Note that some web fonts glyphs require more than 20KB.
+ //static const DWORD BUFFERSIZE = (1 << 13);
+
+ //GDI only uses hinted outlines when axis aligned.
+ UINT format = GGO_NATIVE | GGO_GLYPH_INDEX;
+ if (fRec.getHinting() == SkPaint::kNo_Hinting || fRec.getHinting() == SkPaint::kSlight_Hinting){
+ format |= GGO_UNHINTED;
+ }
+ SkAutoSTMalloc<BUFFERSIZE, uint8_t> glyphbuf(BUFFERSIZE);
+ DWORD total_size = getGDIGlyphPath(glyph, format, &glyphbuf);
+ if (0 == total_size) {
+ return;
+ }
+
+ if (fRec.getHinting() != SkPaint::kSlight_Hinting) {
+ sk_path_from_gdi_path(path, glyphbuf, total_size);
+ } else {
+ //GDI only uses hinted outlines when axis aligned.
+ UINT format = GGO_NATIVE | GGO_GLYPH_INDEX;
+
+ SkAutoSTMalloc<BUFFERSIZE, uint8_t> hintedGlyphbuf(BUFFERSIZE);
+ DWORD hinted_total_size = getGDIGlyphPath(glyph, format, &hintedGlyphbuf);
+ if (0 == hinted_total_size) {
+ return;
+ }
+
+ sk_path_from_gdi_paths(path, glyphbuf, total_size,
+ GDIGlyphbufferPointIter(hintedGlyphbuf, hinted_total_size));
+ }
+}
+
static void logfont_for_name(const char* familyName, LOGFONT* lf) {
sk_bzero(lf, sizeof(LOGFONT));
#ifdef UNICODE
@@ -1795,31 +2107,31 @@
SkScalerContext::kAutohinting_Flag |
SkScalerContext::kEmbeddedBitmapText_Flag |
SkScalerContext::kEmbolden_Flag |
- SkScalerContext::kSubpixelPositioning_Flag |
SkScalerContext::kLCD_BGROrder_Flag |
SkScalerContext::kLCD_Vertical_Flag;
rec->fFlags &= ~flagsWeDontSupport;
SkPaint::Hinting h = rec->getHinting();
-
- // I think we can support no-hinting, if we get hires outlines and just
- // use skia to rasterize into a gray-scale mask...
-#if 0
switch (h) {
case SkPaint::kNo_Hinting:
+ break;
case SkPaint::kSlight_Hinting:
- h = SkPaint::kNo_Hinting;
+ // Only do slight hinting when axis aligned.
+ if (!isAxisAligned(*rec)) {
+ h = SkPaint::kNo_Hinting;
+ }
break;
case SkPaint::kNormal_Hinting:
case SkPaint::kFull_Hinting:
+ // TODO: need to be able to distinguish subpixel positioned glyphs
+ // and linear metrics.
+ //rec->fFlags &= ~SkScalerContext::kSubpixelPositioning_Flag;
h = SkPaint::kNormal_Hinting;
break;
default:
SkDEBUGFAIL("unknown hinting");
}
-#else
- h = SkPaint::kNormal_Hinting;
-#endif
+ //TODO: if this is a bitmap font, squash hinting and subpixel.
rec->setHinting(h);
// turn this off since GDI might turn A8 into BW! Need a bigger fix.
diff --git a/tools/generate_fir_coeff.py b/tools/generate_fir_coeff.py
new file mode 100644
index 0000000..70f521f
--- /dev/null
+++ b/tools/generate_fir_coeff.py
@@ -0,0 +1,119 @@
+#!/usr/bin/python
+
+'''
+Copyright 2013 Google Inc.
+
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+'''
+
+import math
+import pprint
+
+def withinStdDev(n):
+ """Returns the percent of samples within n std deviations of the normal."""
+ return math.erf(n / math.sqrt(2))
+
+def withinStdDevRange(a, b):
+ """Returns the percent of samples within the std deviation range a, b"""
+ if b < a:
+ return 0;
+
+ if a < 0:
+ if b < 0:
+ return (withinStdDev(-a) - withinStdDev(-b)) / 2;
+ else:
+ return (withinStdDev(-a) + withinStdDev(b)) / 2;
+ else:
+ return (withinStdDev(b) - withinStdDev(a)) / 2;
+
+
+#We have a bunch of smudged samples which represent the average coverage of a range.
+#We have a 'center' which may not line up with those samples.
+#From the 'center' we want to make a normal approximation where '5' sample width out we're at '3' std deviations.
+#The first and last samples may not be fully covered.
+
+#This is the sub-sample shift for each set of FIR coefficients (the centers of the lcds in the samples)
+#Each subpxl takes up 1/3 of a pixel, so they are centered at x=(i/n+1/2n), or 1/6, 3/6, 5/6 of a pixel.
+#Each sample takes up 1/4 of a pixel, so the results fall at (x*4)%1, or 2/3, 0, 1/3 of a sample.
+samples_per_pixel = 4
+subpxls_per_pixel = 3
+#sample_offsets is (frac, int) in sample units.
+sample_offsets = [math.modf((float(subpxl_index)/subpxls_per_pixel + 1.0/(2.0*subpxls_per_pixel))*samples_per_pixel) for subpxl_index in range(subpxls_per_pixel)]
+
+#How many samples to consider to the left and right of the subpxl center.
+sample_units_width = 5
+
+#The std deviation at sample_units_width.
+std_dev_max = 3
+
+#The target sum is in some fixed point representation.
+#Values larger the 1 in fixed point simulate ink spread.
+target_sum = 0x110
+
+for sample_offset, sample_align in sample_offsets:
+ coeffs = []
+ coeffs_rounded = []
+
+ #We start at sample_offset - sample_units_width
+ current_sample_left = sample_offset - sample_units_width
+ current_std_dev_left = -std_dev_max
+
+ done = False
+ while not done:
+ current_sample_right = math.floor(current_sample_left + 1)
+ if current_sample_right > sample_offset + sample_units_width:
+ done = True
+ current_sample_right = sample_offset + sample_units_width
+ current_std_dev_right = current_std_dev_left + ((current_sample_right - current_sample_left) / sample_units_width) * std_dev_max
+
+ coverage = withinStdDevRange(current_std_dev_left, current_std_dev_right)
+ coeffs.append(coverage * target_sum)
+ coeffs_rounded.append(int(round(coverage * target_sum)))
+
+ current_sample_left = current_sample_right
+ current_std_dev_left = current_std_dev_right
+
+ # Now we have the numbers we want, but our rounding needs to add up to target_sum.
+ delta = 0
+ coeffs_rounded_sum = sum(coeffs_rounded)
+ if coeffs_rounded_sum > target_sum:
+ # The coeffs add up to too much. Subtract 1 from the ones which were rounded up the most.
+ delta = -1
+
+ if coeffs_rounded_sum < target_sum:
+ # The coeffs add up to too little. Add 1 to the ones which were rounded down the most.
+ delta = 1
+
+ if delta:
+ print "Initial sum is 0x%0.2X, adjusting." % (coeffs_rounded_sum,)
+ coeff_diff = [(coeff_rounded - coeff) * delta
+ for coeff, coeff_rounded in zip(coeffs, coeffs_rounded)]
+
+ class IndexTracker:
+ def __init__(self, index, item):
+ self.index = index
+ self.item = item
+ def __lt__(self, other):
+ return self.item < other.item
+ def __repr__(self):
+ return "arr[%d] == %s" % (self.index, repr(self.item))
+
+ coeff_pkg = [IndexTracker(i, diff) for i, diff in enumerate(coeff_diff)]
+ coeff_pkg.sort()
+
+ # num_elements_to_force_round had better be < (2 * sample_units_width + 1) or
+ # * our math was wildy wrong
+ # * an awful lot of the curve is out side our sample
+ # either is pretty bad, and probably means the results will not be useful.
+ num_elements_to_force_round = abs(coeffs_rounded_sum - target_sum)
+ for i in xrange(num_elements_to_force_round):
+ print "Adding %d to index %d to force round %f." % (delta, coeff_pkg[i].index, coeffs[coeff_pkg[i].index])
+ coeffs_rounded[coeff_pkg[i].index] += delta
+
+ print "Prepending %d 0x00 for allignment." % (sample_align,)
+ coeffs_rounded_aligned = ([0] * int(sample_align)) + coeffs_rounded
+
+ print ', '.join(["0x%0.2X" % coeff_rounded for coeff_rounded in coeffs_rounded_aligned])
+ print sum(coeffs), hex(sum(coeffs_rounded))
+ print