/*
|
* Copyright 2006-2012 The Android Open Source Project
|
* Copyright 2012 Mozilla Foundation
|
*
|
* Use of this source code is governed by a BSD-style license that can be
|
* found in the LICENSE file.
|
*/
|
|
#include "SkBitmap.h"
|
#include "SkCanvas.h"
|
#include "SkColor.h"
|
#include "SkColorData.h"
|
#include "SkFDot6.h"
|
#include "SkFontHost_FreeType_common.h"
|
#include "SkPath.h"
|
#include "SkTo.h"
|
|
#include <utility>
|
|
#include <ft2build.h>
|
#include FT_FREETYPE_H
|
#include FT_BITMAP_H
|
#ifdef FT_COLOR_H
|
# include FT_COLOR_H
|
#endif
|
#include FT_IMAGE_H
|
#include FT_OUTLINE_H
|
// In the past, FT_GlyphSlot_Own_Bitmap was defined in this header file.
|
#include FT_SYNTHESIS_H
|
|
// FT_LOAD_COLOR and the corresponding FT_Pixel_Mode::FT_PIXEL_MODE_BGRA
|
// were introduced in FreeType 2.5.0.
|
// The following may be removed once FreeType 2.5.0 is required to build.
|
#ifndef FT_LOAD_COLOR
|
# define FT_LOAD_COLOR ( 1L << 20 )
|
# define FT_PIXEL_MODE_BGRA 7
|
#endif
|
|
#ifdef SK_DEBUG
|
const char* SkTraceFtrGetError(int e) {
|
switch ((FT_Error)e) {
|
#undef FTERRORS_H_
|
#define FT_ERRORDEF( e, v, s ) case v: return s;
|
#define FT_ERROR_START_LIST
|
#define FT_ERROR_END_LIST
|
#include FT_ERRORS_H
|
#undef FT_ERRORDEF
|
#undef FT_ERROR_START_LIST
|
#undef FT_ERROR_END_LIST
|
default: return "";
|
}
|
}
|
#endif // SK_DEBUG
|
|
namespace {
|
|
FT_Pixel_Mode compute_pixel_mode(SkMask::Format format) {
|
switch (format) {
|
case SkMask::kBW_Format:
|
return FT_PIXEL_MODE_MONO;
|
case SkMask::kA8_Format:
|
default:
|
return FT_PIXEL_MODE_GRAY;
|
}
|
}
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
uint16_t packTriple(U8CPU r, U8CPU g, U8CPU b) {
|
#ifdef SK_SHOW_TEXT_BLIT_COVERAGE
|
r = SkTMax(r, (U8CPU)0x40);
|
g = SkTMax(g, (U8CPU)0x40);
|
b = SkTMax(b, (U8CPU)0x40);
|
#endif
|
return SkPack888ToRGB16(r, g, b);
|
}
|
|
uint16_t grayToRGB16(U8CPU gray) {
|
#ifdef SK_SHOW_TEXT_BLIT_COVERAGE
|
gray = SkTMax(gray, (U8CPU)0x40);
|
#endif
|
return SkPack888ToRGB16(gray, gray, gray);
|
}
|
|
int bittst(const uint8_t data[], int bitOffset) {
|
SkASSERT(bitOffset >= 0);
|
int lowBit = data[bitOffset >> 3] >> (~bitOffset & 7);
|
return lowBit & 1;
|
}
|
|
/**
|
* Copies a FT_Bitmap into an SkMask with the same dimensions.
|
*
|
* FT_PIXEL_MODE_MONO
|
* FT_PIXEL_MODE_GRAY
|
* FT_PIXEL_MODE_LCD
|
* FT_PIXEL_MODE_LCD_V
|
*/
|
template<bool APPLY_PREBLEND>
|
void copyFT2LCD16(const FT_Bitmap& bitmap, const SkMask& mask, int lcdIsBGR,
|
const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB)
|
{
|
SkASSERT(SkMask::kLCD16_Format == mask.fFormat);
|
if (FT_PIXEL_MODE_LCD != bitmap.pixel_mode) {
|
SkASSERT(mask.fBounds.width() == static_cast<int>(bitmap.width));
|
}
|
if (FT_PIXEL_MODE_LCD_V != bitmap.pixel_mode) {
|
SkASSERT(mask.fBounds.height() == static_cast<int>(bitmap.rows));
|
}
|
|
const uint8_t* src = bitmap.buffer;
|
uint16_t* dst = reinterpret_cast<uint16_t*>(mask.fImage);
|
const size_t dstRB = mask.fRowBytes;
|
|
const int width = mask.fBounds.width();
|
const int height = mask.fBounds.height();
|
|
switch (bitmap.pixel_mode) {
|
case FT_PIXEL_MODE_MONO:
|
for (int y = height; y --> 0;) {
|
for (int x = 0; x < width; ++x) {
|
dst[x] = -bittst(src, x);
|
}
|
dst = (uint16_t*)((char*)dst + dstRB);
|
src += bitmap.pitch;
|
}
|
break;
|
case FT_PIXEL_MODE_GRAY:
|
for (int y = height; y --> 0;) {
|
for (int x = 0; x < width; ++x) {
|
dst[x] = grayToRGB16(src[x]);
|
}
|
dst = (uint16_t*)((char*)dst + dstRB);
|
src += bitmap.pitch;
|
}
|
break;
|
case FT_PIXEL_MODE_LCD:
|
SkASSERT(3 * mask.fBounds.width() == static_cast<int>(bitmap.width));
|
for (int y = height; y --> 0;) {
|
const uint8_t* triple = src;
|
if (lcdIsBGR) {
|
for (int x = 0; x < width; x++) {
|
dst[x] = packTriple(sk_apply_lut_if<APPLY_PREBLEND>(triple[2], tableR),
|
sk_apply_lut_if<APPLY_PREBLEND>(triple[1], tableG),
|
sk_apply_lut_if<APPLY_PREBLEND>(triple[0], tableB));
|
triple += 3;
|
}
|
} else {
|
for (int x = 0; x < width; x++) {
|
dst[x] = packTriple(sk_apply_lut_if<APPLY_PREBLEND>(triple[0], tableR),
|
sk_apply_lut_if<APPLY_PREBLEND>(triple[1], tableG),
|
sk_apply_lut_if<APPLY_PREBLEND>(triple[2], tableB));
|
triple += 3;
|
}
|
}
|
src += bitmap.pitch;
|
dst = (uint16_t*)((char*)dst + dstRB);
|
}
|
break;
|
case FT_PIXEL_MODE_LCD_V:
|
SkASSERT(3 * mask.fBounds.height() == static_cast<int>(bitmap.rows));
|
for (int y = height; y --> 0;) {
|
const uint8_t* srcR = src;
|
const uint8_t* srcG = srcR + bitmap.pitch;
|
const uint8_t* srcB = srcG + bitmap.pitch;
|
if (lcdIsBGR) {
|
using std::swap;
|
swap(srcR, srcB);
|
}
|
for (int x = 0; x < width; x++) {
|
dst[x] = packTriple(sk_apply_lut_if<APPLY_PREBLEND>(*srcR++, tableR),
|
sk_apply_lut_if<APPLY_PREBLEND>(*srcG++, tableG),
|
sk_apply_lut_if<APPLY_PREBLEND>(*srcB++, tableB));
|
}
|
src += 3 * bitmap.pitch;
|
dst = (uint16_t*)((char*)dst + dstRB);
|
}
|
break;
|
default:
|
SkDEBUGF("FT_Pixel_Mode %d", bitmap.pixel_mode);
|
SkDEBUGFAIL("unsupported FT_Pixel_Mode for LCD16");
|
break;
|
}
|
}
|
|
/**
|
* Copies a FT_Bitmap into an SkMask with the same dimensions.
|
*
|
* Yes, No, Never Requested, Never Produced
|
*
|
* kBW kA8 k3D kARGB32 kLCD16
|
* FT_PIXEL_MODE_MONO Y Y NR N Y
|
* FT_PIXEL_MODE_GRAY N Y NR N Y
|
* FT_PIXEL_MODE_GRAY2 NP NP NR NP NP
|
* FT_PIXEL_MODE_GRAY4 NP NP NR NP NP
|
* FT_PIXEL_MODE_LCD NP NP NR NP NP
|
* FT_PIXEL_MODE_LCD_V NP NP NR NP NP
|
* FT_PIXEL_MODE_BGRA N N NR Y N
|
*
|
* TODO: All of these N need to be Y or otherwise ruled out.
|
*/
|
void copyFTBitmap(const FT_Bitmap& srcFTBitmap, SkMask& dstMask) {
|
SkASSERTF(dstMask.fBounds.width() == static_cast<int>(srcFTBitmap.width),
|
"dstMask.fBounds.width() = %d\n"
|
"static_cast<int>(srcFTBitmap.width) = %d",
|
dstMask.fBounds.width(),
|
static_cast<int>(srcFTBitmap.width)
|
);
|
SkASSERTF(dstMask.fBounds.height() == static_cast<int>(srcFTBitmap.rows),
|
"dstMask.fBounds.height() = %d\n"
|
"static_cast<int>(srcFTBitmap.rows) = %d",
|
dstMask.fBounds.height(),
|
static_cast<int>(srcFTBitmap.rows)
|
);
|
|
const uint8_t* src = reinterpret_cast<const uint8_t*>(srcFTBitmap.buffer);
|
const FT_Pixel_Mode srcFormat = static_cast<FT_Pixel_Mode>(srcFTBitmap.pixel_mode);
|
// FT_Bitmap::pitch is an int and allowed to be negative.
|
const int srcPitch = srcFTBitmap.pitch;
|
const size_t srcRowBytes = SkTAbs(srcPitch);
|
|
uint8_t* dst = dstMask.fImage;
|
const SkMask::Format dstFormat = static_cast<SkMask::Format>(dstMask.fFormat);
|
const size_t dstRowBytes = dstMask.fRowBytes;
|
|
const size_t width = srcFTBitmap.width;
|
const size_t height = srcFTBitmap.rows;
|
|
if (SkMask::kLCD16_Format == dstFormat) {
|
copyFT2LCD16<false>(srcFTBitmap, dstMask, false, nullptr, nullptr, nullptr);
|
return;
|
}
|
|
if ((FT_PIXEL_MODE_MONO == srcFormat && SkMask::kBW_Format == dstFormat) ||
|
(FT_PIXEL_MODE_GRAY == srcFormat && SkMask::kA8_Format == dstFormat))
|
{
|
size_t commonRowBytes = SkTMin(srcRowBytes, dstRowBytes);
|
for (size_t y = height; y --> 0;) {
|
memcpy(dst, src, commonRowBytes);
|
src += srcPitch;
|
dst += dstRowBytes;
|
}
|
} else if (FT_PIXEL_MODE_MONO == srcFormat && SkMask::kA8_Format == dstFormat) {
|
for (size_t y = height; y --> 0;) {
|
uint8_t byte = 0;
|
int bits = 0;
|
const uint8_t* src_row = src;
|
uint8_t* dst_row = dst;
|
for (size_t x = width; x --> 0;) {
|
if (0 == bits) {
|
byte = *src_row++;
|
bits = 8;
|
}
|
*dst_row++ = byte & 0x80 ? 0xff : 0x00;
|
bits--;
|
byte <<= 1;
|
}
|
src += srcPitch;
|
dst += dstRowBytes;
|
}
|
} else if (FT_PIXEL_MODE_BGRA == srcFormat && SkMask::kARGB32_Format == dstFormat) {
|
// FT_PIXEL_MODE_BGRA is pre-multiplied.
|
for (size_t y = height; y --> 0;) {
|
const uint8_t* src_row = src;
|
SkPMColor* dst_row = reinterpret_cast<SkPMColor*>(dst);
|
for (size_t x = 0; x < width; ++x) {
|
uint8_t b = *src_row++;
|
uint8_t g = *src_row++;
|
uint8_t r = *src_row++;
|
uint8_t a = *src_row++;
|
*dst_row++ = SkPackARGB32(a, r, g, b);
|
#ifdef SK_SHOW_TEXT_BLIT_COVERAGE
|
*(dst_row-1) = SkFourByteInterp256(*(dst_row-1), SK_ColorWHITE, 0x40);
|
#endif
|
}
|
src += srcPitch;
|
dst += dstRowBytes;
|
}
|
} else {
|
SkDEBUGF("FT_Pixel_Mode %d, SkMask::Format %d\n", srcFormat, dstFormat);
|
SkDEBUGFAIL("unsupported combination of FT_Pixel_Mode and SkMask::Format");
|
}
|
}
|
|
inline int convert_8_to_1(unsigned byte) {
|
SkASSERT(byte <= 0xFF);
|
// Arbitrary decision that making the cutoff at 1/4 instead of 1/2 in general looks better.
|
return (byte >> 6) != 0;
|
}
|
|
uint8_t pack_8_to_1(const uint8_t alpha[8]) {
|
unsigned bits = 0;
|
for (int i = 0; i < 8; ++i) {
|
bits <<= 1;
|
bits |= convert_8_to_1(alpha[i]);
|
}
|
return SkToU8(bits);
|
}
|
|
void packA8ToA1(const SkMask& mask, const uint8_t* src, size_t srcRB) {
|
const int height = mask.fBounds.height();
|
const int width = mask.fBounds.width();
|
const int octs = width >> 3;
|
const int leftOverBits = width & 7;
|
|
uint8_t* dst = mask.fImage;
|
const int dstPad = mask.fRowBytes - SkAlign8(width)/8;
|
SkASSERT(dstPad >= 0);
|
|
const int srcPad = srcRB - width;
|
SkASSERT(srcPad >= 0);
|
|
for (int y = 0; y < height; ++y) {
|
for (int i = 0; i < octs; ++i) {
|
*dst++ = pack_8_to_1(src);
|
src += 8;
|
}
|
if (leftOverBits > 0) {
|
unsigned bits = 0;
|
int shift = 7;
|
for (int i = 0; i < leftOverBits; ++i, --shift) {
|
bits |= convert_8_to_1(*src++) << shift;
|
}
|
*dst++ = bits;
|
}
|
src += srcPad;
|
dst += dstPad;
|
}
|
}
|
|
inline SkMask::Format SkMaskFormat_for_SkColorType(SkColorType colorType) {
|
switch (colorType) {
|
case kAlpha_8_SkColorType:
|
return SkMask::kA8_Format;
|
case kN32_SkColorType:
|
return SkMask::kARGB32_Format;
|
default:
|
SkDEBUGFAIL("unsupported SkBitmap::Config");
|
return SkMask::kA8_Format;
|
}
|
}
|
|
inline SkColorType SkColorType_for_FTPixelMode(FT_Pixel_Mode pixel_mode) {
|
switch (pixel_mode) {
|
case FT_PIXEL_MODE_MONO:
|
case FT_PIXEL_MODE_GRAY:
|
return kAlpha_8_SkColorType;
|
case FT_PIXEL_MODE_BGRA:
|
return kN32_SkColorType;
|
default:
|
SkDEBUGFAIL("unsupported FT_PIXEL_MODE");
|
return kAlpha_8_SkColorType;
|
}
|
}
|
|
inline SkColorType SkColorType_for_SkMaskFormat(SkMask::Format format) {
|
switch (format) {
|
case SkMask::kBW_Format:
|
case SkMask::kA8_Format:
|
case SkMask::kLCD16_Format:
|
return kAlpha_8_SkColorType;
|
case SkMask::kARGB32_Format:
|
return kN32_SkColorType;
|
default:
|
SkDEBUGFAIL("unsupported destination SkBitmap::Config");
|
return kAlpha_8_SkColorType;
|
}
|
}
|
|
} // namespace
|
|
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 (this->isSubpixel()) {
|
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);
|
|
#ifdef FT_COLOR_H
|
if (SkMask::kARGB32_Format == glyph.fMaskFormat) {
|
SkBitmap dstBitmap;
|
// TODO: mark this as sRGB when the blits will be sRGB.
|
dstBitmap.setInfo(SkImageInfo::Make(glyph.fWidth, glyph.fHeight,
|
kN32_SkColorType,
|
kPremul_SkAlphaType),
|
glyph.rowBytes());
|
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);
|
|
if (this->isSubpixel()) {
|
canvas.translate(SkFixedToScalar(glyph.getSubXFixed()),
|
SkFixedToScalar(glyph.getSubYFixed()));
|
}
|
|
SkPaint paint;
|
paint.setAntiAlias(true);
|
|
FT_Color *palette;
|
FT_Error err = FT_Palette_Select(face, 0, &palette);
|
if (err) {
|
SK_TRACEFTR(err, "Could not get palette from %s fontFace.", face->family_name);
|
return;
|
}
|
FT_LayerIterator layerIterator;
|
layerIterator.p = NULL;
|
FT_Bool haveLayers = false;
|
FT_UInt layerGlyphIndex;
|
FT_UInt layerColorIndex;
|
|
while (FT_Get_Color_Glyph_Layer(face, glyph.getGlyphID(), &layerGlyphIndex,
|
&layerColorIndex,
|
&layerIterator)) {
|
haveLayers = true;
|
if (layerColorIndex == 0xFFFF) {
|
paint.setColor(SK_ColorBLACK);
|
} else {
|
SkColor color = SkColorSetARGB(palette[layerColorIndex].alpha,
|
palette[layerColorIndex].red,
|
palette[layerColorIndex].green,
|
palette[layerColorIndex].blue);
|
paint.setColor(color);
|
}
|
SkPath path;
|
if (this->generateFacePath(face, layerGlyphIndex, &path)) {
|
canvas.drawPath(path, paint);
|
}
|
}
|
|
if (!haveLayers) {
|
SK_TRACEFTR(err, "Could not get layers from %s fontFace.", face->family_name);
|
return;
|
}
|
} else
|
#endif
|
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 %x.", face->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)glyph.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;
|
// Using kMedium FilterQuality will cause mipmaps to be generated. Use
|
// kLow when the results will be roughly the same in order to avoid
|
// the mipmap generation cost.
|
// See skbug.com/6967
|
if (bitmapTransform.getMinScale() < 0.5) {
|
paint.setFilterQuality(kMedium_SkFilterQuality);
|
} else {
|
paint.setFilterQuality(kLow_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
|
}
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
namespace {
|
|
int move_proc(const FT_Vector* pt, void* ctx) {
|
SkPath* path = (SkPath*)ctx;
|
path->close(); // to close the previous contour (if any)
|
path->moveTo(SkFDot6ToScalar(pt->x), -SkFDot6ToScalar(pt->y));
|
return 0;
|
}
|
|
int line_proc(const FT_Vector* pt, void* ctx) {
|
SkPath* path = (SkPath*)ctx;
|
path->lineTo(SkFDot6ToScalar(pt->x), -SkFDot6ToScalar(pt->y));
|
return 0;
|
}
|
|
int quad_proc(const FT_Vector* pt0, const FT_Vector* pt1, void* ctx) {
|
SkPath* path = (SkPath*)ctx;
|
path->quadTo(SkFDot6ToScalar(pt0->x), -SkFDot6ToScalar(pt0->y),
|
SkFDot6ToScalar(pt1->x), -SkFDot6ToScalar(pt1->y));
|
return 0;
|
}
|
|
int cubic_proc(const FT_Vector* pt0, const FT_Vector* pt1, const FT_Vector* pt2, void* ctx) {
|
SkPath* path = (SkPath*)ctx;
|
path->cubicTo(SkFDot6ToScalar(pt0->x), -SkFDot6ToScalar(pt0->y),
|
SkFDot6ToScalar(pt1->x), -SkFDot6ToScalar(pt1->y),
|
SkFDot6ToScalar(pt2->x), -SkFDot6ToScalar(pt2->y));
|
return 0;
|
}
|
|
} // namespace
|
|
bool SkScalerContext_FreeType_Base::generateGlyphPath(FT_Face face, SkPath* path) {
|
FT_Outline_Funcs funcs;
|
|
funcs.move_to = move_proc;
|
funcs.line_to = line_proc;
|
funcs.conic_to = quad_proc;
|
funcs.cubic_to = cubic_proc;
|
funcs.shift = 0;
|
funcs.delta = 0;
|
|
FT_Error err = FT_Outline_Decompose(&face->glyph->outline, &funcs, path);
|
|
if (err != 0) {
|
path->reset();
|
return false;
|
}
|
|
path->close();
|
return true;
|
}
|
|
bool SkScalerContext_FreeType_Base::generateFacePath(FT_Face face, SkGlyphID glyphID, SkPath* path) {
|
uint32_t flags = 0; //fLoadGlyphFlags;
|
flags |= FT_LOAD_NO_BITMAP; // ignore embedded bitmaps so we're sure to get the outline
|
flags &= ~FT_LOAD_RENDER; // don't scan convert (we just want the outline)
|
|
FT_Error err = FT_Load_Glyph(face, glyphID, flags);
|
if (err != 0) {
|
path->reset();
|
return false;
|
}
|
|
if (!generateGlyphPath(face, path)) {
|
path->reset();
|
return false;
|
}
|
return true;
|
}
|