aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar agl@chromium.org <agl@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>2009-07-21 17:41:32 +0000
committerGravatar agl@chromium.org <agl@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>2009-07-21 17:41:32 +0000
commit309485b7b51f4cae4c0361ab4da00fe9cc89515c (patch)
treea6c987c01d54a52545b210552b8a52bd834477f0 /src
parenta380ae4a9ac209f5676c06aeaceacc1b08817eda (diff)
Subpixel glyph rendering support.
This patch adds support for rendering subpixel glyphs (using Freetype). In order to control this rendering see SkPaint::setLCDRenderText in SkPaint.h. To setup the LCD mode, see SkFontHost::SetSubpixelOrientation and SkFontHost::SetSubpixelOrder in SkFontHost.h. This patch also adds more fine grained control over hinting (again, only for Freetype currently). One can now control the hinting with SkPaint::setHinting. git-svn-id: http://skia.googlecode.com/svn/trunk@275 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'src')
-rw-r--r--src/core/SkBlitter_ARGB32.cpp102
-rw-r--r--src/core/SkBlitter_ARGB32_Subpixel.cpp102
-rw-r--r--src/core/SkFontHost.cpp90
-rw-r--r--src/core/SkPaint.cpp32
-rw-r--r--src/core/SkScalerContext.cpp66
-rw-r--r--src/ports/SkFontHost_FreeType.cpp123
-rw-r--r--src/ports/SkFontHost_FreeType_Subpixel.cpp143
7 files changed, 589 insertions, 69 deletions
diff --git a/src/core/SkBlitter_ARGB32.cpp b/src/core/SkBlitter_ARGB32.cpp
index ed2fc0ec9a..ecafc3355b 100644
--- a/src/core/SkBlitter_ARGB32.cpp
+++ b/src/core/SkBlitter_ARGB32.cpp
@@ -21,6 +21,21 @@
#include "SkUtils.h"
#include "SkXfermode.h"
+#if defined(SK_BUILD_SUBPIXEL)
+namespace skia_blitter_support {
+// subpixel helper functions from SkBlitter_ARGB32_Subpixel.cpp
+extern uint32_t BlendLCDPixelWithColor(const uint32_t alphaPixel, const uint32_t originalPixel,
+ const uint32_t sourcePixel);
+extern uint32_t BlendLCDPixelWithOpaqueColor(const uint32_t alphaPixel, const uint32_t originalPixel,
+ const uint32_t sourcePixel);
+extern uint32_t BlendLCDPixelWithBlack(const uint32_t alphaPixel, const uint32_t originalPixel);
+}
+
+using namespace skia_blitter_support;
+#endif
+
+//////////////////////////////////////////////////////////////////////////////////////
+
SkARGB32_Blitter::SkARGB32_Blitter(const SkBitmap& device, const SkPaint& paint)
: INHERITED(device) {
uint32_t color = paint.getColor();
@@ -201,12 +216,47 @@ void SkARGB32_Opaque_Blitter::blitMask(const SkMask& mask,
int width = clip.width();
int height = clip.height();
- uint32_t* device = fDevice.getAddr32(x, y);
- const uint8_t* alpha = mask.getAddr(x, y);
+#if defined(SK_BUILD_SUBPIXEL)
+ const bool lcdMode = mask.fFormat == SkMask::kHorizontalLCD_Format;
+ const bool verticalLCDMode = mask.fFormat == SkMask::kVerticalLCD_Format;
+#else
+ const bool lcdMode = false, verticalLCDMode = false;
+#endif
+
+ // In LCD mode the masks have either an extra couple of rows or columns on the edges.
+ uint32_t* device = fDevice.getAddr32(x - lcdMode, y - verticalLCDMode);
uint32_t srcColor = fPMColor;
- unsigned devRB = fDevice.rowBytes() - (width << 2);
- unsigned maskRB = mask.fRowBytes - width;
+#if defined(SK_BUILD_SUBPIXEL)
+ if (lcdMode || verticalLCDMode) {
+ const uint32_t* alpha32 = mask.getAddrLCD(clip.fLeft, clip.fTop);
+
+ if (lcdMode)
+ width += 2; // we have extra columns on the left and right
+ else
+ height += 2; // we have extra rows at the top and bottom
+
+ unsigned devRB = fDevice.rowBytes() - (width << 2);
+ unsigned alphaExtraRowWords = mask.rowWordsLCD() - width;
+
+ do {
+ unsigned w = width;
+ do {
+ const uint32_t alphaPixel = *alpha32++;
+ const uint32_t originalPixel = *device;
+ *device++ = BlendLCDPixelWithOpaqueColor(alphaPixel, originalPixel, srcColor);
+ } while (--w != 0);
+ device = (uint32_t*)((char*)device + devRB);
+ alpha32 += alphaExtraRowWords;
+ } while (--height != 0);
+
+ return;
+ }
+#endif
+
+ const uint8_t* alpha = mask.getAddr(x, y);
+ unsigned maskRB = mask.fRowBytes - width;
+ unsigned devRB = fDevice.rowBytes() - (width << 2);
do {
int w = width;
do {
@@ -298,18 +348,53 @@ void SkARGB32_Black_Blitter::blitMask(const SkMask& mask, const SkIRect& clip) {
SkARGB32_BlitBW(fDevice, mask, clip, black);
} else {
- uint32_t* device = fDevice.getAddr32(clip.fLeft, clip.fTop);
- const uint8_t* alpha = mask.getAddr(clip.fLeft, clip.fTop);
+#if defined(SK_BUILD_SUBPIXEL)
+ const bool lcdMode = mask.fFormat == SkMask::kHorizontalLCD_Format;
+ const bool verticalLCDMode = mask.fFormat == SkMask::kVerticalLCD_Format;
+#else
+ const bool lcdMode = false, verticalLCDMode = false;
+#endif
+
+ // In LCD mode the masks have either an extra couple of rows or columns on the edges.
+ uint32_t* device = fDevice.getAddr32(clip.fLeft - lcdMode, clip.fTop - verticalLCDMode);
unsigned width = clip.width();
unsigned height = clip.height();
- unsigned deviceRB = fDevice.rowBytes() - (width << 2);
- unsigned maskRB = mask.fRowBytes - width;
+ const uint32_t* alpha32;
SkASSERT((int)height > 0);
SkASSERT((int)width > 0);
SkASSERT((int)deviceRB >= 0);
SkASSERT((int)maskRB >= 0);
+#if defined(SK_BUILD_SUBPIXEL)
+ if (lcdMode || verticalLCDMode) {
+ const uint32_t* alpha32 = mask.getAddrLCD(clip.fLeft, clip.fTop);
+ if (lcdMode)
+ width += 2; // we have extra columns on the left and right
+ else
+ height += 2; // we have extra rows at the top and bottom
+
+ unsigned deviceRB = fDevice.rowBytes() - (width << 2);
+ unsigned alphaExtraRowWords = mask.rowWordsLCD() - width;
+
+ do {
+ unsigned w = width;
+ do {
+ const uint32_t alphaPixel = *alpha32++;
+ const uint32_t originalPixel = *device;
+ *device++ = BlendLCDPixelWithBlack(alphaPixel, originalPixel);
+ } while (--w != 0);
+ device = (uint32_t*)((char*)device + deviceRB);
+ alpha32 += alphaExtraRowWords;
+ } while (--height != 0);
+
+ return;
+ }
+#endif
+
+ unsigned maskRB = mask.fRowBytes - width;
+ unsigned deviceRB = fDevice.rowBytes() - (width << 2);
+ const uint8_t* alpha = mask.getAddr(clip.fLeft, clip.fTop);
do {
unsigned w = width;
do {
@@ -482,4 +567,3 @@ void SkARGB32_Shader_Blitter::blitAntiH(int x, int y, const SkAlpha antialias[],
}
}
}
-
diff --git a/src/core/SkBlitter_ARGB32_Subpixel.cpp b/src/core/SkBlitter_ARGB32_Subpixel.cpp
new file mode 100644
index 0000000000..668ccf5c5c
--- /dev/null
+++ b/src/core/SkBlitter_ARGB32_Subpixel.cpp
@@ -0,0 +1,102 @@
+/* libs/graphics/sgl/SkBlitter_ARGB32_Subpixel.cpp
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+/* LCD blend functions:
+
+ These functions take an alpha pixel of the following form:
+ red, green, blue -> an alpha value for the given colour component.
+ alpha -> the max of the red, green and blue alpha values.
+
+ These alpha pixels result from subpixel renderering. The R/G/B values have
+ already been corrected for RGB/BGR element ordering.
+
+ The alpha pixel is blended with an original pixel and a source colour,
+ resulting in a new pixel value.
+*/
+
+#include "SkColorPriv.h"
+
+namespace skia_blitter_support {
+
+uint32_t BlendLCDPixelWithColor(const uint32_t alphaPixel, const uint32_t originalPixel,
+ const uint32_t sourcePixel) {
+ unsigned alphaRed = SkAlpha255To256(SkGetPackedR32(alphaPixel));
+ unsigned alphaGreen = SkAlpha255To256(SkGetPackedG32(alphaPixel));
+ unsigned alphaBlue = SkAlpha255To256(SkGetPackedB32(alphaPixel));
+
+ unsigned sourceRed = SkGetPackedR32(sourcePixel);
+ unsigned sourceGreen = SkGetPackedG32(sourcePixel);
+ unsigned sourceBlue = SkGetPackedB32(sourcePixel);
+ unsigned sourceAlpha = SkAlpha255To256(SkGetPackedA32(sourcePixel));
+
+ alphaRed = (alphaRed * sourceAlpha) >> 8;
+ alphaGreen = (alphaGreen * sourceAlpha) >> 8;
+ alphaBlue = (alphaBlue * sourceAlpha) >> 8;
+ unsigned alphaAlpha = SkMax32(SkMax32(alphaRed, alphaBlue), alphaGreen);
+
+ unsigned originalRed = SkGetPackedR32(originalPixel);
+ unsigned originalGreen = SkGetPackedG32(originalPixel);
+ unsigned originalBlue = SkGetPackedB32(originalPixel);
+ unsigned originalAlpha = SkGetPackedA32(originalPixel);
+
+ return SkPackARGB32(SkMin32(255u, alphaAlpha + originalAlpha),
+ ((sourceRed * alphaRed) >> 8) + ((originalRed * (256 - alphaRed)) >> 8),
+ ((sourceGreen * alphaGreen) >> 8) + ((originalGreen * (256 - alphaGreen)) >> 8),
+ ((sourceBlue * alphaBlue) >> 8) + ((originalBlue * (256 - alphaBlue)) >> 8));
+
+}
+
+uint32_t BlendLCDPixelWithOpaqueColor(const uint32_t alphaPixel, const uint32_t originalPixel,
+ const uint32_t sourcePixel) {
+ unsigned alphaRed = SkAlpha255To256(SkGetPackedR32(alphaPixel));
+ unsigned alphaGreen = SkAlpha255To256(SkGetPackedG32(alphaPixel));
+ unsigned alphaBlue = SkAlpha255To256(SkGetPackedB32(alphaPixel));
+ unsigned alphaAlpha = SkGetPackedA32(alphaPixel);
+
+ unsigned sourceRed = SkGetPackedR32(sourcePixel);
+ unsigned sourceGreen = SkGetPackedG32(sourcePixel);
+ unsigned sourceBlue = SkGetPackedB32(sourcePixel);
+
+ unsigned originalRed = SkGetPackedR32(originalPixel);
+ unsigned originalGreen = SkGetPackedG32(originalPixel);
+ unsigned originalBlue = SkGetPackedB32(originalPixel);
+ unsigned originalAlpha = SkGetPackedA32(originalPixel);
+
+ return SkPackARGB32(SkMin32(255u, alphaAlpha + originalAlpha),
+ ((sourceRed * alphaRed) >> 8) + ((originalRed * (256 - alphaRed)) >> 8),
+ ((sourceGreen * alphaGreen) >> 8) + ((originalGreen * (256 - alphaGreen)) >> 8),
+ ((sourceBlue * alphaBlue) >> 8) + ((originalBlue * (256 - alphaBlue)) >> 8));
+}
+
+uint32_t BlendLCDPixelWithBlack(const uint32_t alphaPixel, const uint32_t originalPixel) {
+ unsigned alphaRed = SkAlpha255To256(SkGetPackedR32(alphaPixel));
+ unsigned alphaGreen = SkAlpha255To256(SkGetPackedG32(alphaPixel));
+ unsigned alphaBlue = SkAlpha255To256(SkGetPackedB32(alphaPixel));
+ unsigned alphaAlpha = SkGetPackedA32(alphaPixel);
+
+ unsigned originalRed = SkGetPackedR32(originalPixel);
+ unsigned originalGreen = SkGetPackedG32(originalPixel);
+ unsigned originalBlue = SkGetPackedB32(originalPixel);
+ unsigned originalAlpha = SkGetPackedA32(originalPixel);
+
+ return SkPackARGB32(SkMin32(255u, alphaAlpha + originalAlpha),
+ (originalRed * (256 - alphaRed)) >> 8,
+ (originalGreen * (256 - alphaGreen)) >> 8,
+ (originalBlue * (256 - alphaBlue)) >> 8);
+}
+
+} // namespace skia_blitter_support
diff --git a/src/core/SkFontHost.cpp b/src/core/SkFontHost.cpp
new file mode 100644
index 0000000000..fe8e6ea89f
--- /dev/null
+++ b/src/core/SkFontHost.cpp
@@ -0,0 +1,90 @@
+/* libs/graphics/sgl/SkFontHost.cpp
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include "SkFontHost.h"
+
+static SkFontHost::LCDOrientation gLCDOrientation = SkFontHost::kHorizontal_LCDOrientation;
+static SkFontHost::LCDOrder gLCDOrder = SkFontHost::kRGB_LCDOrder;
+
+// static
+SkFontHost::LCDOrientation SkFontHost::GetSubpixelOrientation()
+{
+ return gLCDOrientation;
+}
+
+// static
+void SkFontHost::SetSubpixelOrientation(LCDOrientation orientation)
+{
+ gLCDOrientation = orientation;
+}
+
+// static
+SkFontHost::LCDOrder SkFontHost::GetSubpixelOrder()
+{
+ return gLCDOrder;
+}
+
+// static
+void SkFontHost::SetSubpixelOrder(LCDOrder order)
+{
+ gLCDOrder = order;
+}
+/* libs/graphics/sgl/SkFontHost.cpp
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include "SkFontHost.h"
+
+static SkFontHost::LCDOrientation gLCDOrientation = SkFontHost::kHorizontal_LCDOrientation;
+static SkFontHost::LCDOrder gLCDOrder = SkFontHost::kRGB_LCDOrder;
+
+// static
+SkFontHost::LCDOrientation SkFontHost::GetSubpixelOrientation()
+{
+ return gLCDOrientation;
+}
+
+// static
+void SkFontHost::SetSubpixelOrientation(LCDOrientation orientation)
+{
+ gLCDOrientation = orientation;
+}
+
+// static
+SkFontHost::LCDOrder SkFontHost::GetSubpixelOrder()
+{
+ return gLCDOrder;
+}
+
+// static
+void SkFontHost::SetSubpixelOrder(LCDOrder order)
+{
+ gLCDOrder = order;
+}
diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp
index 8b2a21b5bd..95448c959a 100644
--- a/src/core/SkPaint.cpp
+++ b/src/core/SkPaint.cpp
@@ -57,6 +57,7 @@ SkPaint::SkPaint()
fTextAlign = kLeft_Align;
fStyle = kFill_Style;
fTextEncoding = kUTF8_TextEncoding;
+ fHinting = kNormal_Hinting;
}
SkPaint::SkPaint(const SkPaint& src)
@@ -144,6 +145,11 @@ void SkPaint::setSubpixelText(bool doSubpixel)
this->setFlags(SkSetClearMask(fFlags, doSubpixel, kSubpixelText_Flag));
}
+void SkPaint::setLCDRenderText(bool doLCDRender)
+{
+ this->setFlags(SkSetClearMask(fFlags, doLCDRender, kLCDRenderText_Flag));
+}
+
void SkPaint::setLinearText(bool doLinearText)
{
this->setFlags(SkSetClearMask(fFlags, doLinearText, kLinearText_Flag));
@@ -1118,20 +1124,17 @@ static const SkScalar multipliers[] = { SK_Scalar1/24, SK_Scalar1/32 };
static SkMask::Format computeMaskFormat(const SkPaint& paint)
{
uint32_t flags = paint.getFlags();
-
- return (flags & SkPaint::kAntiAlias_Flag) ? SkMask::kA8_Format : SkMask::kBW_Format;
-}
-static SkScalerContext::Hints computeScalerHints(const SkPaint& paint)
-{
- uint32_t flags = paint.getFlags();
-
- if (flags & SkPaint::kLinearText_Flag)
- return SkScalerContext::kNo_Hints;
- else if (flags & SkPaint::kSubpixelText_Flag)
- return SkScalerContext::kSubpixel_Hints;
- else
- return SkScalerContext::kNormal_Hints;
+ if (flags & SkPaint::kLCDRenderText_Flag)
+#if defined(SK_BUILD_SUBPIXEL)
+ return SkFontHost::GetSubpixelOrientation() == SkFontHost::kHorizontal_LCDOrientation ?
+ SkMask::kHorizontalLCD_Format : SkMask::kVerticalLCD_Format;
+#else
+ return SkMask::kA8_Format;
+#endif
+ if (flags & SkPaint::kAntiAlias_Flag)
+ return SkMask::kA8_Format;
+ return SkMask::kBW_Format;
}
void SkScalerContext::MakeRec(const SkPaint& paint, const SkMatrix* deviceMatrix, Rec* rec)
@@ -1194,9 +1197,10 @@ void SkScalerContext::MakeRec(const SkPaint& paint, const SkMatrix* deviceMatrix
rec->fStrokeJoin = 0;
}
- rec->fHints = SkToU8(computeScalerHints(paint));
+ rec->fSubpixelPositioning = paint.isSubpixelText();
rec->fMaskFormat = SkToU8(computeMaskFormat(paint));
rec->fFlags = SkToU8(flags);
+ rec->setHinting(paint.getHinting());
}
#define MIN_SIZE_FOR_EFFECT_BUFFER 1024
diff --git a/src/core/SkScalerContext.cpp b/src/core/SkScalerContext.cpp
index 7d66c2a5e6..57a3b3548d 100644
--- a/src/core/SkScalerContext.cpp
+++ b/src/core/SkScalerContext.cpp
@@ -16,6 +16,7 @@
*/
#include "SkScalerContext.h"
+#include "SkColorPriv.h"
#include "SkDescriptor.h"
#include "SkDraw.h"
#include "SkFontHost.h"
@@ -45,11 +46,18 @@ void SkGlyph::toMask(SkMask* mask) const {
}
size_t SkGlyph::computeImageSize() const {
- size_t size = this->rowBytes() * fHeight;
- if (fMaskFormat == SkMask::k3D_Format) {
- size *= 3;
+ const size_t size = this->rowBytes() * fHeight;
+
+ switch (fMaskFormat) {
+ case SkMask::kHorizontalLCD_Format:
+ return SkAlign4(size) + sizeof(uint32_t) * ((fWidth + 2) * fHeight);
+ case SkMask::kVerticalLCD_Format:
+ return SkAlign4(size) + sizeof(uint32_t) * (fWidth * (fHeight + 2));
+ case SkMask::k3D_Format:
+ return 3 * size;
+ default:
+ return size;
}
- return size;
}
void SkGlyph::zeroMetrics() {
@@ -63,6 +71,48 @@ void SkGlyph::zeroMetrics() {
fLsbDelta = 0;
}
+void SkGlyph::expandA8ToLCD() const {
+ SkASSERT(fMaskFormat == SkMask::kHorizontalLCD_Format ||
+ fMaskFormat == SkMask::kVerticalLCD_Format);
+
+#if defined(SK_BUILD_SUBPIXEL)
+ uint8_t* input = reinterpret_cast<uint8_t*>(fImage);
+ uint32_t* output = reinterpret_cast<uint32_t*>(input + SkAlign4(rowBytes() * fHeight));
+
+ if (fMaskFormat == SkMask::kHorizontalLCD_Format) {
+ for (unsigned y = 0; y < fHeight; ++y) {
+ const uint8_t* inputRow = input;
+ *output++ = 0; // make the extra column on the left clear
+ for (unsigned x = 0; x < fWidth; ++x) {
+ const uint8_t alpha = *inputRow++;
+ *output++ = SkPackARGB32(alpha, alpha, alpha, alpha);
+ }
+ *output++ = 0;
+
+ input += rowBytes();
+ }
+ } else {
+ const unsigned outputRowBytes = sizeof(uint32_t) * fWidth;
+ memset(output, 0, outputRowBytes);
+ output += fWidth;
+
+ for (unsigned y = 0; y < fHeight; ++y) {
+ const uint8_t* inputRow = input;
+ for (unsigned x = 0; x < fWidth; ++x) {
+ const uint8_t alpha = *inputRow++;
+ *output++ = SkPackARGB32(alpha, alpha, alpha, alpha);
+ }
+
+ input += rowBytes();
+ }
+
+ memset(output, 0, outputRowBytes);
+ output += fWidth;
+ }
+#else
+#endif
+}
+
///////////////////////////////////////////////////////////////////////////////
#ifdef SK_DEBUG
@@ -329,6 +379,9 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) {
this->internalGetPath(*glyph, &fillPath, &devPath, &fillToDevMatrix);
+ const bool lcdMode = fRec.fMaskFormat == SkMask::kHorizontalLCD_Format ||
+ fRec.fMaskFormat == SkMask::kVerticalLCD_Format;
+
if (fRasterizer) {
SkMask mask;
@@ -349,7 +402,7 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) {
SkPaint paint;
SkDraw draw;
- if (SkMask::kA8_Format == fRec.fMaskFormat) {
+ if (SkMask::kA8_Format == fRec.fMaskFormat || lcdMode) {
config = SkBitmap::kA8_Config;
paint.setAntiAlias(true);
} else {
@@ -372,6 +425,9 @@ void SkScalerContext::getImage(const SkGlyph& origGlyph) {
draw.fBounder = NULL;
draw.drawPath(devPath, paint);
}
+
+ if (lcdMode)
+ glyph->expandA8ToLCD();
} else {
this->getGlyphContext(*glyph)->generateImage(*glyph);
}
diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp
index b30aba53fb..5367439705 100644
--- a/src/ports/SkFontHost_FreeType.cpp
+++ b/src/ports/SkFontHost_FreeType.cpp
@@ -15,6 +15,7 @@
** limitations under the License.
*/
+#include "SkColorPriv.h"
#include "SkScalerContext.h"
#include "SkBitmap.h"
#include "SkCanvas.h"
@@ -32,6 +33,11 @@
#include FT_OUTLINE_H
#include FT_SIZES_H
#include FT_TRUETYPE_TABLES_H
+
+#if defined(SK_BUILD_SUBPIXEL)
+#include FT_LCD_FILTER_H
+#endif
+
#ifdef FT_ADVANCES_H
#include FT_ADVANCES_H
#endif
@@ -43,6 +49,7 @@
#include <freetype/ftsizes.h>
#include <freetype/tttables.h>
#include <freetype/ftadvanc.h>
+#include <freetype/ftlcdfil.h>
#endif
//#define ENABLE_GLYPH_SPEW // for tracing calls
@@ -69,6 +76,21 @@ static SkFaceRec* gFaceRecHead;
/////////////////////////////////////////////////////////////////////////
+static bool
+InitFreetype() {
+ FT_Error err = FT_Init_FreeType(&gFTLibrary);
+ if (err)
+ return false;
+
+#if defined(SK_BUILD_SUBPIXEL)
+ // Setup LCD filtering. This reduces colour fringes for LCD rendered
+ // glyphs.
+ err = FT_Library_SetLcdFilter(gFTLibrary, FT_LCD_FILTER_DEFAULT);
+#endif
+
+ return true;
+}
+
class SkScalerContext_FreeType : public SkScalerContext {
public:
SkScalerContext_FreeType(const SkDescriptor* desc);
@@ -247,9 +269,8 @@ SkScalerContext_FreeType::SkScalerContext_FreeType(const SkDescriptor* desc)
FT_Error err;
if (gFTCount == 0) {
- err = FT_Init_FreeType(&gFTLibrary);
-// SkDEBUGF(("FT_Init_FreeType returned %d\n", err));
- SkASSERT(err == 0);
+ const bool success = InitFreetype();
+ SkASSERT(success);
}
++gFTCount;
@@ -275,7 +296,7 @@ SkScalerContext_FreeType::SkScalerContext_FreeType(const SkDescriptor* desc)
SkScalarToFloat(fRec.fPreScaleX), SkScalarToFloat(fRec.fPreSkewX),
SkScalarToFloat(fRec.fPost2x2[0][0]), SkScalarToFloat(fRec.fPost2x2[0][1]),
SkScalarToFloat(fRec.fPost2x2[1][0]), SkScalarToFloat(fRec.fPost2x2[1][1]),
- fRec.fHints, fRec.fMaskFormat, keyString.c_str());
+ fRec.getHinting(), fRec.fMaskFormat, keyString.c_str());
#endif
// now compute our scale factors
@@ -305,42 +326,27 @@ SkScalerContext_FreeType::SkScalerContext_FreeType(const SkDescriptor* desc)
// compute the flags we send to Load_Glyph
{
- uint32_t flags = FT_LOAD_DEFAULT;
- uint32_t render_flags = FT_LOAD_TARGET_NORMAL;
-
- // we force autohinting at the moment
+ FT_Int32 hintingFlags = FT_LOAD_DEFAULT;
- switch (fRec.fHints) {
- case kNo_Hints:
- flags |= FT_LOAD_NO_HINTING;
+ switch (fRec.getHinting()) {
+ case SkPaint::kNo_Hinting:
+ hintingFlags = FT_LOAD_NO_HINTING;
break;
- case kSubpixel_Hints:
- flags |= FT_LOAD_FORCE_AUTOHINT;
- render_flags = FT_LOAD_TARGET_LIGHT;
+ case SkPaint::kSlight_Hinting:
+ hintingFlags = FT_LOAD_TARGET_LIGHT; // This implies FORCE_AUTOHINT
break;
- case kNormal_Hints:
-#ifdef ANDROID
- // The following line disables the font's hinting tables. For
- // Chromium we want font hinting on so that we can generate
- // baselines that look at little like Firefox. It's expected that
- // Mike Reed will rework this code sometime soon so we don't wish
- // to make more extensive changes.
- flags |= FT_LOAD_FORCE_AUTOHINT;
- /* Switch to light hinting (vertical only) to address some chars
- that behaved poorly with NORMAL. In the future we could consider
- making this choice exposed at runtime to the caller.
- */
- render_flags = FT_LOAD_TARGET_LIGHT;
-#endif
+ case SkPaint::kNormal_Hinting:
+ hintingFlags |= FT_LOAD_TARGET_NORMAL;
+ break;
+ case SkPaint::kFull_Hinting:
+ if (SkMask::kHorizontalLCD_Format == fRec.fMaskFormat)
+ hintingFlags = FT_LOAD_TARGET_LCD;
+ else if (SkMask::kVerticalLCD_Format == fRec.fMaskFormat)
+ hintingFlags = FT_LOAD_TARGET_LCD_V;
break;
}
- if (SkMask::kBW_Format == fRec.fMaskFormat)
- render_flags = FT_LOAD_TARGET_MONO;
- else if (SkMask::kLCD_Format == fRec.fMaskFormat)
- render_flags = FT_LOAD_TARGET_LCD;
-
- fLoadGlyphFlags = flags | render_flags;
+ fLoadGlyphFlags = hintingFlags;
}
// now create the FT_Size
@@ -433,10 +439,12 @@ uint16_t SkScalerContext_FreeType::generateCharToGlyph(SkUnichar uni) {
static FT_Pixel_Mode compute_pixel_mode(SkMask::Format format) {
switch (format) {
+ case SkMask::kHorizontalLCD_Format:
+ case SkMask::kVerticalLCD_Format:
+ SkASSERT(!"An LCD format should never be passed here");
+ return FT_PIXEL_MODE_GRAY;
case SkMask::kBW_Format:
return FT_PIXEL_MODE_MONO;
- case SkMask::kLCD_Format:
- return FT_PIXEL_MODE_LCD;
case SkMask::kA8_Format:
default:
return FT_PIXEL_MODE_GRAY;
@@ -503,7 +511,7 @@ void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) {
FT_Outline_Get_CBox(&fFace->glyph->outline, &bbox);
- if (kSubpixel_Hints == fRec.fHints) {
+ if (fRec.fSubpixelPositioning) {
int dx = glyph->getSubXFixed() >> 10;
int dy = glyph->getSubYFixed() >> 10;
// negate dy since freetype-y-goes-up and skia-y-goes-down
@@ -536,7 +544,7 @@ void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) {
goto ERROR;
}
- if (kNormal_Hints == fRec.fHints) {
+ if (!fRec.fSubpixelPositioning) {
glyph->fAdvanceX = SkFDot6ToFixed(fFace->glyph->advance.x);
glyph->fAdvanceY = -SkFDot6ToFixed(fFace->glyph->advance.y);
if (fRec.fFlags & kDevKernText_Flag) {
@@ -554,6 +562,16 @@ void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) {
#endif
}
+#if defined(SK_BUILD_SUBPIXEL)
+namespace skia_freetype_support {
+// extern functions from SkFontHost_FreeType_Subpixel
+extern void CopyFreetypeBitmapToLCDMask(const SkGlyph& dest, const FT_Bitmap& source);
+extern void CopyFreetypeBitmapToVerticalLCDMask(const SkGlyph& dest, const FT_Bitmap& source);
+}
+
+using namespace skia_freetype_support;
+#endif
+
void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) {
SkAutoMutexAcquire ac(gFTMutex);
@@ -572,6 +590,9 @@ void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) {
return;
}
+ const bool lcdRenderMode = fRec.fMaskFormat == SkMask::kHorizontalLCD_Format ||
+ fRec.fMaskFormat == SkMask::kVerticalLCD_Format;
+
switch ( fFace->glyph->format ) {
case FT_GLYPH_FORMAT_OUTLINE: {
FT_Outline* outline = &fFace->glyph->outline;
@@ -579,7 +600,7 @@ void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) {
FT_Bitmap target;
int dx = 0, dy = 0;
- if (kSubpixel_Hints == fRec.fHints) {
+ if (fRec.fSubpixelPositioning) {
dx = glyph.getSubXFixed() >> 10;
dy = glyph.getSubYFixed() >> 10;
// negate dy since freetype-y-goes-up and skia-y-goes-down
@@ -597,6 +618,23 @@ void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) {
FT_Outline_Translate(outline, dx - ((bbox.xMin + dx) & ~63),
dy - ((bbox.yMin + dy) & ~63));
+#if defined(SK_BUILD_SUBPIXEL)
+ if (lcdRenderMode) {
+ // FT_Outline_Get_Bitmap cannot render LCD glyphs. In this case
+ // we have to call FT_Render_Glyph and memcpy the image out.
+ const bool isVertical = fRec.fMaskFormat == SkMask::kVerticalLCD_Format;
+ FT_Render_Mode mode = isVertical ? FT_RENDER_MODE_LCD_V : FT_RENDER_MODE_LCD;
+ FT_Render_Glyph(fFace->glyph, mode);
+
+ if (isVertical)
+ CopyFreetypeBitmapToVerticalLCDMask(glyph, fFace->glyph->bitmap);
+ else
+ CopyFreetypeBitmapToLCDMask(glyph, fFace->glyph->bitmap);
+
+ break;
+ }
+#endif
+
target.width = glyph.fWidth;
target.rows = glyph.fHeight;
target.pitch = glyph.rowBytes();
@@ -652,6 +690,10 @@ void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) {
dst += glyph.rowBytes();
}
}
+
+ if (lcdRenderMode)
+ glyph.expandA8ToLCD();
+
} break;
default:
@@ -858,7 +900,7 @@ SkScalerContext* SkFontHost::CreateScalerContext(const SkDescriptor* desc) {
*/
SkTypeface::Style find_name_and_style(SkStream* stream, SkString* name) {
FT_Library library;
- if (FT_Init_FreeType(&library)) {
+ if (!InitFreetype()) {
name->set(NULL);
return SkTypeface::kNormal;
}
@@ -905,4 +947,3 @@ SkTypeface::Style find_name_and_style(SkStream* stream, SkString* name) {
FT_Done_FreeType(library);
return (SkTypeface::Style)style;
}
-
diff --git a/src/ports/SkFontHost_FreeType_Subpixel.cpp b/src/ports/SkFontHost_FreeType_Subpixel.cpp
new file mode 100644
index 0000000000..2087ba8023
--- /dev/null
+++ b/src/ports/SkFontHost_FreeType_Subpixel.cpp
@@ -0,0 +1,143 @@
+/* libs/graphics/ports/SkFontHost_FreeType_Subpixel.cpp
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+/* This file contains functions for converting Freetype's subpixel output
+ formats into the format used by SkMask for subpixel masks. See the comments
+ in SkMask.h for details on the format.
+*/
+
+#include "SkColorPriv.h"
+#include "SkFontHost.h"
+#include "SkMask.h"
+#include "SkScalerContext.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#if 0
+// Also include the files by name for build tools which require this.
+#include <freetype/freetype.h>
+#endif
+
+namespace skia_freetype_support {
+
+void CopyFreetypeBitmapToLCDMask(const SkGlyph& dest, const FT_Bitmap& source)
+{
+ // |source| has three alpha values per pixel and has an extra column at the
+ // left and right edges.
+
+ // ----- <--- a single pixel in the output
+ // source .oOo.. |.oO|o..
+ // .OOO.. -----
+ // .oOo.. --> .OO O..
+ // .oO o..
+
+ uint8_t* output = reinterpret_cast<uint8_t*>(dest.fImage);
+ const unsigned outputPitch = SkAlign4((source.width / 3) - 2);
+ const uint8_t* input = source.buffer;
+
+ // First we calculate the A8 mask.
+ for (int y = 0; y < source.rows; ++y) {
+ const uint8_t* inputRow = input;
+ uint8_t* outputRow = output;
+ inputRow += 3; // skip the extra column on the left
+ for (int x = 3; x < source.width - 3; x += 3) {
+ const uint8_t averageAlpha = (static_cast<unsigned>(inputRow[0]) + inputRow[1] + inputRow[2] + 1) / 3;
+ *outputRow++ = averageAlpha;
+ inputRow += 3;
+ }
+
+ input += source.pitch;
+ output += outputPitch;
+ }
+
+ // Align the 32-bit plane on a word boundary
+ uint32_t* output32 = (uint32_t*) SkAlign4((uintptr_t) output);
+
+ // Now we build the 32-bit alpha mask and RGB order correct.
+ const int isBGR = SkFontHost::GetSubpixelOrder() == SkFontHost::kBGR_LCDOrder;
+ input = source.buffer;
+
+ for (int y = 0; y < source.rows; ++y) {
+ const uint8_t* inputRow = input;
+ for (int x = 0; x < source.width; x += 3) {
+ const uint8_t alphaRed = isBGR ? inputRow[2] : inputRow[0];
+ const uint8_t alphaGreen = inputRow[1];
+ const uint8_t alphaBlue = isBGR ? inputRow[0] : inputRow[2];
+ const uint8_t maxAlpha = SkMax32(alphaRed, SkMax32(alphaGreen, alphaBlue));
+ *output32++ = SkPackARGB32(maxAlpha, alphaRed, alphaGreen, alphaBlue);
+
+ inputRow += 3;
+ }
+
+ input += source.pitch;
+ }
+}
+
+void CopyFreetypeBitmapToVerticalLCDMask(const SkGlyph& dest, const FT_Bitmap& source)
+{
+ // |source| has three times as many rows as normal, and an extra triple on the
+ // top and bottom.
+
+ // source .oOo.. |.|oOo..
+ // .OOO.. --> |.|OOO..
+ // .oOo.. |.|oOo..
+ // ^
+ // |-------- A single pixel in the output
+
+ uint8_t* output = reinterpret_cast<uint8_t*>(dest.fImage);
+ const unsigned outputPitch = SkAlign4(source.rows);
+ const uint8_t* input = source.buffer;
+
+ // First we calculate the A8 mask.
+ input += 3 * source.pitch; // skip the extra at the beginning
+ for (int y = 3; y < source.rows - 3; y += 3) {
+ const uint8_t* inputRow = input;
+ uint8_t* outputRow = output;
+ for (int x = 0; x < source.width; ++x) {
+ const uint8_t averageAlpha = (static_cast<unsigned>(*inputRow) + inputRow[source.pitch] + inputRow[source.pitch * 2] + 1) / 3;
+ *outputRow++ = averageAlpha;
+ inputRow++;
+ }
+
+ input += source.pitch * 3;
+ output += outputPitch;
+ }
+
+ // Align the 32-bit plane on a word boundary
+ uint32_t* output32 = (uint32_t*) SkAlign4((uintptr_t) output);
+
+ // Now we build the 32-bit alpha mask and RGB order correct.
+ const int isBGR = SkFontHost::GetSubpixelOrder() == SkFontHost::kBGR_LCDOrder;
+ input = source.buffer;
+
+ for (int y = 0; y < source.rows; y += 3) {
+ const uint8_t* inputRow = input;
+ for (int x = 0; x < source.width; ++x) {
+ const uint8_t alphaRed = isBGR ? inputRow[source.pitch * 2] : inputRow[0];
+ const uint8_t alphaGreen = inputRow[source.pitch];
+ const uint8_t alphaBlue = isBGR ? inputRow[0] : inputRow[2 * source.pitch];
+ const uint8_t maxAlpha = SkMax32(alphaRed, SkMax32(alphaGreen, alphaBlue));
+ *output32++ = SkPackARGB32(maxAlpha, alphaRed, alphaGreen, alphaBlue);
+ inputRow++;
+ }
+
+ input += source.pitch * 3;
+ }
+}
+
+} // namespace skia_freetype_support