Browse Source

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.

tags/2021-05-28
jules 11 years ago
parent
commit
a1a43ea418
3 changed files with 205 additions and 57 deletions
  1. +133
    -2
      modules/juce_graphics/fonts/juce_Typeface.cpp
  2. +10
    -0
      modules/juce_graphics/fonts/juce_Typeface.h
  3. +62
    -55
      modules/juce_graphics/native/juce_RenderingHelpers.h

+ 133
- 2
modules/juce_graphics/fonts/juce_Typeface.cpp View File

@@ -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<float> y;
DefaultElementComparator<float> sorter;
for (int i = 0; i < ga.getNumGlyphs(); ++i)
{
Path p;
ga.getGlyph (i).createPath (p);
Rectangle<float> 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);
}

+ 10
- 0
modules/juce_graphics/fonts/juce_Typeface.h View File

@@ -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<HintingParams>;
ScopedPointer<HintingParams> hintingParams;
CriticalSection hintingLock;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Typeface)
};


+ 62
- 55
modules/juce_graphics/native/juce_RenderingHelpers.h View File

@@ -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> edgeTable;
ScopedPointer<EdgeTable> edgeTable;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedGlyphEdgeTable)
};
@@ -1351,7 +1349,7 @@ namespace EdgeTableFillers
const int maxX, maxY;
int y;
DestPixelType* linePixels;
HeapBlock <SrcPixelType> scratchBuffer;
HeapBlock<SrcPixelType> scratchBuffer;
size_t scratchSize;
JUCE_DECLARE_NON_COPYABLE (TransformedImageFill)
@@ -1369,16 +1367,16 @@ namespace EdgeTableFillers
switch (srcData.pixelFormat)
{
case Image::ARGB:
if (tiledFill) { TransformedImageFill <PixelARGB, PixelARGB, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill <PixelARGB, PixelARGB, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
if (tiledFill) { TransformedImageFill<PixelARGB, PixelARGB, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill<PixelARGB, PixelARGB, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
break;
case Image::RGB:
if (tiledFill) { TransformedImageFill <PixelARGB, PixelRGB, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill <PixelARGB, PixelRGB, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
if (tiledFill) { TransformedImageFill<PixelARGB, PixelRGB, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill<PixelARGB, PixelRGB, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
break;
default:
if (tiledFill) { TransformedImageFill <PixelARGB, PixelAlpha, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill <PixelARGB, PixelAlpha, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
if (tiledFill) { TransformedImageFill<PixelARGB, PixelAlpha, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill<PixelARGB, PixelAlpha, false> 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 <PixelRGB, PixelARGB, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill <PixelRGB, PixelARGB, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
if (tiledFill) { TransformedImageFill<PixelRGB, PixelARGB, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill<PixelRGB, PixelARGB, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
break;
case Image::RGB:
if (tiledFill) { TransformedImageFill <PixelRGB, PixelRGB, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill <PixelRGB, PixelRGB, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
if (tiledFill) { TransformedImageFill<PixelRGB, PixelRGB, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill<PixelRGB, PixelRGB, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
break;
default:
if (tiledFill) { TransformedImageFill <PixelRGB, PixelAlpha, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill <PixelRGB, PixelAlpha, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
if (tiledFill) { TransformedImageFill<PixelRGB, PixelAlpha, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill<PixelRGB, PixelAlpha, false> 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 <PixelAlpha, PixelARGB, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill <PixelAlpha, PixelARGB, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
if (tiledFill) { TransformedImageFill<PixelAlpha, PixelARGB, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill<PixelAlpha, PixelARGB, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
break;
case Image::RGB:
if (tiledFill) { TransformedImageFill <PixelAlpha, PixelRGB, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill <PixelAlpha, PixelRGB, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
if (tiledFill) { TransformedImageFill<PixelAlpha, PixelRGB, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill<PixelAlpha, PixelRGB, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
break;
default:
if (tiledFill) { TransformedImageFill <PixelAlpha, PixelAlpha, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill <PixelAlpha, PixelAlpha, false> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
if (tiledFill) { TransformedImageFill<PixelAlpha, PixelAlpha, true> r (destData, srcData, transform, alpha, quality); iter.iterate (r); }
else { TransformedImageFill<PixelAlpha, PixelAlpha, false> 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 <PixelARGB, PixelARGB, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill <PixelARGB, PixelARGB, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
if (tiledFill) { ImageFill<PixelARGB, PixelARGB, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill<PixelARGB, PixelARGB, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
break;
case Image::RGB:
if (tiledFill) { ImageFill <PixelARGB, PixelRGB, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill <PixelARGB, PixelRGB, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
if (tiledFill) { ImageFill<PixelARGB, PixelRGB, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill<PixelARGB, PixelRGB, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
break;
default:
if (tiledFill) { ImageFill <PixelARGB, PixelAlpha, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill <PixelARGB, PixelAlpha, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
if (tiledFill) { ImageFill<PixelARGB, PixelAlpha, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill<PixelARGB, PixelAlpha, false> 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 <PixelRGB, PixelARGB, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill <PixelRGB, PixelARGB, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
if (tiledFill) { ImageFill<PixelRGB, PixelARGB, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill<PixelRGB, PixelARGB, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
break;
case Image::RGB:
if (tiledFill) { ImageFill <PixelRGB, PixelRGB, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill <PixelRGB, PixelRGB, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
if (tiledFill) { ImageFill<PixelRGB, PixelRGB, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill<PixelRGB, PixelRGB, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
break;
default:
if (tiledFill) { ImageFill <PixelRGB, PixelAlpha, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill <PixelRGB, PixelAlpha, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
if (tiledFill) { ImageFill<PixelRGB, PixelAlpha, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill<PixelRGB, PixelAlpha, false> 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 <PixelAlpha, PixelARGB, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill <PixelAlpha, PixelARGB, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
if (tiledFill) { ImageFill<PixelAlpha, PixelARGB, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill<PixelAlpha, PixelARGB, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
break;
case Image::RGB:
if (tiledFill) { ImageFill <PixelAlpha, PixelRGB, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill <PixelAlpha, PixelRGB, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
if (tiledFill) { ImageFill<PixelAlpha, PixelRGB, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill<PixelAlpha, PixelRGB, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
break;
default:
if (tiledFill) { ImageFill <PixelAlpha, PixelAlpha, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill <PixelAlpha, PixelAlpha, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
if (tiledFill) { ImageFill<PixelAlpha, PixelAlpha, true> r (destData, srcData, alpha, x, y); iter.iterate (r); }
else { ImageFill<PixelAlpha, PixelAlpha, false> r (destData, srcData, alpha, x, y); iter.iterate (r); }
break;
}
break;
@@ -1487,12 +1485,12 @@ namespace EdgeTableFillers
{
if (replaceContents)
{
EdgeTableFillers::SolidColour <DestPixelType, true> r (destData, fillColour);
EdgeTableFillers::SolidColour<DestPixelType, true> r (destData, fillColour);
iter.iterate (r);
}
else
{
EdgeTableFillers::SolidColour <DestPixelType, false> r (destData, fillColour);
EdgeTableFillers::SolidColour<DestPixelType, false> r (destData, fillColour);
iter.iterate (r);
}
}
@@ -1505,18 +1503,18 @@ namespace EdgeTableFillers
{
if (isIdentity)
{
EdgeTableFillers::Gradient <DestPixelType, GradientPixelIterators::Radial> renderer (destData, g, transform, lookupTable, numLookupEntries);
EdgeTableFillers::Gradient<DestPixelType, GradientPixelIterators::Radial> renderer (destData, g, transform, lookupTable, numLookupEntries);
iter.iterate (renderer);
}
else
{
EdgeTableFillers::Gradient <DestPixelType, GradientPixelIterators::TransformedRadial> renderer (destData, g, transform, lookupTable, numLookupEntries);
EdgeTableFillers::Gradient<DestPixelType, GradientPixelIterators::TransformedRadial> renderer (destData, g, transform, lookupTable, numLookupEntries);
iter.iterate (renderer);
}
}
else
{
EdgeTableFillers::Gradient <DestPixelType, GradientPixelIterators::Linear> renderer (destData, g, transform, lookupTable, numLookupEntries);
EdgeTableFillers::Gradient<DestPixelType, GradientPixelIterators::Linear> renderer (destData, g, transform, lookupTable, numLookupEntries);
iter.iterate (renderer);
}
}
@@ -1721,7 +1719,7 @@ struct ClipRegions
template <class SrcPixelType>
void transformedClipImage (const Image::BitmapData& srcData, const AffineTransform& transform, const Graphics::ResamplingQuality quality, const SrcPixelType*)
{
EdgeTableFillers::TransformedImageFill <SrcPixelType, SrcPixelType, false> renderer (srcData, srcData, transform, 255, quality);
EdgeTableFillers::TransformedImageFill<SrcPixelType, SrcPixelType, false> 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<int> r (imageX, imageY, srcData.width, srcData.height);
edgeTable.clipToRectangle (r);
EdgeTableFillers::ImageFill <SrcPixelType, SrcPixelType, false> renderer (srcData, srcData, 255, imageX, imageY);
EdgeTableFillers::ImageFill<SrcPixelType, SrcPixelType, false> 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 <SavedStateType*> (this); }
SavedStateType& getThis() noexcept { return *static_cast<SavedStateType*> (this); }
bool clipToRectangle (const Rectangle<int>& 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 <float>& line)
void drawLine (const Line<float>& line)
{
Path p;
p.addLineSegment (line, 1.0f);
@@ -2437,7 +2444,7 @@ public:
}
}
typedef GlyphCache<CachedGlyphEdgeTable <SoftwareRendererSavedState>, SoftwareRendererSavedState> GlyphCacheType;
typedef GlyphCache<CachedGlyphEdgeTable<SoftwareRendererSavedState>, SoftwareRendererSavedState> GlyphCacheType;
static void clearGlyphCache()
{
@@ -2523,7 +2530,7 @@ public:
template <typename IteratorType>
void fillWithGradient (IteratorType& iter, ColourGradient& gradient, const AffineTransform& trans, bool isIdentity) const
{
HeapBlock <PixelARGB> lookupTable;
HeapBlock<PixelARGB> 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 <float>& line) override { stack->drawLine (line); }
void drawLine (const Line<float>& line) override { stack->drawLine (line); }
void setFont (const Font& newFont) override { stack->font = newFont; }
const Font& getFont() override { return stack->font; }


Loading…
Cancel
Save