Simplify freetype rendering

Switch to always using FT_Render_Glyph for all glyph types.

Change-Id: I9427bbffd30a8d1ca92d7ab9a92df8761f6b89c3
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
This commit is contained in:
Allan Sandfeld Jensen 2017-09-22 14:54:39 +02:00
parent b179abc33b
commit e48b854087

View File

@ -121,13 +121,12 @@ class QtFreetypeData
{
public:
QtFreetypeData()
: library(0), hasPatentFreeLcdRendering(false)
: library(0)
{ }
~QtFreetypeData();
FT_Library library;
QHash<QFontEngine::FaceId, QFreetypeFace *> faces;
bool hasPatentFreeLcdRendering;
};
QtFreetypeData::~QtFreetypeData()
@ -153,11 +152,6 @@ QtFreetypeData *qt_getFreetypeData()
FT_Bool no_darkening = false;
FT_Property_Set(freetypeData->library, "cff", "no-stem-darkening", &no_darkening);
#endif
// FreeType has since 2.8.1 a patent free alternative to LCD-filtering.
FT_Int amajor, aminor = 0, apatch = 0;
FT_Library_Version(freetypeData->library, &amajor, &aminor, &apatch);
if (QT_VERSION_CHECK(amajor, aminor, apatch) >= QT_VERSION_CHECK(2, 8, 1))
freetypeData->hasPatentFreeLcdRendering = true;
}
return freetypeData;
}
@ -561,26 +555,7 @@ QFontEngineFT::Glyph::~Glyph()
delete [] data;
}
struct LcdFilterDummy
{
static inline void filterPixel(uchar &, uchar &, uchar &)
{}
};
struct LcdFilterLegacy
{
static inline void filterPixel(uchar &red, uchar &green, uchar &blue)
{
uint r = red, g = green, b = blue;
// intra-pixel filter used by the legacy filter (adopted from _ft_lcd_filter_legacy)
red = (r * uint(65538 * 9/13) + g * uint(65538 * 1/6) + b * uint(65538 * 1/13)) / 65536;
green = (r * uint(65538 * 3/13) + g * uint(65538 * 4/6) + b * uint(65538 * 3/13)) / 65536;
blue = (r * uint(65538 * 1/13) + g * uint(65538 * 1/6) + b * uint(65538 * 9/13)) / 65536;
}
};
template <typename LcdFilter>
static void convertRGBToARGB_helper(const uchar *src, uint *dst, int width, int height, int src_pitch, bool bgr)
static inline void convertRGBToARGB(const uchar *src, uint *dst, int width, int height, int src_pitch, bool bgr)
{
const int offs = bgr ? -1 : 1;
const int w = width * 3;
@ -590,7 +565,6 @@ static void convertRGBToARGB_helper(const uchar *src, uint *dst, int width, int
uchar red = src[x + 1 - offs];
uchar green = src[x + 1];
uchar blue = src[x + 1 + offs];
LcdFilter::filterPixel(red, green, blue);
*dd++ = (0xFFU << 24) | (red << 16) | (green << 8) | blue;
}
dst += width;
@ -598,16 +572,7 @@ static void convertRGBToARGB_helper(const uchar *src, uint *dst, int width, int
}
}
static inline void convertRGBToARGB(const uchar *src, uint *dst, int width, int height, int src_pitch, bool bgr, bool legacyFilter)
{
if (!legacyFilter)
convertRGBToARGB_helper<LcdFilterDummy>(src, dst, width, height, src_pitch, bgr);
else
convertRGBToARGB_helper<LcdFilterLegacy>(src, dst, width, height, src_pitch, bgr);
}
template <typename LcdFilter>
static void convertRGBToARGB_V_helper(const uchar *src, uint *dst, int width, int height, int src_pitch, bool bgr)
static inline void convertRGBToARGB_V(const uchar *src, uint *dst, int width, int height, int src_pitch, bool bgr)
{
const int offs = bgr ? -src_pitch : src_pitch;
while (height--) {
@ -615,54 +580,12 @@ static void convertRGBToARGB_V_helper(const uchar *src, uint *dst, int width, in
uchar red = src[x + src_pitch - offs];
uchar green = src[x + src_pitch];
uchar blue = src[x + src_pitch + offs];
LcdFilter::filterPixel(red, green, blue);
*dst++ = (0XFFU << 24) | (red << 16) | (green << 8) | blue;
}
src += 3*src_pitch;
}
}
static inline void convertRGBToARGB_V(const uchar *src, uint *dst, int width, int height, int src_pitch, bool bgr, bool legacyFilter)
{
if (!legacyFilter)
convertRGBToARGB_V_helper<LcdFilterDummy>(src, dst, width, height, src_pitch, bgr);
else
convertRGBToARGB_V_helper<LcdFilterLegacy>(src, dst, width, height, src_pitch, bgr);
}
static inline void convertGRAYToARGB(const uchar *src, uint *dst, int width, int height, int src_pitch)
{
while (height--) {
const uchar *p = src;
const uchar * const e = p + width;
while (p < e) {
uchar gray = *p++;
*dst++ = (0xFFU << 24) | (gray << 16) | (gray << 8) | gray;
}
src += src_pitch;
}
}
static void convoluteBitmap(const uchar *src, uchar *dst, int width, int height, int pitch)
{
// convolute the bitmap with a triangle filter to get rid of color fringes
// If we take account for a gamma value of 2, we end up with
// weights of 1, 4, 9, 4, 1. We use an approximation of 1, 3, 8, 3, 1 here,
// as this nicely sums up to 16 :)
int h = height;
while (h--) {
dst[0] = dst[1] = 0;
//
for (int x = 2; x < width - 2; ++x) {
uint sum = src[x-2] + 3*src[x-1] + 8*src[x] + 3*src[x+1] + src[x+2];
dst[x] = (uchar) (sum >> 4);
}
dst[width - 2] = dst[width - 1] = 0;
src += pitch;
dst += pitch;
}
}
static QFontEngine::SubpixelAntialiasingType subpixelAntialiasingTypeHint()
{
static int type = -1;
@ -1153,196 +1076,97 @@ QFontEngineFT::Glyph *QFontEngineFT::loadGlyph(QGlyphSet *set, uint glyph,
int glyph_buffer_size = 0;
QScopedArrayPointer<uchar> glyph_buffer;
bool useFreetypeRenderGlyph = false;
if (slot->format == FT_GLYPH_FORMAT_OUTLINE && (hsubpixel || vfactor != 1)) {
err = FT_Library_SetLcdFilter(slot->library, (FT_LcdFilter)lcdFilterType);
// We use FT_Render_Glyph if freetype has support for lcd-filtering
// or is version 2.8.1 or higher and can do without.
if (err == FT_Err_Ok || qt_getFreetypeData()->hasPatentFreeLcdRendering)
useFreetypeRenderGlyph = true;
FT_Render_Mode renderMode = (default_hint_style == HintLight) ? FT_RENDER_MODE_LIGHT : FT_RENDER_MODE_NORMAL;
switch (format) {
case Format_Mono:
renderMode = FT_RENDER_MODE_MONO;
break;
case Format_A32:
Q_ASSERT(hsubpixel || vfactor != 1);
renderMode = hsubpixel ? FT_RENDER_MODE_LCD : FT_RENDER_MODE_LCD_V;
break;
case Format_A8:
case Format_ARGB:
break;
default:
Q_UNREACHABLE();
}
if (useFreetypeRenderGlyph) {
err = FT_Render_Glyph(slot, hsubpixel ? FT_RENDER_MODE_LCD : FT_RENDER_MODE_LCD_V);
FT_Library_SetLcdFilter(slot->library, (FT_LcdFilter)lcdFilterType);
if (err != FT_Err_Ok)
qWarning("render glyph failed err=%x face=%p, glyph=%d", err, face, glyph);
err = FT_Render_Glyph(slot, renderMode);
if (err != FT_Err_Ok)
qWarning("render glyph failed err=%x face=%p, glyph=%d", err, face, glyph);
FT_Library_SetLcdFilter(slot->library, FT_LCD_FILTER_NONE);
FT_Library_SetLcdFilter(slot->library, FT_LCD_FILTER_NONE);
info.height = slot->bitmap.rows / vfactor;
info.width = hsubpixel ? slot->bitmap.width / 3 : slot->bitmap.width;
info.x = slot->bitmap_left;
info.y = slot->bitmap_top;
glyph_buffer_size = info.width * info.height * 4;
glyph_buffer.reset(new uchar[glyph_buffer_size]);
if (hsubpixel)
convertRGBToARGB(slot->bitmap.buffer, (uint *)glyph_buffer.data(), info.width, info.height, slot->bitmap.pitch, subpixelType != Subpixel_RGB, false);
else if (vfactor != 1)
convertRGBToARGB_V(slot->bitmap.buffer, (uint *)glyph_buffer.data(), info.width, info.height, slot->bitmap.pitch, subpixelType != Subpixel_VRGB, false);
} else {
int left = slot->metrics.horiBearingX;
int right = slot->metrics.horiBearingX + slot->metrics.width;
int top = slot->metrics.horiBearingY;
int bottom = slot->metrics.horiBearingY - slot->metrics.height;
if (transform && slot->format != FT_GLYPH_FORMAT_BITMAP)
transformBoundingBox(&left, &top, &right, &bottom, &matrix);
left = FLOOR(left);
right = CEIL(right);
bottom = FLOOR(bottom);
top = CEIL(top);
int hpixels = TRUNC(right - left);
// subpixel position requires one more pixel
if (subPixelPosition > 0 && format != Format_Mono)
hpixels++;
if (hsubpixel)
hpixels = hpixels*3 + 8;
info.width = hpixels;
info.height = TRUNC(top - bottom);
info.x = TRUNC(left);
info.y = TRUNC(top);
if (hsubpixel) {
info.width /= 3;
info.x -= 1;
}
// If any of the metrics are too large to fit, don't cache them
if (areMetricsTooLarge(info))
return 0;
info.height = slot->bitmap.rows;
info.width = slot->bitmap.width;
info.x = slot->bitmap_left;
info.y = slot->bitmap_top;
if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_LCD)
info.width = info.width / 3;
if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_LCD_V)
info.height = info.height / vfactor;
int pitch = (format == Format_Mono ? ((info.width + 31) & ~31) >> 3 :
(format == Format_A8 ? (info.width + 3) & ~3 : info.width * 4));
if (glyph_buffer_size < pitch * info.height) {
glyph_buffer_size = pitch * info.height;
glyph_buffer.reset(new uchar[glyph_buffer_size]);
memset(glyph_buffer.data(), 0, glyph_buffer_size);
}
if (slot->format == FT_GLYPH_FORMAT_OUTLINE) {
FT_Bitmap bitmap;
bitmap.rows = info.height*vfactor;
bitmap.width = hpixels;
bitmap.pitch = format == Format_Mono ? (((info.width + 31) & ~31) >> 3) : ((bitmap.width + 3) & ~3);
int bitmap_buffer_size = bitmap.rows * bitmap.pitch;
if (!hsubpixel && vfactor == 1 && format != Format_A32) {
Q_ASSERT(glyph_buffer_size <= bitmap_buffer_size);
bitmap.buffer = glyph_buffer.data();
} else {
bitmap.buffer = new uchar[bitmap_buffer_size];
memset(bitmap.buffer, 0, bitmap_buffer_size);
}
bitmap.pixel_mode = format == Format_Mono ? FT_PIXEL_MODE_MONO : FT_PIXEL_MODE_GRAY;
FT_Matrix matrix;
matrix.xx = (hsubpixel ? 3 : 1) << 16;
matrix.yy = vfactor << 16;
matrix.yx = matrix.xy = 0;
glyph_buffer_size = info.height * pitch;
glyph_buffer.reset(new uchar[glyph_buffer_size]);
FT_Outline_Transform(&slot->outline, &matrix);
FT_Outline_Translate (&slot->outline, (hsubpixel ? -3*left +(4<<6) : -left), -bottom*vfactor);
FT_Outline_Get_Bitmap(slot->library, &slot->outline, &bitmap);
if (hsubpixel) {
Q_ASSERT (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY);
Q_ASSERT(antialias);
uchar *convoluted = new uchar[bitmap_buffer_size];
bool useLegacyLcdFilter = false;
useLegacyLcdFilter = (lcdFilterType == FT_LCD_FILTER_LEGACY);
uchar *buffer = bitmap.buffer;
if (!useLegacyLcdFilter) {
convoluteBitmap(bitmap.buffer, convoluted, bitmap.width, info.height, bitmap.pitch);
buffer = convoluted;
}
convertRGBToARGB(buffer + 1, (uint *)glyph_buffer.data(), info.width, info.height, bitmap.pitch, subpixelType != Subpixel_RGB, useLegacyLcdFilter);
delete [] convoluted;
} else if (vfactor != 1) {
convertRGBToARGB_V(bitmap.buffer, (uint *)glyph_buffer.data(), info.width, info.height, bitmap.pitch, subpixelType != Subpixel_VRGB, true);
} else if (format == Format_A32 && bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
convertGRAYToARGB(bitmap.buffer, (uint *)glyph_buffer.data(), info.width, info.height, bitmap.pitch);
}
if (bitmap.buffer != glyph_buffer.data())
delete [] bitmap.buffer;
} else if (slot->format == FT_GLYPH_FORMAT_BITMAP) {
#if ((FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100) >= 20500)
Q_ASSERT(slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO || slot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA);
#else
Q_ASSERT(slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO);
#endif
if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
Q_ASSERT(format == Format_Mono);
uchar *src = slot->bitmap.buffer;
uchar *dst = glyph_buffer.data();
int h = slot->bitmap.rows;
if (format == Format_Mono) {
int bytes = ((info.width + 7) & ~7) >> 3;
while (h--) {
memcpy (dst, src, bytes);
dst += pitch;
src += slot->bitmap.pitch;
}
} else if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
if (hsubpixel) {
while (h--) {
uint *dd = (uint *)dst;
*dd++ = 0;
for (int x = 0; x < static_cast<int>(slot->bitmap.width); x++) {
uint a = ((src[x >> 3] & (0x80 >> (x & 7))) ? 0xffffff : 0x000000);
*dd++ = a;
}
*dd++ = 0;
dst += pitch;
src += slot->bitmap.pitch;
}
} else if (vfactor != 1) {
while (h--) {
uint *dd = (uint *)dst;
for (int x = 0; x < static_cast<int>(slot->bitmap.width); x++) {
uint a = ((src[x >> 3] & (0x80 >> (x & 7))) ? 0xffffff : 0x000000);
*dd++ = a;
}
dst += pitch;
src += slot->bitmap.pitch;
}
} else {
while (h--) {
for (int x = 0; x < static_cast<int>(slot->bitmap.width); x++) {
unsigned char a = ((src[x >> 3] & (0x80 >> (x & 7))) ? 0xff : 0x00);
dst[x] = a;
}
dst += pitch;
src += slot->bitmap.pitch;
}
}
int bytes = ((info.width + 7) & ~7) >> 3;
while (h--) {
memcpy (dst, src, bytes);
dst += pitch;
src += slot->bitmap.pitch;
}
#if ((FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100) >= 20500)
else if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA)
{
while (h--) {
} else if (slot->bitmap.pixel_mode == 7 /*FT_PIXEL_MODE_BGRA*/) {
Q_ASSERT(format == Format_ARGB);
uchar *src = slot->bitmap.buffer;
uchar *dst = glyph_buffer.data();
int h = slot->bitmap.rows;
while (h--) {
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
const quint32 *srcPixel = (const quint32 *)src;
quint32 *dstPixel = (quint32 *)dst;
for (int x = 0; x < static_cast<int>(slot->bitmap.width); x++, srcPixel++, dstPixel++) {
const quint32 pixel = *srcPixel;
*dstPixel = qbswap(pixel);
}
#else
memcpy(dst, src, slot->bitmap.width * 4);
#endif
dst += slot->bitmap.pitch;
src += slot->bitmap.pitch;
const quint32 *srcPixel = (const quint32 *)src;
quint32 *dstPixel = (quint32 *)dst;
for (int x = 0; x < static_cast<int>(slot->bitmap.width); x++, srcPixel++, dstPixel++) {
const quint32 pixel = *srcPixel;
*dstPixel = qbswap(pixel);
}
info.width = info.linearAdvance = info.xOff = slot->bitmap.width;
info.height = slot->bitmap.rows;
info.x = slot->bitmap_left;
info.y = slot->bitmap_top;
}
#else
memcpy(dst, src, slot->bitmap.width * 4);
#endif
dst += slot->bitmap.pitch;
src += slot->bitmap.pitch;
}
info.linearAdvance = info.xOff = slot->bitmap.width;
} else if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
Q_ASSERT(format == Format_A8);
uchar *src = slot->bitmap.buffer;
uchar *dst = glyph_buffer.data();
int h = slot->bitmap.rows;
int bytes = info.width;
while (h--) {
memcpy (dst, src, bytes);
dst += pitch;
src += slot->bitmap.pitch;
}
} else if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_LCD) {
Q_ASSERT(format == Format_A32);
convertRGBToARGB(slot->bitmap.buffer, (uint *)glyph_buffer.data(), info.width, info.height, slot->bitmap.pitch, subpixelType != Subpixel_RGB);
} else if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_LCD_V) {
Q_ASSERT(format == Format_A32);
convertRGBToARGB_V(slot->bitmap.buffer, (uint *)glyph_buffer.data(), info.width, info.height, slot->bitmap.pitch, subpixelType != Subpixel_VRGB);
} else {
qWarning("QFontEngine: Glyph neither outline nor bitmap format=%d", slot->format);
qWarning("QFontEngine: Glyph rendered in unknown pixel_mode=%d", slot->bitmap.pixel_mode);
return 0;
}
}
if (!g) {
g = new Glyph;