/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-11 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #ifndef __JUCE_RENDERINGHELPERS_JUCEHEADER__ #define __JUCE_RENDERINGHELPERS_JUCEHEADER__ namespace RenderingHelpers { //============================================================================== /** Holds either a simple integer translation, or an affine transform. */ class TranslationOrTransform { public: TranslationOrTransform (int xOffset_, int yOffset_) noexcept : xOffset (xOffset_), yOffset (yOffset_), isOnlyTranslated (true) { } TranslationOrTransform (const TranslationOrTransform& other) noexcept : complexTransform (other.complexTransform), xOffset (other.xOffset), yOffset (other.yOffset), isOnlyTranslated (other.isOnlyTranslated) { } AffineTransform getTransform() const noexcept { return isOnlyTranslated ? AffineTransform::translation ((float) xOffset, (float) yOffset) : complexTransform; } AffineTransform getTransformWith (const AffineTransform& userTransform) const noexcept { return isOnlyTranslated ? userTransform.translated ((float) xOffset, (float) yOffset) : userTransform.followedBy (complexTransform); } void setOrigin (const int x, const int y) noexcept { if (isOnlyTranslated) { xOffset += x; yOffset += y; } else { complexTransform = AffineTransform::translation ((float) x, (float) y) .followedBy (complexTransform); } } void addTransform (const AffineTransform& t) noexcept { if (isOnlyTranslated && t.isOnlyTranslation() && isIntegerTranslation (t)) { xOffset += (int) t.getTranslationX(); yOffset += (int) t.getTranslationY(); } else { complexTransform = getTransformWith (t); isOnlyTranslated = false; } } float getScaleFactor() const noexcept { return isOnlyTranslated ? 1.0f : complexTransform.getScaleFactor(); } void moveOriginInDeviceSpace (const int dx, const int dy) noexcept { if (isOnlyTranslated) { xOffset += dx; yOffset += dy; } else { complexTransform = complexTransform.translated ((float) dx, (float) dy); } } template Rectangle translated (const Rectangle& r) const noexcept { jassert (isOnlyTranslated); return r.translated (static_cast (xOffset), static_cast (yOffset)); } Rectangle deviceSpaceToUserSpace (const Rectangle& r) const noexcept { return isOnlyTranslated ? r.translated (-xOffset, -yOffset) : r.toFloat().transformed (complexTransform.inverted()).getSmallestIntegerContainer(); } AffineTransform complexTransform; int xOffset, yOffset; bool isOnlyTranslated; private: static inline bool isIntegerTranslation (const AffineTransform& t) noexcept { const int tx = (int) (t.getTranslationX() * 256.0f); const int ty = (int) (t.getTranslationY() * 256.0f); return ((tx | ty) & 0xf8) == 0; } }; //============================================================================== template class GlyphCache : private DeletedAtShutdown { public: GlyphCache() { addNewGlyphSlots (120); } ~GlyphCache() { getSingletonPointer() = nullptr; } static GlyphCache& getInstance() { GlyphCache*& g = getSingletonPointer(); if (g == nullptr) g = new GlyphCache(); return *g; } //============================================================================== void drawGlyph (RenderTargetType& target, const Font& font, const int glyphNumber, float x, float y) { ++accessCounter; CachedGlyphType* glyph = nullptr; const ScopedReadLock srl (lock); for (int i = glyphs.size(); --i >= 0;) { CachedGlyphType* const g = glyphs.getUnchecked (i); if (g->glyph == glyphNumber && g->font == font) { glyph = g; ++hits; break; } } if (glyph == nullptr) { ++misses; const ScopedWriteLock swl (lock); if (hits.value + misses.value > glyphs.size() * 16) { if (misses.value * 2 > hits.value) addNewGlyphSlots (32); hits.set (0); misses.set (0); glyph = glyphs.getLast(); } else { glyph = findLeastRecentlyUsedGlyph(); } jassert (glyph != nullptr); glyph->generate (font, glyphNumber); } glyph->lastAccessCount = accessCounter.value; glyph->draw (target, x, y); } private: friend class OwnedArray ; OwnedArray glyphs; Atomic accessCounter, hits, misses; ReadWriteLock lock; void addNewGlyphSlots (int num) { while (--num >= 0) glyphs.add (new CachedGlyphType()); } CachedGlyphType* findLeastRecentlyUsedGlyph() const noexcept { CachedGlyphType* oldest = glyphs.getLast(); int oldestCounter = oldest->lastAccessCount; for (int i = glyphs.size() - 1; --i >= 0;) { CachedGlyphType* const glyph = glyphs.getUnchecked(i); if (glyph->lastAccessCount <= oldestCounter) { oldestCounter = glyph->lastAccessCount; oldest = glyph; } } return oldest; } static GlyphCache*& getSingletonPointer() noexcept { static GlyphCache* g = nullptr; return g; } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GlyphCache); }; //============================================================================== template class CachedGlyphEdgeTable { public: CachedGlyphEdgeTable() : glyph (0), lastAccessCount (0) {} void draw (RendererType& state, float x, const float y) const { if (snapToIntegerCoordinate) x = std::floor (x + 0.5f); if (edgeTable != nullptr) state.fillEdgeTable (*edgeTable, x, roundToInt (y)); } void generate (const Font& newFont, const int glyphNumber) { font = newFont; Typeface* const typeface = newFont.getTypeface(); snapToIntegerCoordinate = typeface->isHinted(); glyph = glyphNumber; const float fontHeight = font.getHeight(); edgeTable = typeface->getEdgeTableForGlyph (glyphNumber, AffineTransform::scale (fontHeight * font.getHorizontalScale(), fontHeight) #if JUCE_MAC || JUCE_IOS .translated (0.0f, -0.5f) #endif ); } Font font; int glyph, lastAccessCount; bool snapToIntegerCoordinate; private: ScopedPointer edgeTable; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedGlyphEdgeTable); }; //============================================================================== template class SavedStateStack { public: SavedStateStack (StateObjectType* const initialState) noexcept : currentState (initialState) {} inline StateObjectType* operator->() const noexcept { return currentState; } inline StateObjectType& operator*() const noexcept { return *currentState; } void save() { stack.add (new StateObjectType (*currentState)); } void restore() { StateObjectType* const top = stack.getLast(); if (top != nullptr) { currentState = top; stack.removeLast (1, false); } else { jassertfalse; // trying to pop with an empty stack! } } void beginTransparencyLayer (float opacity) { save(); currentState = currentState->beginTransparencyLayer (opacity); } void endTransparencyLayer() { const ScopedPointer finishedTransparencyLayer (currentState); restore(); currentState->endTransparencyLayer (*finishedTransparencyLayer); } private: ScopedPointer currentState; OwnedArray stack; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedStateStack); }; //============================================================================== // Calculates the alpha values and positions for rendering the edges of a non-pixel // aligned rectangle. struct FloatRectangleRasterisingInfo { FloatRectangleRasterisingInfo (const Rectangle& area) : left (roundToInt (256.0f * area.getX())), top (roundToInt (256.0f * area.getY())), right (roundToInt (256.0f * area.getRight())), bottom (roundToInt (256.0f * area.getBottom())) { if ((top >> 8) == (bottom >> 8)) { topAlpha = bottom - top; bottomAlpha = 0; totalTop = top >> 8; totalBottom = bottom = top = totalTop + 1; } else { if ((top & 255) == 0) { topAlpha = 0; top = totalTop = (top >> 8); } else { topAlpha = 255 - (top & 255); totalTop = (top >> 8); top = totalTop + 1; } bottomAlpha = bottom & 255; bottom >>= 8; totalBottom = bottom + (bottomAlpha != 0 ? 1 : 0); } if ((left >> 8) == (right >> 8)) { leftAlpha = right - left; rightAlpha = 0; totalLeft = (left >> 8); totalRight = right = left = totalLeft + 1; } else { if ((left & 255) == 0) { leftAlpha = 0; left = totalLeft = (left >> 8); } else { leftAlpha = 255 - (left & 255); totalLeft = (left >> 8); left = totalLeft + 1; } rightAlpha = right & 255; right >>= 8; totalRight = right + (rightAlpha != 0 ? 1 : 0); } } template void iterate (Callback& callback) const { if (topAlpha != 0) callback (totalLeft, totalTop, totalRight - totalLeft, 1, topAlpha); if (bottomAlpha != 0) callback (totalLeft, bottom, totalRight - totalLeft, 1, bottomAlpha); if (leftAlpha != 0) callback (totalLeft, totalTop, 1, totalBottom - totalTop, leftAlpha); if (rightAlpha != 0) callback (right, totalTop, 1, totalBottom - totalTop, rightAlpha); callback (left, top, right - left, bottom - top, 255); } inline bool isOnePixelWide() const noexcept { return right - left == 1 && leftAlpha + rightAlpha == 0; } inline int getTopLeftCornerAlpha() const noexcept { return (topAlpha * leftAlpha) >> 8; } inline int getTopRightCornerAlpha() const noexcept { return (topAlpha * rightAlpha) >> 8; } inline int getBottomLeftCornerAlpha() const noexcept { return (bottomAlpha * leftAlpha) >> 8; } inline int getBottomRightCornerAlpha() const noexcept { return (bottomAlpha * rightAlpha) >> 8; } //============================================================================== int left, top, right, bottom; // bounds of the solid central area, excluding anti-aliased edges int totalTop, totalLeft, totalBottom, totalRight; // bounds of the total area, including edges int topAlpha, leftAlpha, bottomAlpha, rightAlpha; // alpha of each anti-aliased edge }; } #endif // __JUCE_RENDERINGHELPERS_JUCEHEADER__