Improve the dynamic range of SkPackedGlyphID ctor

The previous version only translated the sub-position reliably
if the position < 2^10. This code extends the range to 2^21.

Change-Id: I7fcdfe2c3f7336ac0788e282c2bd0fc73067d8fb
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/253101
Reviewed-by: Ben Wagner <bungeman@google.com>
Commit-Queue: Herb Derby <herb@google.com>
This commit is contained in:
Herb Derby 2019-11-06 13:04:54 -05:00 committed by Skia Commit-Bot
parent 2524f1df01
commit f7ce19e773
2 changed files with 50 additions and 13 deletions

View File

@ -104,23 +104,39 @@ struct SkPackedGlyphID {
private:
static constexpr uint32_t PackIDSubXSubY(SkGlyphID glyphID, uint32_t x, uint32_t y) {
SkASSERT(x < 4);
SkASSERT(y < 4);
SkASSERT(x < (1u << kSubPixelPosLen));
SkASSERT(y < (1u << kSubPixelPosLen));
return (x << kSubPixelX) | (y << kSubPixelY) | (glyphID << kGlyphID);
}
// Assumptions: pt is properly rounded. mask is set for the x or y fields.
//
// A sub-pixel field is a number on the interval [2^kSubPixel, 2^(kSubPixel + kSubPixelPosLen)).
// Where kSubPixel is either kSubPixelX or kSubPixelY. Given a number x on [0, 1) we can
// generate a sub-pixel field using:
// sub-pixel-field = x * 2^(kSubPixel + kSubPixelPosLen)
//
// We can generate the integer sub-pixel field by &-ing the integer part of sub-filed with the
// sub-pixel field mask.
// int-sub-pixel-field = int(sub-pixel-field) & (kSubPixelPosMask << kSubPixel)
//
// The last trick is to extend the range from [0, 1) to [0, 2). The extend range is
// necessary because the modulo 1 calculation (pt - floor(pt)) generates numbers on [-1, 1).
// This does not round (floor) properly when converting to integer. Adding one to the range
// causes truncation and floor to be the same. Coincidentally, masking to produce the field also
// removes the +1.
static uint32_t PackIDSkPoint(SkGlyphID glyphID, SkPoint pt, SkIPoint mask) {
using namespace skvx;
using XY = Vec<2, float>;
// The 4.f moves 1/2 bit and 1/4 bit into the 2 and 1 bits. The shift move those two bits
// into the right place in the masks.
const XY magic = {4.f * (1u << kSubPixelX), 4.f * (1u << kSubPixelY)};
using SubXY = Vec<2, int>;
const XY magic = {1.f * (1u << (kSubPixelPosLen + kSubPixelX)),
1.f * (1u << (kSubPixelPosLen + kSubPixelY))};
XY pos{pt.x(), pt.y()};
Vec<2, int> sub = cast<int>(floor(pos * magic)) & Vec<2, int> {mask.x(), mask.y()};
SkASSERT(sub[0] / (1u << kSubPixelX) < 4);
SkASSERT(sub[1] / (1u << kSubPixelY) < 4);
XY subPos = (pos - floor(pos)) + 1.0f;
SubXY sub = cast<int>(subPos * magic) & SubXY{mask.x(), mask.y()};
SkASSERT(sub[0] / (1u << kSubPixelX) < (1u << kSubPixelPosLen));
SkASSERT(sub[1] / (1u << kSubPixelY) < (1u << kSubPixelPosLen));
return (glyphID << kGlyphID) | sub[0] | sub[1];
}

View File

@ -31,6 +31,23 @@ DEF_TEST(SkPackedGlyphIDCtor, reporter) {
}
}
{
// No subpixel positioning.
auto roundingSpec = SkGlyphPositionRoundingSpec(false, kNone_SkAxisAlignment);
SkIPoint mask = roundingSpec.ignorePositionFieldMask;
for (int y = -32; y < 33; y++) {
for (int x = -32; x < 33; x++) {
float fx = x * step, fy = y * step;
SkPoint roundedPos = SkPoint{fx, fy} + roundingSpec.halfAxisSampleFreq;
SkPackedGlyphID packedID{3, roundedPos, mask};
uint32_t subX = ((x + 8) & 0b1100u) >> 4;
uint32_t subY = ((y + 8) & 0b1100u) >> 4;
SkPackedGlyphID correctID(3, subX, subY);
REPORTER_ASSERT(reporter, packedID == correctID);
}
}
}
{
// Subpixel with no axis snapping.
auto roundingSpec = SkGlyphPositionRoundingSpec(true, kNone_SkAxisAlignment);
@ -49,16 +66,20 @@ DEF_TEST(SkPackedGlyphIDCtor, reporter) {
}
{
// No subpixel positioning.
auto roundingSpec = SkGlyphPositionRoundingSpec(false, kNone_SkAxisAlignment);
// Test dynamic range by transposing a large distance.
// Floating point numbers have 24 bits of precision. The largest distance is 24 - 2 (for
// sub-pixel) - 1 (for truncation to floor trick in the code). This leaves 21 bits. Large
// Distance is 2^21 - 2 (because the test is on the interval [-2, 2).
auto roundingSpec = SkGlyphPositionRoundingSpec(true, kNone_SkAxisAlignment);
SkIPoint mask = roundingSpec.ignorePositionFieldMask;
const int64_t kLargeDistance = (1ull << 21) - 2;
for (int y = -32; y < 33; y++) {
for (int x = -32; x < 33; x++) {
float fx = x * step, fy = y * step;
float fx = x * step + kLargeDistance, fy = y * step + kLargeDistance;
SkPoint roundedPos = SkPoint{fx, fy} + roundingSpec.halfAxisSampleFreq;
SkPackedGlyphID packedID{3, roundedPos, mask};
uint32_t subX = ((x + 8) & 0b1100u) >> 4;
uint32_t subY = ((y + 8) & 0b1100u) >> 4;
uint32_t subX = ((x + 2) & 0b1100u) >> 2;
uint32_t subY = ((y + 2) & 0b1100u) >> 2;
SkPackedGlyphID correctID(3, subX, subY);
REPORTER_ASSERT(reporter, packedID == correctID);
}