From a1a43ea418ad41baf6fd1e2363a870fb5b80b49d Mon Sep 17 00:00:00 2001 From: jules Date: Thu, 2 Jan 2014 22:00:47 +0000 Subject: [PATCH] Tweaks to font-rendering: fonts are now vertically hinted by finding the best overall scale that will make the most common horizontal features better aligned. Changed the font gamma to be greater when rendering with brighter colours. --- modules/juce_graphics/fonts/juce_Typeface.cpp | 135 +++++++++++++++++- modules/juce_graphics/fonts/juce_Typeface.h | 10 ++ .../native/juce_RenderingHelpers.h | 117 ++++++++------- 3 files changed, 205 insertions(+), 57 deletions(-) diff --git a/modules/juce_graphics/fonts/juce_Typeface.cpp b/modules/juce_graphics/fonts/juce_Typeface.cpp index 032ee318dd..85df87eb89 100644 --- a/modules/juce_graphics/fonts/juce_Typeface.cpp +++ b/modules/juce_graphics/fonts/juce_Typeface.cpp @@ -101,8 +101,8 @@ struct FontStyleHelpers }; //============================================================================== -Typeface::Typeface (const String& name_, const String& style_) noexcept - : name (name_), style (style_) +Typeface::Typeface (const String& faceName, const String& styleName) noexcept + : name (faceName), style (styleName) { } @@ -126,3 +126,134 @@ EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransfor return nullptr; } + +//============================================================================== +struct Typeface::HintingParams +{ + HintingParams (Typeface& t) + : top (0), middle (0), bottom (0) + { + Font font (&t); + font = font.withHeight ((float) standardHeight); + + top = getAverageY (font, "BDEFPRTZOQC", true); + middle = getAverageY (font, "acegmnopqrsuvwxy", true); + bottom = getAverageY (font, "BDELZOC", false); + } + + AffineTransform getVerticalHintingTransform (float fontSize) noexcept + { + if (cachedSize == fontSize) + return cachedTransform; + + const float t = fontSize * top; + const float m = fontSize * middle; + const float b = fontSize * bottom; + + if (b < t + 2.0f) + return AffineTransform(); + + Scaling s[] = { Scaling (t, m, b, 0.0f, 0.0f), + Scaling (t, m, b, 1.0f, 0.0f), + Scaling (t, m, b, 0.0f, 1.0f), + Scaling (t, m, b, 1.0f, 1.0f) }; + + int best = 0; + + for (int i = 1; i < numElementsInArray (s); ++i) + if (s[i].drift < s[best].drift) + best = i; + + cachedSize = fontSize; + + AffineTransform result (s[best].getTransform()); + cachedTransform = result; + return result; + } + +private: + float cachedSize; + AffineTransform cachedTransform; + + struct Scaling + { + Scaling (float t, float m, float b, float direction1, float direction2) noexcept + { + float newT = std::floor (t) + direction1; + float newB = std::floor (b) + direction2; + float newM = newT + (newB - newT) * (m - t) / (b - t); + + float middleOffset = newM - std::floor (newM); + float nudge = middleOffset < 0.5f ? (middleOffset * -0.2f) : ((1.0f - middleOffset) * 0.2f); + newT += nudge; + newB += nudge; + + scale = (newB - newT) / (b - t); + offset = (newB / scale) - b; + + drift = getDrift (t) + getDrift (m) + getDrift (b) + offset + 20.0f * std::abs (scale - 1.0f); + } + + AffineTransform getTransform() const noexcept + { + return AffineTransform::translation (0, offset).scaled (1.0f, scale); + } + + float getDrift (float n) const noexcept + { + n = (n + offset) * scale; + const float diff = n - std::floor (n); + return jmin (diff, 1.0f - diff); + } + + float offset, scale, drift; + }; + + static float getAverageY (const Font& font, const char* chars, bool getTop) + { + GlyphArrangement ga; + ga.addLineOfText (font, chars, 0, 0); + + Array y; + DefaultElementComparator sorter; + + for (int i = 0; i < ga.getNumGlyphs(); ++i) + { + Path p; + ga.getGlyph (i).createPath (p); + Rectangle bounds (p.getBounds()); + + if (! p.isEmpty()) + y.addSorted (sorter, getTop ? bounds.getY() : bounds.getBottom()); + } + + float median = y[y.size() / 2]; + + int total = 0; + int num = 0; + + for (int i = 0; i < y.size(); ++i) + { + if (std::abs (median - y.getUnchecked(i)) < 0.05f * (float) standardHeight) + { + total += y.getUnchecked(i); + ++num; + } + } + + return num < 4 ? 0.0f : total / (num * (float) standardHeight); + } + + enum { standardHeight = 100 }; + float top, middle, bottom; +}; + +AffineTransform Typeface::getVerticalHintingTransform (float fontSize) +{ + ScopedLock sl (hintingLock); + + if (hintingParams == nullptr) + hintingParams = new HintingParams (*this); + + return hintingParams->getVerticalHintingTransform (fontSize); +} diff --git a/modules/juce_graphics/fonts/juce_Typeface.h b/modules/juce_graphics/fonts/juce_Typeface.h index 9c2ae31ef3..ed42a48210 100644 --- a/modules/juce_graphics/fonts/juce_Typeface.h +++ b/modules/juce_graphics/fonts/juce_Typeface.h @@ -134,6 +134,11 @@ public: */ static void scanFolderForFonts (const File& folder); + /** Makes an attempt at estimating a good overall transform that will scale a font of + the given size to align vertically with the pixel grid. + */ + AffineTransform getVerticalHintingTransform (float fontHeight); + protected: //============================================================================== String name, style; @@ -143,6 +148,11 @@ protected: static Ptr getFallbackTypeface(); private: + struct HintingParams; + friend struct ContainerDeletePolicy; + ScopedPointer hintingParams; + CriticalSection hintingLock; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Typeface) }; diff --git a/modules/juce_graphics/native/juce_RenderingHelpers.h b/modules/juce_graphics/native/juce_RenderingHelpers.h index dae758cf80..cb8e9d6cc8 100644 --- a/modules/juce_graphics/native/juce_RenderingHelpers.h +++ b/modules/juce_graphics/native/juce_RenderingHelpers.h @@ -283,11 +283,9 @@ public: glyph = glyphNumber; const float fontHeight = font.getHeight(); - edgeTable = typeface->getEdgeTableForGlyph (glyphNumber, AffineTransform::scale (fontHeight * font.getHorizontalScale(), - fontHeight)); - - if (edgeTable != nullptr) - edgeTable->multiplyLevels (1.5f); + edgeTable = typeface->getEdgeTableForGlyph (glyphNumber, + AffineTransform::scale (fontHeight * font.getHorizontalScale(), fontHeight) + .followedBy (font.getTypeface()->getVerticalHintingTransform (fontHeight))); } Font font; @@ -295,7 +293,7 @@ public: bool snapToIntegerCoordinate; private: - ScopedPointer edgeTable; + ScopedPointer edgeTable; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedGlyphEdgeTable) }; @@ -1351,7 +1349,7 @@ namespace EdgeTableFillers const int maxX, maxY; int y; DestPixelType* linePixels; - HeapBlock scratchBuffer; + HeapBlock scratchBuffer; size_t scratchSize; JUCE_DECLARE_NON_COPYABLE (TransformedImageFill) @@ -1369,16 +1367,16 @@ namespace EdgeTableFillers switch (srcData.pixelFormat) { case Image::ARGB: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; case Image::RGB: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; default: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; } break; @@ -1387,16 +1385,16 @@ namespace EdgeTableFillers switch (srcData.pixelFormat) { case Image::ARGB: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; case Image::RGB: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; default: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; } break; @@ -1405,16 +1403,16 @@ namespace EdgeTableFillers switch (srcData.pixelFormat) { case Image::ARGB: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; case Image::RGB: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; default: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; } break; @@ -1430,16 +1428,16 @@ namespace EdgeTableFillers switch (srcData.pixelFormat) { case Image::ARGB: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; case Image::RGB: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; default: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; } break; @@ -1448,16 +1446,16 @@ namespace EdgeTableFillers switch (srcData.pixelFormat) { case Image::ARGB: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; case Image::RGB: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; default: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; } break; @@ -1466,16 +1464,16 @@ namespace EdgeTableFillers switch (srcData.pixelFormat) { case Image::ARGB: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; case Image::RGB: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; default: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; } break; @@ -1487,12 +1485,12 @@ namespace EdgeTableFillers { if (replaceContents) { - EdgeTableFillers::SolidColour r (destData, fillColour); + EdgeTableFillers::SolidColour r (destData, fillColour); iter.iterate (r); } else { - EdgeTableFillers::SolidColour r (destData, fillColour); + EdgeTableFillers::SolidColour r (destData, fillColour); iter.iterate (r); } } @@ -1505,18 +1503,18 @@ namespace EdgeTableFillers { if (isIdentity) { - EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); + EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); iter.iterate (renderer); } else { - EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); + EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); iter.iterate (renderer); } } else { - EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); + EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); iter.iterate (renderer); } } @@ -1721,7 +1719,7 @@ struct ClipRegions template void transformedClipImage (const Image::BitmapData& srcData, const AffineTransform& transform, const Graphics::ResamplingQuality quality, const SrcPixelType*) { - EdgeTableFillers::TransformedImageFill renderer (srcData, srcData, transform, 255, quality); + EdgeTableFillers::TransformedImageFill renderer (srcData, srcData, transform, 255, quality); for (int y = 0; y < edgeTable.getMaximumBounds().getHeight(); ++y) renderer.clipEdgeTableLine (edgeTable, edgeTable.getMaximumBounds().getX(), y + edgeTable.getMaximumBounds().getY(), @@ -1734,7 +1732,7 @@ struct ClipRegions Rectangle r (imageX, imageY, srcData.width, srcData.height); edgeTable.clipToRectangle (r); - EdgeTableFillers::ImageFill renderer (srcData, srcData, 255, imageX, imageY); + EdgeTableFillers::ImageFill renderer (srcData, srcData, 255, imageX, imageY); for (int y = 0; y < r.getHeight(); ++y) renderer.clipEdgeTableLine (edgeTable, r.getX(), y + r.getY(), r.getWidth()); @@ -2002,7 +2000,7 @@ public: { } - SavedStateType& getThis() noexcept { return *static_cast (this); } + SavedStateType& getThis() noexcept { return *static_cast (this); } bool clipToRectangle (const Rectangle& r) { @@ -2253,11 +2251,20 @@ public: { EdgeTableRegionType* edgeTableClip = new EdgeTableRegionType (edgeTable); edgeTableClip->edgeTable.translate (x, y); + + if (fillType.isColour()) + { + float brightness = fillType.colour.getBrightness() - 0.5f; + + if (brightness > 0.0f) + edgeTableClip->edgeTable.multiplyLevels (1.0f + 1.6f * brightness); + } + fillShape (edgeTableClip, false); } } - void drawLine (const Line & line) + void drawLine (const Line& line) { Path p; p.addLineSegment (line, 1.0f); @@ -2437,7 +2444,7 @@ public: } } - typedef GlyphCache, SoftwareRendererSavedState> GlyphCacheType; + typedef GlyphCache, SoftwareRendererSavedState> GlyphCacheType; static void clearGlyphCache() { @@ -2523,7 +2530,7 @@ public: template void fillWithGradient (IteratorType& iter, ColourGradient& gradient, const AffineTransform& trans, bool isIdentity) const { - HeapBlock lookupTable; + HeapBlock lookupTable; const int numLookupEntries = gradient.createLookupTable (trans, lookupTable); jassert (numLookupEntries > 0); @@ -2632,7 +2639,7 @@ public: void fillPath (const Path& path, const AffineTransform& t) override { stack->fillPath (path, t); } void drawImage (const Image& im, const AffineTransform& t) override { stack->drawImage (im, t); } void drawGlyph (int glyphNumber, const AffineTransform& t) override { stack->drawGlyph (glyphNumber, t); } - void drawLine (const Line & line) override { stack->drawLine (line); } + void drawLine (const Line& line) override { stack->drawLine (line); } void setFont (const Font& newFont) override { stack->font = newFont; } const Font& getFont() override { return stack->font; }