void GrTextUtils::BmpAppendGlyph(GrAtlasTextBlob* blob, int runIndex, GrBatchFontCache* fontCache, GrBatchTextStrike** strike, const SkGlyph& skGlyph, int vx, int vy, GrColor color, GrFontScaler* scaler) { if (!*strike) { *strike = fontCache->getStrike(scaler); } GrGlyph::PackedID id = GrGlyph::Pack(skGlyph.getGlyphID(), skGlyph.getSubXFixed(), skGlyph.getSubYFixed(), GrGlyph::kCoverage_MaskStyle); GrGlyph* glyph = (*strike)->getGlyph(skGlyph, id, scaler); if (!glyph) { return; } int x = vx + glyph->fBounds.fLeft; int y = vy + glyph->fBounds.fTop; // keep them as ints until we've done the clip-test int width = glyph->fBounds.width(); int height = glyph->fBounds.height(); SkRect r; r.fLeft = SkIntToScalar(x); r.fTop = SkIntToScalar(y); r.fRight = r.fLeft + SkIntToScalar(width); r.fBottom = r.fTop + SkIntToScalar(height); blob->appendGlyph(runIndex, r, color, *strike, glyph, scaler, skGlyph, SkIntToScalar(vx), SkIntToScalar(vy), 1.0f, false); }
bool GrTextUtils::DfAppendGlyph(GrAtlasTextBlob* blob, int runIndex, GrBatchFontCache* cache, GrBatchTextStrike** strike, const SkGlyph& skGlyph, SkScalar sx, SkScalar sy, GrColor color, GrFontScaler* scaler, SkScalar textRatio, const SkMatrix& viewMatrix) { if (!*strike) { *strike = cache->getStrike(scaler); } GrGlyph::PackedID id = GrGlyph::Pack(skGlyph.getGlyphID(), skGlyph.getSubXFixed(), skGlyph.getSubYFixed(), GrGlyph::kDistance_MaskStyle); GrGlyph* glyph = (*strike)->getGlyph(skGlyph, id, scaler); if (!glyph) { return true; } // fallback to color glyph support if (kA8_GrMaskFormat != glyph->fMaskFormat) { return false; } SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft + SK_DistanceFieldInset); SkScalar dy = SkIntToScalar(glyph->fBounds.fTop + SK_DistanceFieldInset); SkScalar width = SkIntToScalar(glyph->fBounds.width() - 2 * SK_DistanceFieldInset); SkScalar height = SkIntToScalar(glyph->fBounds.height() - 2 * SK_DistanceFieldInset); SkScalar scale = textRatio; dx *= scale; dy *= scale; width *= scale; height *= scale; sx += dx; sy += dy; SkRect glyphRect = SkRect::MakeXYWH(sx, sy, width, height); blob->appendGlyph(runIndex, glyphRect, color, *strike, glyph, scaler, skGlyph, sx - dx, sy - dy, scale, true); return true; }
void SkScalerContext::internalGetPath(const SkGlyph& glyph, SkPath* fillPath, SkPath* devPath, SkMatrix* fillToDevMatrix) { SkPath path; this->getGlyphContext(glyph)->generatePath(glyph, &path); if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) { SkFixed dx = glyph.getSubXFixed(); SkFixed dy = glyph.getSubYFixed(); if (dx | dy) { path.offset(SkFixedToScalar(dx), SkFixedToScalar(dy)); } } if (fRec.fFrameWidth > 0 || fPathEffect != NULL) { // need the path in user-space, with only the point-size applied // so that our stroking and effects will operate the same way they // would if the user had extracted the path themself, and then // called drawPath SkPath localPath; SkMatrix matrix, inverse; fRec.getMatrixFrom2x2(&matrix); matrix.invert(&inverse); path.transform(inverse, &localPath); // now localPath is only affected by the paint settings, and not the canvas matrix SkScalar width = fRec.fFrameWidth; if (fPathEffect) { SkPath effectPath; if (fPathEffect->filterPath(&effectPath, localPath, &width)) { localPath.swap(effectPath); } } if (width > 0) { SkStroke stroker; SkPath outline; stroker.setWidth(width); stroker.setMiterLimit(fRec.fMiterLimit); stroker.setJoin((SkPaint::Join)fRec.fStrokeJoin); stroker.setDoFill(SkToBool(fRec.fFlags & kFrameAndFill_Flag)); stroker.strokePath(localPath, &outline); localPath.swap(outline); } // now return stuff to the caller if (fillToDevMatrix) { *fillToDevMatrix = matrix; } if (devPath) { localPath.transform(matrix, devPath); } if (fillPath) { fillPath->swap(localPath); } } else { // nothing tricky to do if (fillToDevMatrix) { fillToDevMatrix->reset(); } if (devPath) { if (fillPath == NULL) { devPath->swap(path); } else { *devPath = path; } } if (fillPath) { fillPath->swap(path); } } if (devPath) { devPath->updateBoundsCache(); } if (fillPath) { fillPath->updateBoundsCache(); } }
void SkScalerContext::internalGetPath(const SkGlyph& glyph, SkPath* fillPath, SkPath* devPath, SkMatrix* fillToDevMatrix) { SkPath path; generatePath(glyph, &path); if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) { SkFixed dx = glyph.getSubXFixed(); SkFixed dy = glyph.getSubYFixed(); if (dx | dy) { path.offset(SkFixedToScalar(dx), SkFixedToScalar(dy)); } } if (fRec.fFrameWidth > 0 || fPathEffect != nullptr) { // need the path in user-space, with only the point-size applied // so that our stroking and effects will operate the same way they // would if the user had extracted the path themself, and then // called drawPath SkPath localPath; SkMatrix matrix, inverse; fRec.getMatrixFrom2x2(&matrix); if (!matrix.invert(&inverse)) { // assume fillPath and devPath are already empty. return; } path.transform(inverse, &localPath); // now localPath is only affected by the paint settings, and not the canvas matrix SkStrokeRec rec(SkStrokeRec::kFill_InitStyle); if (fRec.fFrameWidth > 0) { rec.setStrokeStyle(fRec.fFrameWidth, SkToBool(fRec.fFlags & kFrameAndFill_Flag)); // glyphs are always closed contours, so cap type is ignored, // so we just pass something. rec.setStrokeParams((SkPaint::Cap)fRec.fStrokeCap, (SkPaint::Join)fRec.fStrokeJoin, fRec.fMiterLimit); } if (fPathEffect) { SkPath effectPath; if (fPathEffect->filterPath(&effectPath, localPath, &rec, nullptr)) { localPath.swap(effectPath); } } if (rec.needToApply()) { SkPath strokePath; if (rec.applyToPath(&strokePath, localPath)) { localPath.swap(strokePath); } } // now return stuff to the caller if (fillToDevMatrix) { *fillToDevMatrix = matrix; } if (devPath) { localPath.transform(matrix, devPath); } if (fillPath) { fillPath->swap(localPath); } } else { // nothing tricky to do if (fillToDevMatrix) { fillToDevMatrix->reset(); } if (devPath) { if (fillPath == nullptr) { devPath->swap(path); } else { *devPath = path; } } if (fillPath) { fillPath->swap(path); } } if (devPath) { devPath->updateBoundsCache(); } if (fillPath) { fillPath->updateBoundsCache(); } }
void SkScalerContext_FreeType_Base::generateGlyphImage( FT_Face face, const SkGlyph& glyph, const SkMatrix& bitmapTransform) { const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag); const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag); switch ( face->glyph->format ) { case FT_GLYPH_FORMAT_OUTLINE: { FT_Outline* outline = &face->glyph->outline; int dx = 0, dy = 0; if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) { dx = SkFixedToFDot6(glyph.getSubXFixed()); dy = SkFixedToFDot6(glyph.getSubYFixed()); // negate dy since freetype-y-goes-up and skia-y-goes-down dy = -dy; } memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); if (SkMask::kLCD16_Format == glyph.fMaskFormat) { FT_Outline_Translate(outline, dx, dy); FT_Error err = FT_Render_Glyph(face->glyph, doVert ? FT_RENDER_MODE_LCD_V : FT_RENDER_MODE_LCD); if (err) { SK_TRACEFTR(err, "Could not render glyph."); return; } SkMask mask; glyph.toMask(&mask); #ifdef SK_SHOW_TEXT_BLIT_COVERAGE memset(mask.fImage, 0x80, mask.fBounds.height() * mask.fRowBytes); #endif FT_GlyphSlotRec& ftGlyph = *face->glyph; if (!SkIRect::Intersects(mask.fBounds, SkIRect::MakeXYWH( ftGlyph.bitmap_left, -ftGlyph.bitmap_top, ftGlyph.bitmap.width, ftGlyph.bitmap.rows))) { return; } // If the FT_Bitmap extent is larger, discard bits of the bitmap outside the mask. // If the SkMask extent is larger, shrink mask to fit bitmap (clearing discarded). unsigned char* origBuffer = ftGlyph.bitmap.buffer; // First align the top left (origin). if (-ftGlyph.bitmap_top < mask.fBounds.fTop) { int32_t topDiff = mask.fBounds.fTop - (-ftGlyph.bitmap_top); ftGlyph.bitmap.buffer += ftGlyph.bitmap.pitch * topDiff; ftGlyph.bitmap.rows -= topDiff; ftGlyph.bitmap_top = -mask.fBounds.fTop; } if (ftGlyph.bitmap_left < mask.fBounds.fLeft) { int32_t leftDiff = mask.fBounds.fLeft - ftGlyph.bitmap_left; ftGlyph.bitmap.buffer += leftDiff; ftGlyph.bitmap.width -= leftDiff; ftGlyph.bitmap_left = mask.fBounds.fLeft; } if (mask.fBounds.fTop < -ftGlyph.bitmap_top) { mask.fImage += mask.fRowBytes * (-ftGlyph.bitmap_top - mask.fBounds.fTop); mask.fBounds.fTop = -ftGlyph.bitmap_top; } if (mask.fBounds.fLeft < ftGlyph.bitmap_left) { mask.fImage += sizeof(uint16_t) * (ftGlyph.bitmap_left - mask.fBounds.fLeft); mask.fBounds.fLeft = ftGlyph.bitmap_left; } // Origins aligned, clean up the width and height. int ftVertScale = (doVert ? 3 : 1); int ftHoriScale = (doVert ? 1 : 3); if (mask.fBounds.height() * ftVertScale < SkToInt(ftGlyph.bitmap.rows)) { ftGlyph.bitmap.rows = mask.fBounds.height() * ftVertScale; } if (mask.fBounds.width() * ftHoriScale < SkToInt(ftGlyph.bitmap.width)) { ftGlyph.bitmap.width = mask.fBounds.width() * ftHoriScale; } if (SkToInt(ftGlyph.bitmap.rows) < mask.fBounds.height() * ftVertScale) { mask.fBounds.fBottom = mask.fBounds.fTop + ftGlyph.bitmap.rows / ftVertScale; } if (SkToInt(ftGlyph.bitmap.width) < mask.fBounds.width() * ftHoriScale) { mask.fBounds.fRight = mask.fBounds.fLeft + ftGlyph.bitmap.width / ftHoriScale; } if (fPreBlend.isApplicable()) { copyFT2LCD16<true>(ftGlyph.bitmap, mask, doBGR, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } else { copyFT2LCD16<false>(ftGlyph.bitmap, mask, doBGR, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } // Restore the buffer pointer so FreeType can properly free it. ftGlyph.bitmap.buffer = origBuffer; } else { FT_BBox bbox; FT_Bitmap target; FT_Outline_Get_CBox(outline, &bbox); /* what we really want to do for subpixel is offset(dx, dy) compute_bounds offset(bbox & !63) but that is two calls to offset, so we do the following, which achieves the same thing with only one offset call. */ FT_Outline_Translate(outline, dx - ((bbox.xMin + dx) & ~63), dy - ((bbox.yMin + dy) & ~63)); target.width = glyph.fWidth; target.rows = glyph.fHeight; target.pitch = glyph.rowBytes(); target.buffer = reinterpret_cast<uint8_t*>(glyph.fImage); target.pixel_mode = compute_pixel_mode( (SkMask::Format)fRec.fMaskFormat); target.num_grays = 256; FT_Outline_Get_Bitmap(face->glyph->library, outline, &target); #ifdef SK_SHOW_TEXT_BLIT_COVERAGE for (int y = 0; y < glyph.fHeight; ++y) { for (int x = 0; x < glyph.fWidth; ++x) { uint8_t& a = ((uint8_t*)glyph.fImage)[(glyph.rowBytes() * y) + x]; a = SkTMax<uint8_t>(a, 0x20); } } #endif } } break; case FT_GLYPH_FORMAT_BITMAP: { FT_Pixel_Mode pixel_mode = static_cast<FT_Pixel_Mode>(face->glyph->bitmap.pixel_mode); SkMask::Format maskFormat = static_cast<SkMask::Format>(glyph.fMaskFormat); // Assume that the other formats do not exist. SkASSERT(FT_PIXEL_MODE_MONO == pixel_mode || FT_PIXEL_MODE_GRAY == pixel_mode || FT_PIXEL_MODE_BGRA == pixel_mode); // These are the only formats this ScalerContext should request. SkASSERT(SkMask::kBW_Format == maskFormat || SkMask::kA8_Format == maskFormat || SkMask::kARGB32_Format == maskFormat || SkMask::kLCD16_Format == maskFormat); // If no scaling needed, directly copy glyph bitmap. if (bitmapTransform.isIdentity()) { SkMask dstMask; glyph.toMask(&dstMask); copyFTBitmap(face->glyph->bitmap, dstMask); break; } // Otherwise, scale the bitmap. // Copy the FT_Bitmap into an SkBitmap (either A8 or ARGB) SkBitmap unscaledBitmap; // TODO: mark this as sRGB when the blits will be sRGB. unscaledBitmap.allocPixels(SkImageInfo::Make(face->glyph->bitmap.width, face->glyph->bitmap.rows, SkColorType_for_FTPixelMode(pixel_mode), kPremul_SkAlphaType)); SkMask unscaledBitmapAlias; unscaledBitmapAlias.fImage = reinterpret_cast<uint8_t*>(unscaledBitmap.getPixels()); unscaledBitmapAlias.fBounds.set(0, 0, unscaledBitmap.width(), unscaledBitmap.height()); unscaledBitmapAlias.fRowBytes = unscaledBitmap.rowBytes(); unscaledBitmapAlias.fFormat = SkMaskFormat_for_SkColorType(unscaledBitmap.colorType()); copyFTBitmap(face->glyph->bitmap, unscaledBitmapAlias); // Wrap the glyph's mask in a bitmap, unless the glyph's mask is BW or LCD. // BW requires an A8 target for resizing, which can then be down sampled. // LCD should use a 4x A8 target, which will then be down sampled. // For simplicity, LCD uses A8 and is replicated. int bitmapRowBytes = 0; if (SkMask::kBW_Format != maskFormat && SkMask::kLCD16_Format != maskFormat) { bitmapRowBytes = glyph.rowBytes(); } SkBitmap dstBitmap; // TODO: mark this as sRGB when the blits will be sRGB. dstBitmap.setInfo(SkImageInfo::Make(glyph.fWidth, glyph.fHeight, SkColorType_for_SkMaskFormat(maskFormat), kPremul_SkAlphaType), bitmapRowBytes); if (SkMask::kBW_Format == maskFormat || SkMask::kLCD16_Format == maskFormat) { dstBitmap.allocPixels(); } else { dstBitmap.setPixels(glyph.fImage); } // Scale unscaledBitmap into dstBitmap. SkCanvas canvas(dstBitmap); #ifdef SK_SHOW_TEXT_BLIT_COVERAGE canvas.clear(0x33FF0000); #else canvas.clear(SK_ColorTRANSPARENT); #endif canvas.translate(-glyph.fLeft, -glyph.fTop); canvas.concat(bitmapTransform); canvas.translate(face->glyph->bitmap_left, -face->glyph->bitmap_top); SkPaint paint; paint.setFilterQuality(kMedium_SkFilterQuality); canvas.drawBitmap(unscaledBitmap, 0, 0, &paint); // If the destination is BW or LCD, convert from A8. if (SkMask::kBW_Format == maskFormat) { // Copy the A8 dstBitmap into the A1 glyph.fImage. SkMask dstMask; glyph.toMask(&dstMask); packA8ToA1(dstMask, dstBitmap.getAddr8(0, 0), dstBitmap.rowBytes()); } else if (SkMask::kLCD16_Format == maskFormat) { // Copy the A8 dstBitmap into the LCD16 glyph.fImage. uint8_t* src = dstBitmap.getAddr8(0, 0); uint16_t* dst = reinterpret_cast<uint16_t*>(glyph.fImage); for (int y = dstBitmap.height(); y --> 0;) { for (int x = 0; x < dstBitmap.width(); ++x) { dst[x] = grayToRGB16(src[x]); } dst = (uint16_t*)((char*)dst + glyph.rowBytes()); src += dstBitmap.rowBytes(); } } } break; default: SkDEBUGFAIL("unknown glyph format"); memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); return; } // We used to always do this pre-USE_COLOR_LUMINANCE, but with colorlum, // it is optional #if defined(SK_GAMMA_APPLY_TO_A8) if (SkMask::kA8_Format == glyph.fMaskFormat && fPreBlend.isApplicable()) { 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] = fPreBlend.fG[dst[x]]; } dst += rowBytes; } } #endif }
void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face, const SkGlyph& glyph) { const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag); const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag); switch ( face->glyph->format ) { case FT_GLYPH_FORMAT_OUTLINE: { FT_Outline* outline = &face->glyph->outline; FT_BBox bbox; FT_Bitmap target; if (fRec.fFlags & SkScalerContext::kEmbolden_Flag) { emboldenOutline(face, outline); } int dx = 0, dy = 0; if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) { dx = SkFixedToFDot6(glyph.getSubXFixed()); dy = SkFixedToFDot6(glyph.getSubYFixed()); // negate dy since freetype-y-goes-up and skia-y-goes-down dy = -dy; } FT_Outline_Get_CBox(outline, &bbox); /* what we really want to do for subpixel is offset(dx, dy) compute_bounds offset(bbox & !63) but that is two calls to offset, so we do the following, which achieves the same thing with only one offset call. */ FT_Outline_Translate(outline, dx - ((bbox.xMin + dx) & ~63), dy - ((bbox.yMin + dy) & ~63)); if (SkMask::kLCD16_Format == glyph.fMaskFormat) { FT_Render_Glyph(face->glyph, doVert ? FT_RENDER_MODE_LCD_V : FT_RENDER_MODE_LCD); if (fPreBlend.isApplicable()) { copyFT2LCD16<true>(glyph, face->glyph->bitmap, doBGR, doVert, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } else { copyFT2LCD16<false>(glyph, face->glyph->bitmap, doBGR, doVert, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } } else { target.width = glyph.fWidth; target.rows = glyph.fHeight; target.pitch = glyph.rowBytes(); target.buffer = reinterpret_cast<uint8_t*>(glyph.fImage); target.pixel_mode = compute_pixel_mode( (SkMask::Format)fRec.fMaskFormat); target.num_grays = 256; memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); FT_Outline_Get_Bitmap(face->glyph->library, outline, &target); } } break; case FT_GLYPH_FORMAT_BITMAP: { if (fRec.fFlags & SkScalerContext::kEmbolden_Flag) { FT_GlyphSlot_Own_Bitmap(face->glyph); FT_Bitmap_Embolden(face->glyph->library, &face->glyph->bitmap, kBitmapEmboldenStrength, 0); } SkASSERT_CONTINUE(glyph.fWidth == face->glyph->bitmap.width); SkASSERT_CONTINUE(glyph.fHeight == face->glyph->bitmap.rows); SkASSERT_CONTINUE(glyph.fTop == -face->glyph->bitmap_top); SkASSERT_CONTINUE(glyph.fLeft == face->glyph->bitmap_left); const uint8_t* src = (const uint8_t*)face->glyph->bitmap.buffer; uint8_t* dst = (uint8_t*)glyph.fImage; if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY || (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO && glyph.fMaskFormat == SkMask::kBW_Format)) { unsigned srcRowBytes = face->glyph->bitmap.pitch; unsigned dstRowBytes = glyph.rowBytes(); unsigned minRowBytes = SkMin32(srcRowBytes, dstRowBytes); unsigned extraRowBytes = dstRowBytes - minRowBytes; for (int y = face->glyph->bitmap.rows - 1; y >= 0; --y) { memcpy(dst, src, minRowBytes); memset(dst + minRowBytes, 0, extraRowBytes); src += srcRowBytes; dst += dstRowBytes; } } else if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO && glyph.fMaskFormat == SkMask::kA8_Format) { for (int y = 0; y < face->glyph->bitmap.rows; ++y) { uint8_t byte = 0; int bits = 0; const uint8_t* src_row = src; uint8_t* dst_row = dst; for (int x = 0; x < face->glyph->bitmap.width; ++x) { if (!bits) { byte = *src_row++; bits = 8; } *dst_row++ = byte & 0x80 ? 0xff : 0; bits--; byte <<= 1; } src += face->glyph->bitmap.pitch; dst += glyph.rowBytes(); } } else if (SkMask::kLCD16_Format == glyph.fMaskFormat) { if (fPreBlend.isApplicable()) { copyFT2LCD16<true>(glyph, face->glyph->bitmap, doBGR, doVert, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } else { copyFT2LCD16<false>(glyph, face->glyph->bitmap, doBGR, doVert, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } } else { SkDEBUGFAIL("unknown glyph bitmap transform needed"); } } break; default: SkDEBUGFAIL("unknown glyph format"); memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); return; } // We used to always do this pre-USE_COLOR_LUMINANCE, but with colorlum, // it is optional #if defined(SK_GAMMA_APPLY_TO_A8) if (SkMask::kA8_Format == glyph.fMaskFormat && fPreBlend.isApplicable()) { 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] = fPreBlend.fG[dst[x]]; } dst += rowBytes; } } #endif }
void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face, const SkGlyph& glyph) { const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag); const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag); switch ( face->glyph->format ) { case FT_GLYPH_FORMAT_OUTLINE: { FT_Outline* outline = &face->glyph->outline; FT_BBox bbox; FT_Bitmap target; if (fRec.fFlags & SkScalerContext::kEmbolden_Flag && !(face->style_flags & FT_STYLE_FLAG_BOLD)) { emboldenOutline(face, outline); } int dx = 0, dy = 0; if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) { dx = SkFixedToFDot6(glyph.getSubXFixed()); dy = SkFixedToFDot6(glyph.getSubYFixed()); // negate dy since freetype-y-goes-up and skia-y-goes-down dy = -dy; } FT_Outline_Get_CBox(outline, &bbox); /* what we really want to do for subpixel is offset(dx, dy) compute_bounds offset(bbox & !63) but that is two calls to offset, so we do the following, which achieves the same thing with only one offset call. */ FT_Outline_Translate(outline, dx - ((bbox.xMin + dx) & ~63), dy - ((bbox.yMin + dy) & ~63)); if (SkMask::kLCD16_Format == glyph.fMaskFormat) { FT_Render_Glyph(face->glyph, doVert ? FT_RENDER_MODE_LCD_V : FT_RENDER_MODE_LCD); SkMask mask; glyph.toMask(&mask); if (fPreBlend.isApplicable()) { copyFT2LCD16<true>(face->glyph->bitmap, mask, doBGR, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } else { copyFT2LCD16<false>(face->glyph->bitmap, mask, doBGR, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } } else { target.width = glyph.fWidth; target.rows = glyph.fHeight; target.pitch = glyph.rowBytes(); target.buffer = reinterpret_cast<uint8_t*>(glyph.fImage); target.pixel_mode = compute_pixel_mode( (SkMask::Format)fRec.fMaskFormat); target.num_grays = 256; memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); FT_Outline_Get_Bitmap(face->glyph->library, outline, &target); } } break; case FT_GLYPH_FORMAT_BITMAP: { FT_Pixel_Mode pixel_mode = static_cast<FT_Pixel_Mode>(face->glyph->bitmap.pixel_mode); SkMask::Format maskFormat = static_cast<SkMask::Format>(glyph.fMaskFormat); // Assume that the other formats do not exist. SkASSERT(FT_PIXEL_MODE_MONO == pixel_mode || FT_PIXEL_MODE_GRAY == pixel_mode || FT_PIXEL_MODE_BGRA == pixel_mode); // These are the only formats this ScalerContext should request. SkASSERT(SkMask::kBW_Format == maskFormat || SkMask::kA8_Format == maskFormat || SkMask::kARGB32_Format == maskFormat || SkMask::kLCD16_Format == maskFormat); if (fRec.fFlags & SkScalerContext::kEmbolden_Flag && !(face->style_flags & FT_STYLE_FLAG_BOLD)) { FT_GlyphSlot_Own_Bitmap(face->glyph); FT_Bitmap_Embolden(face->glyph->library, &face->glyph->bitmap, kBitmapEmboldenStrength, 0); } // If no scaling needed, directly copy glyph bitmap. if (glyph.fWidth == face->glyph->bitmap.width && glyph.fHeight == face->glyph->bitmap.rows && glyph.fTop == -face->glyph->bitmap_top && glyph.fLeft == face->glyph->bitmap_left) { SkMask dstMask; glyph.toMask(&dstMask); copyFTBitmap(face->glyph->bitmap, dstMask); break; } // Otherwise, scale the bitmap. // Copy the FT_Bitmap into an SkBitmap (either A8 or ARGB) SkBitmap unscaledBitmap; unscaledBitmap.setConfig(SkBitmapConfig_for_FTPixelMode(pixel_mode), face->glyph->bitmap.width, face->glyph->bitmap.rows); unscaledBitmap.allocPixels(); SkMask unscaledBitmapAlias; unscaledBitmapAlias.fImage = reinterpret_cast<uint8_t*>(unscaledBitmap.getPixels()); unscaledBitmapAlias.fBounds.set(0, 0, unscaledBitmap.width(), unscaledBitmap.height()); unscaledBitmapAlias.fRowBytes = unscaledBitmap.rowBytes(); unscaledBitmapAlias.fFormat = SkMaskFormat_for_SkBitmapConfig(unscaledBitmap.config()); copyFTBitmap(face->glyph->bitmap, unscaledBitmapAlias); // Wrap the glyph's mask in a bitmap, unless the glyph's mask is BW or LCD. // BW requires an A8 target for resizing, which can then be down sampled. // LCD should use a 4x A8 target, which will then be down sampled. // For simplicity, LCD uses A8 and is replicated. int bitmapRowBytes = 0; if (SkMask::kBW_Format != maskFormat && SkMask::kLCD16_Format != maskFormat) { bitmapRowBytes = glyph.rowBytes(); } SkBitmap dstBitmap; dstBitmap.setConfig(SkBitmapConfig_for_SkMaskFormat(maskFormat), glyph.fWidth, glyph.fHeight, bitmapRowBytes); if (SkMask::kBW_Format == maskFormat || SkMask::kLCD16_Format == maskFormat) { dstBitmap.allocPixels(); } else { dstBitmap.setPixels(glyph.fImage); } // Scale unscaledBitmap into dstBitmap. SkCanvas canvas(dstBitmap); canvas.clear(SK_ColorTRANSPARENT); canvas.scale(SkIntToScalar(glyph.fWidth) / SkIntToScalar(face->glyph->bitmap.width), SkIntToScalar(glyph.fHeight) / SkIntToScalar(face->glyph->bitmap.rows)); SkPaint paint; paint.setFilterLevel(SkPaint::kLow_FilterLevel); canvas.drawBitmap(unscaledBitmap, 0, 0, &paint); // If the destination is BW or LCD, convert from A8. if (SkMask::kBW_Format == maskFormat) { // Copy the A8 dstBitmap into the A1 glyph.fImage. SkMask dstMask; glyph.toMask(&dstMask); packA8ToA1(dstMask, dstBitmap.getAddr8(0, 0), dstBitmap.rowBytes()); } else if (SkMask::kLCD16_Format == maskFormat) { // Copy the A8 dstBitmap into the LCD16 glyph.fImage. uint8_t* src = dstBitmap.getAddr8(0, 0); uint16_t* dst = reinterpret_cast<uint16_t*>(glyph.fImage); for (int y = dstBitmap.height(); y --> 0;) { for (int x = 0; x < dstBitmap.width(); ++x) { dst[x] = grayToRGB16(src[x]); } dst = (uint16_t*)((char*)dst + glyph.rowBytes()); src += dstBitmap.rowBytes(); } } } break; default: SkDEBUGFAIL("unknown glyph format"); memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); return; } // We used to always do this pre-USE_COLOR_LUMINANCE, but with colorlum, // it is optional #if defined(SK_GAMMA_APPLY_TO_A8) if (SkMask::kA8_Format == glyph.fMaskFormat && fPreBlend.isApplicable()) { 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] = fPreBlend.fG[dst[x]]; } dst += rowBytes; } } #endif }