Separated atlases from Font class
First use of shared_ptr, yay! Former-commit-id: 4c22341fba5674746d5299cf0e8dbf6dc31d5362
This commit is contained in:
parent
b61ff3a0d1
commit
b6c5668232
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright (C) 2015 Jérôme Leclercq
|
||||||
|
// This file is part of the "Nazara Engine - Graphics module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NAZARA_GUILLOTINETEXTUREATLAS_HPP
|
||||||
|
#define NAZARA_GUILLOTINETEXTUREATLAS_HPP
|
||||||
|
|
||||||
|
#include <Nazara/Prerequesites.hpp>
|
||||||
|
#include <Nazara/Utility/GuillotineImageAtlas.hpp>
|
||||||
|
|
||||||
|
class NAZARA_API NzGuillotineTextureAtlas : public NzGuillotineImageAtlas
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NzGuillotineTextureAtlas() = default;
|
||||||
|
~NzGuillotineTextureAtlas() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned int GetMaxAtlasSize() const override;
|
||||||
|
bool ResizeImage(Layer& layer, const NzVector2ui& size) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NAZARA_GUILLOTINETEXTUREATLAS_HPP
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright (C) 2015 Jérôme Leclercq
|
||||||
|
// This file is part of the "Nazara Engine - Utility module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NAZARA_ABSTRACTFONTATLAS_HPP
|
||||||
|
#define NAZARA_ABSTRACTFONTATLAS_HPP
|
||||||
|
|
||||||
|
#include <Nazara/Prerequesites.hpp>
|
||||||
|
#include <Nazara/Core/SparsePtr.hpp>
|
||||||
|
#include <Nazara/Math/Rect.hpp>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
class NzAbstractImage;
|
||||||
|
class NzFont;
|
||||||
|
class NzImage;
|
||||||
|
|
||||||
|
class NAZARA_API NzAbstractFontAtlas
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NzAbstractFontAtlas() = default;
|
||||||
|
virtual ~NzAbstractFontAtlas();
|
||||||
|
|
||||||
|
virtual void Clear() = 0;
|
||||||
|
virtual void Free(NzSparsePtr<const NzRectui> rects, NzSparsePtr<unsigned int> layers, unsigned int count) = 0;
|
||||||
|
virtual NzAbstractImage* GetLayer(unsigned int layerIndex) const = 0;
|
||||||
|
virtual unsigned int GetLayerCount() const = 0;
|
||||||
|
virtual bool Insert(const NzImage& image, NzRectui* rect, bool* flipped, unsigned int* layerIndex) = 0;
|
||||||
|
void RegisterFont(NzFont* font);
|
||||||
|
void UnregisterFont(NzFont* font);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void NotifyCleared();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::set<NzFont*> m_registredFonts;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NAZARA_ABSTRACTFONTATLAS_HPP
|
||||||
|
|
@ -8,11 +8,10 @@
|
||||||
#define NAZARA_FONT_HPP
|
#define NAZARA_FONT_HPP
|
||||||
|
|
||||||
#include <Nazara/Prerequesites.hpp>
|
#include <Nazara/Prerequesites.hpp>
|
||||||
#include <Nazara/Core/GuillotineBinPack.hpp>
|
|
||||||
#include <Nazara/Core/NonCopyable.hpp>
|
#include <Nazara/Core/NonCopyable.hpp>
|
||||||
#include <Nazara/Core/ResourceRef.hpp>
|
#include <Nazara/Core/ResourceRef.hpp>
|
||||||
#include <Nazara/Core/ResourceLoader.hpp>
|
#include <Nazara/Core/ResourceLoader.hpp>
|
||||||
#include <Nazara/Utility/Image.hpp>
|
#include <Nazara/Utility/AbstractFontAtlas.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
|
@ -24,7 +23,7 @@ struct NAZARA_API NzFontParams
|
||||||
class NzFont;
|
class NzFont;
|
||||||
class NzFontData;
|
class NzFontData;
|
||||||
|
|
||||||
struct NzFontGlyph; // TEMP
|
struct NzFontGlyph;
|
||||||
|
|
||||||
using NzFontConstRef = NzResourceRef<const NzFont>;
|
using NzFontConstRef = NzResourceRef<const NzFont>;
|
||||||
using NzFontLoader = NzResourceLoader<NzFont, NzFontParams>;
|
using NzFontLoader = NzResourceLoader<NzFont, NzFontParams>;
|
||||||
|
|
@ -32,31 +31,12 @@ using NzFontRef = NzResourceRef<NzFont>;
|
||||||
|
|
||||||
class NAZARA_API NzFont : public NzResource, NzNonCopyable
|
class NAZARA_API NzFont : public NzResource, NzNonCopyable
|
||||||
{
|
{
|
||||||
|
friend NzAbstractFontAtlas;
|
||||||
friend NzFontLoader;
|
friend NzFontLoader;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct Atlas
|
struct Glyph;
|
||||||
{
|
struct SizeInfo;
|
||||||
NzGuillotineBinPack binPack;
|
|
||||||
NzImage image;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Glyph
|
|
||||||
{
|
|
||||||
NzRecti aabb;
|
|
||||||
NzRectui atlasRect;
|
|
||||||
bool flipped;
|
|
||||||
bool valid;
|
|
||||||
int advance;
|
|
||||||
unsigned int atlasIndex;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SizeInfo
|
|
||||||
{
|
|
||||||
unsigned int lineHeight;
|
|
||||||
float underlinePosition;
|
|
||||||
float underlineThickness;
|
|
||||||
};
|
|
||||||
|
|
||||||
NzFont();
|
NzFont();
|
||||||
NzFont(NzFont&& font) = default;
|
NzFont(NzFont&& font) = default;
|
||||||
|
|
@ -71,14 +51,12 @@ class NAZARA_API NzFont : public NzResource, NzNonCopyable
|
||||||
|
|
||||||
bool ExtractGlyph(unsigned int characterSize, char32_t character, nzUInt32 style, NzFontGlyph* glyph) const;
|
bool ExtractGlyph(unsigned int characterSize, char32_t character, nzUInt32 style, NzFontGlyph* glyph) const;
|
||||||
|
|
||||||
const Atlas& GetAtlas(unsigned int atlasIndex) const;
|
const NzAbstractFontAtlas* GetAtlas() const;
|
||||||
unsigned int GetAtlasCount() const;
|
|
||||||
unsigned int GetCachedGlyphCount(unsigned int characterSize, nzUInt32 style) const;
|
unsigned int GetCachedGlyphCount(unsigned int characterSize, nzUInt32 style) const;
|
||||||
unsigned int GetCachedGlyphCount() const;
|
unsigned int GetCachedGlyphCount() const;
|
||||||
NzString GetFamilyName() const;
|
NzString GetFamilyName() const;
|
||||||
int GetKerning(unsigned int characterSize, char32_t first, char32_t second) const;
|
int GetKerning(unsigned int characterSize, char32_t first, char32_t second) const;
|
||||||
const Glyph& GetGlyph(unsigned int characterSize, nzUInt32 style, char32_t character) const;
|
const Glyph& GetGlyph(unsigned int characterSize, nzUInt32 style, char32_t character) const;
|
||||||
unsigned int GetMaxAtlasSize() const;
|
|
||||||
unsigned int GetMinimumStepSize() const;
|
unsigned int GetMinimumStepSize() const;
|
||||||
const SizeInfo& GetSizeInfo(unsigned int characterSize) const;
|
const SizeInfo& GetSizeInfo(unsigned int characterSize) const;
|
||||||
NzString GetStyleName() const;
|
NzString GetStyleName() const;
|
||||||
|
|
@ -93,41 +71,50 @@ class NAZARA_API NzFont : public NzResource, NzNonCopyable
|
||||||
bool OpenFromMemory(const void* data, std::size_t size, const NzFontParams& params = NzFontParams());
|
bool OpenFromMemory(const void* data, std::size_t size, const NzFontParams& params = NzFontParams());
|
||||||
bool OpenFromStream(NzInputStream& stream, const NzFontParams& params = NzFontParams());
|
bool OpenFromStream(NzInputStream& stream, const NzFontParams& params = NzFontParams());
|
||||||
|
|
||||||
void SetMaxAtlasSize(unsigned int maxAtlasSize);
|
void SetAtlas(std::shared_ptr<NzAbstractFontAtlas> atlas);
|
||||||
void SetMinimumStepSize(unsigned int minimumSizeStep);
|
void SetMinimumStepSize(unsigned int minimumSizeStep);
|
||||||
|
|
||||||
NzFont& operator=(NzFont&& font) = default;
|
NzFont& operator=(NzFont&& font) = default;
|
||||||
|
|
||||||
static unsigned int GetDefaultMaxAtlasSize();
|
enum ModicationCode
|
||||||
static void SetDefaultMaxAtlasSize(unsigned int maxAtlasSize);
|
{
|
||||||
|
ModificationCode_GlyphCacheCleared,
|
||||||
|
ModificationCode_KerningCacheCleared,
|
||||||
|
ModificationCode_SizeInfoCacheCleared
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Glyph
|
||||||
|
{
|
||||||
|
NzRecti aabb;
|
||||||
|
NzRectui atlasRect;
|
||||||
|
bool flipped;
|
||||||
|
bool valid;
|
||||||
|
int advance;
|
||||||
|
unsigned int layerIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SizeInfo
|
||||||
|
{
|
||||||
|
unsigned int lineHeight;
|
||||||
|
float underlinePosition;
|
||||||
|
float underlineThickness;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using GlyphMap = std::unordered_map<char32_t, Glyph>;
|
using GlyphMap = std::unordered_map<char32_t, Glyph>;
|
||||||
|
|
||||||
struct QueuedGlyph
|
|
||||||
{
|
|
||||||
char32_t codepoint;
|
|
||||||
NzImage image;
|
|
||||||
GlyphMap* map;
|
|
||||||
};
|
|
||||||
|
|
||||||
nzUInt64 ComputeKey(unsigned int characterSize, nzUInt32 style) const;
|
nzUInt64 ComputeKey(unsigned int characterSize, nzUInt32 style) const;
|
||||||
unsigned int GetRealMaxAtlasSize() const;
|
void OnAtlasCleared();
|
||||||
unsigned int InsertRect(NzRectui* rect, bool* flipped) const;
|
|
||||||
const Glyph& PrecacheGlyph(GlyphMap& glyphMap, unsigned int characterSize, bool bold, char32_t character) const;
|
const Glyph& PrecacheGlyph(GlyphMap& glyphMap, unsigned int characterSize, bool bold, char32_t character) const;
|
||||||
void ProcessGlyphQueue() const;
|
|
||||||
|
|
||||||
|
std::shared_ptr<NzAbstractFontAtlas> m_atlas;
|
||||||
std::unique_ptr<NzFontData> m_data;
|
std::unique_ptr<NzFontData> m_data;
|
||||||
mutable std::unordered_map<nzUInt64, std::unordered_map<nzUInt64, int>> m_kerningCache;
|
mutable std::unordered_map<nzUInt64, std::unordered_map<nzUInt64, int>> m_kerningCache;
|
||||||
mutable std::unordered_map<nzUInt64, GlyphMap> m_glyphes;
|
mutable std::unordered_map<nzUInt64, GlyphMap> m_glyphes;
|
||||||
mutable std::unordered_map<nzUInt64, SizeInfo> m_sizeInfoCache;
|
mutable std::unordered_map<nzUInt64, SizeInfo> m_sizeInfoCache;
|
||||||
mutable std::vector<Atlas> m_atlases;
|
|
||||||
mutable std::vector<QueuedGlyph> m_glyphQueue;
|
|
||||||
unsigned int m_maxAtlasSize;
|
|
||||||
unsigned int m_minimumSizeStep;
|
unsigned int m_minimumSizeStep;
|
||||||
|
|
||||||
static NzFontLoader::LoaderList s_loaders;
|
static NzFontLoader::LoaderList s_loaders;
|
||||||
static unsigned int s_maxAtlasSize;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // NAZARA_FONT_HPP
|
#endif // NAZARA_FONT_HPP
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Copyright (C) 2015 Jérôme Leclercq
|
||||||
|
// This file is part of the "Nazara Engine - Utility module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NAZARA_GUILLOTINEIMAGEATLAS_HPP
|
||||||
|
#define NAZARA_GUILLOTINEIMAGEATLAS_HPP
|
||||||
|
|
||||||
|
#include <Nazara/Prerequesites.hpp>
|
||||||
|
#include <Nazara/Core/GuillotineBinPack.hpp>
|
||||||
|
#include <Nazara/Utility/AbstractFontAtlas.hpp>
|
||||||
|
#include <Nazara/Utility/AbstractImage.hpp>
|
||||||
|
#include <Nazara/Utility/Image.hpp>
|
||||||
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class NAZARA_API NzGuillotineImageAtlas : public NzAbstractFontAtlas
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NzGuillotineImageAtlas();
|
||||||
|
virtual ~NzGuillotineImageAtlas();
|
||||||
|
|
||||||
|
void Clear();
|
||||||
|
void Free(NzSparsePtr<const NzRectui> rects, NzSparsePtr<unsigned int> layers, unsigned int count);
|
||||||
|
|
||||||
|
NzGuillotineBinPack::FreeRectChoiceHeuristic GetRectChoiceHeuristic() const;
|
||||||
|
NzGuillotineBinPack::GuillotineSplitHeuristic GetRectSplitHeuristic() const;
|
||||||
|
NzAbstractImage* GetLayer(unsigned int layerIndex) const;
|
||||||
|
unsigned int GetLayerCount() const;
|
||||||
|
|
||||||
|
bool Insert(const NzImage& image, NzRectui* rect, bool* flipped, unsigned int* layerIndex);
|
||||||
|
|
||||||
|
void SetRectChoiceHeuristic(NzGuillotineBinPack::FreeRectChoiceHeuristic heuristic);
|
||||||
|
void SetRectSplitHeuristic(NzGuillotineBinPack::GuillotineSplitHeuristic heuristic);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
struct Layer;
|
||||||
|
|
||||||
|
virtual unsigned int GetMaxAtlasSize() const;
|
||||||
|
virtual bool ResizeImage(Layer& layer, const NzVector2ui& size) const;
|
||||||
|
|
||||||
|
struct QueuedGlyph
|
||||||
|
{
|
||||||
|
NzImage image;
|
||||||
|
NzRectui rect;
|
||||||
|
bool flipped;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Layer
|
||||||
|
{
|
||||||
|
std::vector<QueuedGlyph> queuedGlyphs;
|
||||||
|
std::unique_ptr<NzAbstractImage> image;
|
||||||
|
NzGuillotineBinPack binPack;
|
||||||
|
unsigned int freedRectangles = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ProcessGlyphQueue(Layer& layer) const;
|
||||||
|
|
||||||
|
std::set<NzFont*> m_fonts;
|
||||||
|
mutable std::vector<Layer> m_layers;
|
||||||
|
NzGuillotineBinPack::FreeRectChoiceHeuristic m_rectChoiceHeuristic;
|
||||||
|
NzGuillotineBinPack::GuillotineSplitHeuristic m_rectSplitHeuristic;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NAZARA_GUILLOTINEIMAGEATLAS_HPP
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright (C) 2015 Jérôme Leclercq
|
||||||
|
// This file is part of the "Nazara Engine - Graphics module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
#include <Nazara/Graphics/GuillotineTextureAtlas.hpp>
|
||||||
|
#include <Nazara/Renderer/Renderer.hpp>
|
||||||
|
#include <Nazara/Renderer/Texture.hpp>
|
||||||
|
#include <Nazara/Graphics/Debug.hpp>
|
||||||
|
|
||||||
|
bool NzGuillotineTextureAtlas::ResizeImage(Layer& layer, const NzVector2ui& size) const
|
||||||
|
{
|
||||||
|
NzTexture newTexture;
|
||||||
|
if (newTexture.Create(nzImageType_2D, nzPixelFormat_A8, size.x, size.y, 1, 0xFF))
|
||||||
|
{
|
||||||
|
newTexture.EnableMipmapping(true);
|
||||||
|
|
||||||
|
if (layer.image)
|
||||||
|
{
|
||||||
|
NzTexture& texture = *static_cast<NzTexture*>(layer.image.get());
|
||||||
|
|
||||||
|
// Copie des anciennes données
|
||||||
|
///TODO: Copie de texture à texture
|
||||||
|
NzImage image;
|
||||||
|
if (!texture.Download(&image))
|
||||||
|
{
|
||||||
|
NazaraError("Failed to download old texture");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newTexture.Update(image, NzRectui(0, 0, image.GetWidth(), image.GetHeight())))
|
||||||
|
{
|
||||||
|
NazaraError("Failed to update texture");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
texture = std::move(newTexture);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
layer.image.reset(new NzTexture(std::move(newTexture)));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NazaraError("Failed to create texture");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NzGuillotineTextureAtlas::GetMaxAtlasSize() const
|
||||||
|
{
|
||||||
|
///FIXME: D'après la documentation OpenGL, cette valeur n'est qu'une approximation et les texture proxies sont une meilleure solution
|
||||||
|
/// Cependant le test ne se fait pas au même moment, penser à adapter le code pour gérer ce cas ?
|
||||||
|
/// (Cela permettrait au passage de gérer le cas où une image ne peut être allouée car il n'y a pas assez de mémoire contigüe pour la contenir)
|
||||||
|
|
||||||
|
return NzRenderer::GetMaxTextureSize();
|
||||||
|
}
|
||||||
|
|
@ -8,18 +8,12 @@
|
||||||
#include <Nazara/Utility/FontGlyph.hpp>
|
#include <Nazara/Utility/FontGlyph.hpp>
|
||||||
#include <Nazara/Utility/Debug.hpp>
|
#include <Nazara/Utility/Debug.hpp>
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
const unsigned int s_atlasStartSize = 512;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NzFontParams::IsValid() const
|
bool NzFontParams::IsValid() const
|
||||||
{
|
{
|
||||||
return true; // Rien à tester
|
return true; // Rien à tester
|
||||||
}
|
}
|
||||||
|
|
||||||
NzFont::NzFont() :
|
NzFont::NzFont() :
|
||||||
m_maxAtlasSize(0),
|
|
||||||
m_minimumSizeStep(1)
|
m_minimumSizeStep(1)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -31,25 +25,39 @@ NzFont::~NzFont()
|
||||||
|
|
||||||
void NzFont::ClearGlyphCache()
|
void NzFont::ClearGlyphCache()
|
||||||
{
|
{
|
||||||
// Destruction des atlas et glyphes mémorisés
|
if (m_atlas)
|
||||||
m_atlases.clear();
|
{
|
||||||
m_glyphes.clear();
|
if (m_atlas.use_count() > 1) // Au moins une autre police utilise cet atlas, on vire nos glyphes
|
||||||
m_glyphQueue.clear();
|
{
|
||||||
|
for (auto mapIt = m_glyphes.begin(); mapIt != m_glyphes.end(); ++mapIt)
|
||||||
|
{
|
||||||
|
GlyphMap& glyphMap = mapIt->second;
|
||||||
|
for (auto glyphIt = glyphMap.begin(); glyphIt != glyphMap.end(); ++glyphIt)
|
||||||
|
{
|
||||||
|
Glyph& glyph = glyphIt->second;
|
||||||
|
m_atlas->Free(&glyph.atlasRect, &glyph.layerIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Création du premier atlas
|
// Destruction des glyphes mémorisés
|
||||||
m_atlases.resize(1);
|
m_glyphes.clear();
|
||||||
Atlas& atlas = m_atlases.back();
|
NotifyModified(ModificationCode_GlyphCacheCleared);
|
||||||
atlas.binPack.Reset(s_atlasStartSize, s_atlasStartSize);
|
}
|
||||||
|
else
|
||||||
|
m_atlas->Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NzFont::ClearKerningCache()
|
void NzFont::ClearKerningCache()
|
||||||
{
|
{
|
||||||
m_kerningCache.clear();
|
m_kerningCache.clear();
|
||||||
|
NotifyModified(ModificationCode_KerningCacheCleared);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NzFont::ClearSizeInfoCache()
|
void NzFont::ClearSizeInfoCache()
|
||||||
{
|
{
|
||||||
m_sizeInfoCache.clear();
|
m_sizeInfoCache.clear();
|
||||||
|
NotifyModified(ModificationCode_SizeInfoCacheCleared);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NzFont::Create(NzFontData* data)
|
bool NzFont::Create(NzFontData* data)
|
||||||
|
|
@ -66,18 +74,15 @@ bool NzFont::Create(NzFontData* data)
|
||||||
|
|
||||||
m_data.reset(data);
|
m_data.reset(data);
|
||||||
|
|
||||||
ClearGlyphCache(); // Création du premier atlas en mémoire
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NzFont::Destroy()
|
void NzFont::Destroy()
|
||||||
{
|
{
|
||||||
m_atlases.clear();
|
ClearGlyphCache();
|
||||||
|
|
||||||
m_data.reset();
|
m_data.reset();
|
||||||
m_kerningCache.clear();
|
m_kerningCache.clear();
|
||||||
m_glyphes.clear();
|
|
||||||
m_glyphQueue.clear();
|
|
||||||
m_sizeInfoCache.clear();
|
m_sizeInfoCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,17 +99,9 @@ bool NzFont::ExtractGlyph(unsigned int characterSize, char32_t character, nzUInt
|
||||||
return m_data->ExtractGlyph(characterSize, character, style & nzTextStyle_Bold, glyph);
|
return m_data->ExtractGlyph(characterSize, character, style & nzTextStyle_Bold, glyph);
|
||||||
}
|
}
|
||||||
|
|
||||||
const NzFont::Atlas& NzFont::GetAtlas(unsigned int atlasIndex) const
|
const NzAbstractFontAtlas* NzFont::GetAtlas() const
|
||||||
{
|
{
|
||||||
if (!m_glyphQueue.empty())
|
return m_atlas.get();
|
||||||
ProcessGlyphQueue();
|
|
||||||
|
|
||||||
return m_atlases.at(atlasIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int NzFont::GetAtlasCount() const
|
|
||||||
{
|
|
||||||
return m_atlases.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int NzFont::GetCachedGlyphCount(unsigned int characterSize, nzUInt32 style) const
|
unsigned int NzFont::GetCachedGlyphCount(unsigned int characterSize, nzUInt32 style) const
|
||||||
|
|
@ -173,11 +170,6 @@ const NzFont::Glyph& NzFont::GetGlyph(unsigned int characterSize, nzUInt32 style
|
||||||
return PrecacheGlyph(m_glyphes[key], characterSize, style, character);
|
return PrecacheGlyph(m_glyphes[key], characterSize, style, character);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int NzFont::GetMaxAtlasSize() const
|
|
||||||
{
|
|
||||||
return m_maxAtlasSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int NzFont::GetMinimumStepSize() const
|
unsigned int NzFont::GetMinimumStepSize() const
|
||||||
{
|
{
|
||||||
return m_minimumSizeStep;
|
return m_minimumSizeStep;
|
||||||
|
|
@ -266,26 +258,11 @@ bool NzFont::OpenFromStream(NzInputStream& stream, const NzFontParams& params)
|
||||||
return NzFontLoader::LoadFromStream(this, stream, params);
|
return NzFontLoader::LoadFromStream(this, stream, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NzFont::SetMaxAtlasSize(unsigned int maxAtlasSize)
|
void NzFont::SetAtlas(std::shared_ptr<NzAbstractFontAtlas> atlas)
|
||||||
{
|
{
|
||||||
unsigned int oldMaxAtlasSize = GetRealMaxAtlasSize();
|
|
||||||
m_maxAtlasSize = maxAtlasSize;
|
|
||||||
|
|
||||||
// Si l'un de nos atlas dépasse la nouvelle taille, on doit vider le cache
|
|
||||||
maxAtlasSize = GetRealMaxAtlasSize();
|
|
||||||
if (maxAtlasSize < oldMaxAtlasSize)
|
|
||||||
{
|
|
||||||
for (Atlas& atlas : m_atlases)
|
|
||||||
{
|
|
||||||
unsigned int atlasSize = atlas.binPack.GetWidth();
|
|
||||||
if (atlasSize > maxAtlasSize)
|
|
||||||
{
|
|
||||||
NazaraWarning("At least one atlas was over new max atlas size (" + NzString::Number(atlasSize) + " > " + NzString::Number(maxAtlasSize) + "), clearing glyph cache...");
|
|
||||||
ClearGlyphCache();
|
ClearGlyphCache();
|
||||||
return;
|
|
||||||
}
|
m_atlas = atlas;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NzFont::SetMinimumStepSize(unsigned int minimumStepSize)
|
void NzFont::SetMinimumStepSize(unsigned int minimumStepSize)
|
||||||
|
|
@ -299,16 +276,7 @@ void NzFont::SetMinimumStepSize(unsigned int minimumStepSize)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_minimumSizeStep = minimumStepSize;
|
m_minimumSizeStep = minimumStepSize;
|
||||||
}
|
ClearGlyphCache();
|
||||||
|
|
||||||
unsigned int NzFont::GetDefaultMaxAtlasSize()
|
|
||||||
{
|
|
||||||
return s_maxAtlasSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NzFont::SetDefaultMaxAtlasSize(unsigned int maxAtlasSize)
|
|
||||||
{
|
|
||||||
s_maxAtlasSize = maxAtlasSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nzUInt64 NzFont::ComputeKey(unsigned int characterSize, nzUInt32 style) const
|
nzUInt64 NzFont::ComputeKey(unsigned int characterSize, nzUInt32 style) const
|
||||||
|
|
@ -326,58 +294,12 @@ nzUInt64 NzFont::ComputeKey(unsigned int characterSize, nzUInt32 style) const
|
||||||
return (stylePart << 32) | sizePart;
|
return (stylePart << 32) | sizePart;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int NzFont::GetRealMaxAtlasSize() const
|
void NzFont::OnAtlasCleared()
|
||||||
{
|
{
|
||||||
unsigned int maxAtlasSize = (m_maxAtlasSize == 0) ? s_maxAtlasSize : m_maxAtlasSize;
|
// Notre atlas vient d'être vidé, détruisons le cache de glyphe
|
||||||
if (maxAtlasSize == 0)
|
m_glyphes.clear();
|
||||||
maxAtlasSize = std::numeric_limits<unsigned int>::max();
|
|
||||||
|
|
||||||
return maxAtlasSize;
|
NotifyModified(ModificationCode_GlyphCacheCleared);
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int NzFont::InsertRect(NzRectui* rect, bool* flipped) const
|
|
||||||
{
|
|
||||||
///DOC: Tous les pointeurs doivent être valides
|
|
||||||
// Précondition: Un rectangle ne peut pas être plus grand dans une dimension que la taille maximale de l'atlas
|
|
||||||
|
|
||||||
unsigned int maxAtlasSize = GetRealMaxAtlasSize();
|
|
||||||
|
|
||||||
// Cette fonction ne fait qu'insérer un rectangle de façon virtuelle, l'insertion des images se fait après
|
|
||||||
for (unsigned int i = 0; i < m_atlases.size(); ++i)
|
|
||||||
{
|
|
||||||
Atlas& atlas = m_atlases[i];
|
|
||||||
if (atlas.binPack.Insert(rect, flipped, 1, false, NzGuillotineBinPack::RectBestAreaFit, NzGuillotineBinPack::SplitMinimizeArea))
|
|
||||||
// Insertion réussie dans l'un des atlas, pas de question à se poser
|
|
||||||
return i;
|
|
||||||
else if (i == m_atlases.size() - 1) // Dernière itération ?
|
|
||||||
{
|
|
||||||
// Dernier atlas, et le glyphe ne rentre pas, peut-on agrandir la taille de l'atlas ?
|
|
||||||
unsigned int size = atlas.binPack.GetWidth(); // l'atlas étant carré, on ne teste qu'une dimension
|
|
||||||
if (size < maxAtlasSize)
|
|
||||||
{
|
|
||||||
// On peut encore agrandir l'atlas
|
|
||||||
size = std::min(size*2, maxAtlasSize);
|
|
||||||
atlas.binPack.Expand(size, size);
|
|
||||||
|
|
||||||
// On relance la boucle sur le dernier atlas
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// On ne peut plus agrandir le dernier atlas, il est temps d'en créer un nouveau
|
|
||||||
m_atlases.resize(m_atlases.size() + 1);
|
|
||||||
Atlas& newAtlas = m_atlases.back();
|
|
||||||
|
|
||||||
newAtlas.binPack.Reset(s_atlasStartSize, s_atlasStartSize);
|
|
||||||
|
|
||||||
// On laisse la boucle insérer toute seule le rectangle à la prochaine itération
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si nous arrivons ici, c'est qu'une erreur a eu lieu en amont
|
|
||||||
NazaraInternalError("This shouldn't happen");
|
|
||||||
return std::numeric_limits<unsigned int>::max();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const NzFont::Glyph& NzFont::PrecacheGlyph(GlyphMap& glyphMap, unsigned int characterSize, bool bold, char32_t character) const
|
const NzFont::Glyph& NzFont::PrecacheGlyph(GlyphMap& glyphMap, unsigned int characterSize, bool bold, char32_t character) const
|
||||||
|
|
@ -386,7 +308,7 @@ const NzFont::Glyph& NzFont::PrecacheGlyph(GlyphMap& glyphMap, unsigned int char
|
||||||
if (it != glyphMap.end()) // Si le glyphe n'est pas déjà chargé
|
if (it != glyphMap.end()) // Si le glyphe n'est pas déjà chargé
|
||||||
return it->second;
|
return it->second;
|
||||||
|
|
||||||
Glyph glyph;
|
Glyph& glyph = glyphMap[character]; // Insertion du glyphe
|
||||||
glyph.valid = false;
|
glyph.valid = false;
|
||||||
|
|
||||||
// On extrait le glyphe depuis la police
|
// On extrait le glyphe depuis la police
|
||||||
|
|
@ -396,112 +318,39 @@ const NzFont::Glyph& NzFont::PrecacheGlyph(GlyphMap& glyphMap, unsigned int char
|
||||||
glyph.atlasRect.width = fontGlyph.image.GetWidth();
|
glyph.atlasRect.width = fontGlyph.image.GetWidth();
|
||||||
glyph.atlasRect.height = fontGlyph.image.GetHeight();
|
glyph.atlasRect.height = fontGlyph.image.GetHeight();
|
||||||
|
|
||||||
unsigned int maxAtlasSize = GetRealMaxAtlasSize();
|
|
||||||
if (glyph.atlasRect.width <= maxAtlasSize && glyph.atlasRect.height <= maxAtlasSize)
|
|
||||||
{
|
|
||||||
// Insertion du rectangle dans l'un des atlas
|
// Insertion du rectangle dans l'un des atlas
|
||||||
glyph.aabb = fontGlyph.aabb;
|
|
||||||
glyph.advance = fontGlyph.advance;
|
|
||||||
glyph.valid = true;
|
|
||||||
|
|
||||||
if (glyph.atlasRect.width > 0 && glyph.atlasRect.height > 0) // Si l'image contient quelque chose
|
if (glyph.atlasRect.width > 0 && glyph.atlasRect.height > 0) // Si l'image contient quelque chose
|
||||||
{
|
{
|
||||||
// Padding (pour éviter le débordement lors du filtrage)
|
// Padding (pour éviter le débordement lors du filtrage)
|
||||||
const unsigned int padding = 1; // Un pixel entre chaque glyphe
|
const unsigned int padding = 1; // Un pixel de contour
|
||||||
|
|
||||||
glyph.atlasRect.width += padding;
|
glyph.atlasRect.width += padding*2;
|
||||||
glyph.atlasRect.height += padding;
|
glyph.atlasRect.height += padding*2;
|
||||||
|
|
||||||
// Insertion du rectangle dans l'atlas virtuel
|
// Insertion du rectangle dans l'atlas virtuel
|
||||||
glyph.atlasIndex = InsertRect(&glyph.atlasRect, &glyph.flipped);
|
if (!m_atlas->Insert(fontGlyph.image, &glyph.atlasRect, &glyph.flipped, &glyph.layerIndex))
|
||||||
|
|
||||||
glyph.atlasRect.width -= padding;
|
|
||||||
glyph.atlasRect.height -= padding;
|
|
||||||
|
|
||||||
// Mise en queue pour insertion dans l'atlas réel
|
|
||||||
m_glyphQueue.resize(m_glyphQueue.size()+1);
|
|
||||||
QueuedGlyph& queuedGlyph = m_glyphQueue.back();
|
|
||||||
queuedGlyph.codepoint = character;
|
|
||||||
queuedGlyph.image = std::move(fontGlyph.image);
|
|
||||||
queuedGlyph.map = &glyphMap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
NazaraWarning("Glyph \"" + NzString::Unicode(character) + "\" is bigger than max atlas size");
|
NazaraError("Failed to insert glyph into atlas");
|
||||||
|
return glyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compensation du contour (centrage du glyphe)
|
||||||
|
glyph.atlasRect.x += padding;
|
||||||
|
glyph.atlasRect.y += padding;
|
||||||
|
glyph.atlasRect.width -= padding*2;
|
||||||
|
glyph.atlasRect.height -= padding*2;
|
||||||
|
}
|
||||||
|
|
||||||
|
glyph.aabb = fontGlyph.aabb;
|
||||||
|
glyph.advance = fontGlyph.advance;
|
||||||
|
glyph.valid = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NazaraWarning("Failed to extract glyph \"" + NzString::Unicode(character) + "\"");
|
NazaraWarning("Failed to extract glyph \"" + NzString::Unicode(character) + "\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
return glyphMap.insert(std::make_pair(character, std::move(glyph))).first->second;
|
return glyph;
|
||||||
}
|
|
||||||
|
|
||||||
void NzFont::ProcessGlyphQueue() const
|
|
||||||
{
|
|
||||||
for (QueuedGlyph& queuedGlyph : m_glyphQueue)
|
|
||||||
{
|
|
||||||
GlyphMap& glyphMap = *queuedGlyph.map;
|
|
||||||
auto glyphIt = glyphMap.find(queuedGlyph.codepoint);
|
|
||||||
if (glyphIt == glyphMap.end())
|
|
||||||
continue; // Le glyphe a certainement été supprimé du cache avant la mise à jour de l'atlas
|
|
||||||
|
|
||||||
Glyph& glyph = glyphIt->second;
|
|
||||||
Atlas& atlas = m_atlases[glyph.atlasIndex];
|
|
||||||
|
|
||||||
// On s'assure que l'atlas est de la bonne taille
|
|
||||||
NzVector2ui atlasSize(atlas.image.GetWidth(), atlas.image.GetHeight());
|
|
||||||
NzVector2ui binPackSize = atlas.binPack.GetSize();
|
|
||||||
if (atlasSize != binPackSize)
|
|
||||||
{
|
|
||||||
// Création d'une nouvelle image
|
|
||||||
NzImage newAtlas(nzImageType_2D, nzPixelFormat_A8, binPackSize.x, binPackSize.y);
|
|
||||||
if (atlas.image.IsValid())
|
|
||||||
{
|
|
||||||
newAtlas.Copy(atlas.image, NzRectui(atlasSize), NzVector2ui(0, 0)); // On copie les anciennes données
|
|
||||||
|
|
||||||
// On initialise les nouvelles régions
|
|
||||||
newAtlas.Fill(NzColor(255, 255, 255, 0), NzRectui(0, atlasSize.y, binPackSize.x, binPackSize.y - atlasSize.y));
|
|
||||||
newAtlas.Fill(NzColor(255, 255, 255, 0), NzRectui(atlasSize.x, 0, binPackSize.x - atlasSize.x, atlasSize.y));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
newAtlas.Fill(NzColor(255, 255, 255, 0)); // On initialise les pixels
|
|
||||||
|
|
||||||
atlas.image = std::move(newAtlas); // On déplace la nouvelle image vers l'atlas
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int glyphWidth = queuedGlyph.image.GetWidth();
|
|
||||||
unsigned int glyphHeight = queuedGlyph.image.GetHeight();
|
|
||||||
|
|
||||||
// On copie le glyphe dans l'atlas
|
|
||||||
if (glyph.flipped)
|
|
||||||
{
|
|
||||||
// On tourne le glyphe pour qu'il rentre dans le rectangle
|
|
||||||
const nzUInt8* src = queuedGlyph.image.GetConstPixels();
|
|
||||||
nzUInt8* ptr = atlas.image.GetPixels(glyph.atlasRect.x, glyph.atlasRect.y + glyphWidth - 1);
|
|
||||||
|
|
||||||
unsigned int lineStride = atlas.image.GetWidth(); // BPP = 1
|
|
||||||
for (unsigned int y = 0; y < glyphHeight; ++y)
|
|
||||||
{
|
|
||||||
for (unsigned int x = 0; x < glyphWidth; ++x)
|
|
||||||
{
|
|
||||||
*ptr = *src++; // On copie et on avance dans le glyphe
|
|
||||||
ptr -= lineStride; // On remonte d'une ligne
|
|
||||||
}
|
|
||||||
|
|
||||||
ptr += lineStride*glyphWidth + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
atlas.image.Copy(queuedGlyph.image, NzRectui(glyphWidth, glyphHeight), glyph.atlasRect.GetPosition());
|
|
||||||
|
|
||||||
queuedGlyph.image.Destroy(); // On libère l'image dès que possible (pour réduire la consommation)
|
|
||||||
}
|
|
||||||
|
|
||||||
m_glyphQueue.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NzFontLoader::LoaderList NzFont::s_loaders;
|
NzFontLoader::LoaderList NzFont::s_loaders;
|
||||||
unsigned int NzFont::s_maxAtlasSize = 8192; // Valeur totalement arbitraire
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,244 @@
|
||||||
|
// Copyright (C) 2015 Jérôme Leclercq
|
||||||
|
// This file is part of the "Nazara Engine - Utility module"
|
||||||
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||||
|
|
||||||
|
#include <Nazara/Utility/GuillotineImageAtlas.hpp>
|
||||||
|
#include <Nazara/Utility/Config.hpp>
|
||||||
|
#include <Nazara/Utility/Debug.hpp>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const unsigned int s_atlasStartSize = 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
NzGuillotineImageAtlas::NzGuillotineImageAtlas() :
|
||||||
|
m_rectChoiceHeuristic(NzGuillotineBinPack::RectBestAreaFit),
|
||||||
|
m_rectSplitHeuristic(NzGuillotineBinPack::SplitMinimizeArea)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NzGuillotineImageAtlas::~NzGuillotineImageAtlas() = default;
|
||||||
|
|
||||||
|
void NzGuillotineImageAtlas::Clear()
|
||||||
|
{
|
||||||
|
m_layers.clear();
|
||||||
|
NotifyCleared();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NzGuillotineImageAtlas::Free(NzSparsePtr<const NzRectui> rects, NzSparsePtr<unsigned int> layers, unsigned int count)
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
#ifdef NAZARA_DEBUG
|
||||||
|
if (layers[i] >= m_layers.size())
|
||||||
|
{
|
||||||
|
NazaraWarning("Rectangle #" + NzString::Number(i) + " belong to an out-of-bounds layer (" + NzString::Number(i) + " >= " + NzString::Number(m_layers.size()) + ")");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_layers[layers[i]].binPack.FreeRectangle(rects[i]);
|
||||||
|
m_layers[layers[i]].freedRectangles++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NzGuillotineBinPack::FreeRectChoiceHeuristic NzGuillotineImageAtlas::GetRectChoiceHeuristic() const
|
||||||
|
{
|
||||||
|
return m_rectChoiceHeuristic;
|
||||||
|
}
|
||||||
|
|
||||||
|
NzGuillotineBinPack::GuillotineSplitHeuristic NzGuillotineImageAtlas::GetRectSplitHeuristic() const
|
||||||
|
{
|
||||||
|
return m_rectSplitHeuristic;
|
||||||
|
}
|
||||||
|
|
||||||
|
NzAbstractImage* NzGuillotineImageAtlas::GetLayer(unsigned int layerIndex) const
|
||||||
|
{
|
||||||
|
#if NAZARA_UTILITY_SAFE
|
||||||
|
if (layerIndex >= m_layers.size())
|
||||||
|
{
|
||||||
|
NazaraError("Layer index out of range (" + NzString::Number(layerIndex) + " >= " + NzString::Number(m_layers.size()) + ')');
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Layer& layer = m_layers[layerIndex];
|
||||||
|
ProcessGlyphQueue(layer);
|
||||||
|
|
||||||
|
return layer.image.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NzGuillotineImageAtlas::GetLayerCount() const
|
||||||
|
{
|
||||||
|
return m_layers.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NzGuillotineImageAtlas::Insert(const NzImage& image, NzRectui* rect, bool* flipped, unsigned int* layerIndex)
|
||||||
|
{
|
||||||
|
unsigned int maxAtlasSize = GetMaxAtlasSize();
|
||||||
|
|
||||||
|
if (m_layers.empty())
|
||||||
|
{
|
||||||
|
// On créé une première couche s'il n'y en a pas
|
||||||
|
m_layers.resize(1);
|
||||||
|
Layer& layer = m_layers.back();
|
||||||
|
layer.binPack.Reset(s_atlasStartSize, s_atlasStartSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cette fonction ne fait qu'insérer un rectangle de façon virtuelle, l'insertion des images se fait après
|
||||||
|
for (unsigned int i = 0; i < m_layers.size(); ++i)
|
||||||
|
{
|
||||||
|
Layer& layer = m_layers[i];
|
||||||
|
|
||||||
|
// Une fois qu'un certain nombre de rectangles ont étés libérés d'une couche, on fusionne les rectangles libres
|
||||||
|
if (layer.freedRectangles > 10) // Valeur totalement arbitraire
|
||||||
|
{
|
||||||
|
while (layer.binPack.MergeFreeRectangles()); // Tant qu'une fusion est possible
|
||||||
|
layer.freedRectangles = 0; // Et on repart de zéro
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layer.binPack.Insert(rect, flipped, 1, false, m_rectChoiceHeuristic, m_rectSplitHeuristic))
|
||||||
|
{
|
||||||
|
// Insertion réussie dans l'une des couches, on place le glyphe en file d'attente
|
||||||
|
layer.queuedGlyphs.resize(layer.queuedGlyphs.size()+1);
|
||||||
|
QueuedGlyph& glyph = layer.queuedGlyphs.back();
|
||||||
|
glyph.flipped = *flipped;
|
||||||
|
glyph.image = image; // Merci le Copy-On-Write
|
||||||
|
glyph.rect = *rect;
|
||||||
|
|
||||||
|
*layerIndex = i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (i == m_layers.size() - 1) // Dernière itération ?
|
||||||
|
{
|
||||||
|
// Dernière couche, et le glyphe ne rentre pas, peut-on agrandir la taille de l'image ?
|
||||||
|
unsigned int size = layer.binPack.GetWidth(); // l'image étant carrée, on ne teste qu'une dimension
|
||||||
|
if (size < maxAtlasSize)
|
||||||
|
{
|
||||||
|
// On peut encore agrandir la couche
|
||||||
|
size = std::min(size*2, maxAtlasSize);
|
||||||
|
layer.binPack.Expand(size, size);
|
||||||
|
|
||||||
|
// On relance la boucle sur la nouvelle dernière couche
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// On ne peut plus agrandir la dernière couche, il est temps d'en créer une nouvelle
|
||||||
|
m_layers.resize(m_layers.size() + 1);
|
||||||
|
Layer& newLayer = m_layers.back();
|
||||||
|
|
||||||
|
newLayer.binPack.Reset(s_atlasStartSize, s_atlasStartSize);
|
||||||
|
|
||||||
|
// On laisse la boucle insérer toute seule le rectangle à la prochaine itération
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // Normalement impossible
|
||||||
|
}
|
||||||
|
|
||||||
|
void NzGuillotineImageAtlas::SetRectChoiceHeuristic(NzGuillotineBinPack::FreeRectChoiceHeuristic heuristic)
|
||||||
|
{
|
||||||
|
m_rectChoiceHeuristic = heuristic;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NzGuillotineImageAtlas::SetRectSplitHeuristic(NzGuillotineBinPack::GuillotineSplitHeuristic heuristic)
|
||||||
|
{
|
||||||
|
m_rectSplitHeuristic = heuristic;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NzGuillotineImageAtlas::GetMaxAtlasSize() const
|
||||||
|
{
|
||||||
|
return 8192; // Valeur totalement arbitraire
|
||||||
|
}
|
||||||
|
|
||||||
|
void NzGuillotineImageAtlas::ProcessGlyphQueue(Layer& layer) const
|
||||||
|
{
|
||||||
|
std::vector<nzUInt8> pixelBuffer;
|
||||||
|
|
||||||
|
// On s'assure que l'image est de la bonne taille
|
||||||
|
NzVector2ui binPackSize(layer.binPack.GetSize());
|
||||||
|
NzVector2ui imageSize((layer.image) ? layer.image->GetSize() : NzVector3ui(0U));
|
||||||
|
if (binPackSize != imageSize)
|
||||||
|
ResizeImage(layer, binPackSize);
|
||||||
|
|
||||||
|
for (QueuedGlyph& glyph : layer.queuedGlyphs)
|
||||||
|
{
|
||||||
|
unsigned int glyphWidth = glyph.image.GetWidth();
|
||||||
|
unsigned int glyphHeight = glyph.image.GetHeight();
|
||||||
|
|
||||||
|
// Calcul de l'éventuel padding (pixels de contour)
|
||||||
|
unsigned int paddingX;
|
||||||
|
unsigned int paddingY;
|
||||||
|
if (glyph.flipped)
|
||||||
|
{
|
||||||
|
paddingX = (glyph.rect.height - glyphWidth)/2;
|
||||||
|
paddingY = (glyph.rect.width - glyphHeight)/2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
paddingX = (glyph.rect.width - glyphWidth)/2;
|
||||||
|
paddingY = (glyph.rect.height - glyphHeight)/2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paddingX > 0 || paddingY > 0)
|
||||||
|
{
|
||||||
|
// On remplit les contours
|
||||||
|
pixelBuffer.resize(glyph.rect.width * glyph.rect.height);
|
||||||
|
std::memset(pixelBuffer.data(), 0, glyph.rect.width*glyph.rect.height*sizeof(nzUInt8));
|
||||||
|
|
||||||
|
layer.image->Update(pixelBuffer.data(), glyph.rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nzUInt8* pixels;
|
||||||
|
// On copie le glyphe dans l'atlas
|
||||||
|
if (glyph.flipped)
|
||||||
|
{
|
||||||
|
pixelBuffer.resize(glyphHeight * glyphWidth);
|
||||||
|
|
||||||
|
// On tourne le glyphe pour qu'il rentre dans le rectangle
|
||||||
|
const nzUInt8* src = glyph.image.GetConstPixels();
|
||||||
|
nzUInt8* ptr = pixelBuffer.data();
|
||||||
|
|
||||||
|
unsigned int lineStride = glyphWidth*sizeof(nzUInt8); // BPP = 1
|
||||||
|
src += lineStride-1; // Départ en haut à droite
|
||||||
|
for (unsigned int x = 0; x < glyphWidth; ++x)
|
||||||
|
{
|
||||||
|
for (unsigned int y = 0; y < glyphHeight; ++y)
|
||||||
|
{
|
||||||
|
*ptr++ = *src;
|
||||||
|
src += lineStride;
|
||||||
|
}
|
||||||
|
|
||||||
|
src -= glyphHeight*lineStride + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pixels = pixelBuffer.data();
|
||||||
|
std::swap(glyphWidth, glyphHeight);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
pixels = glyph.image.GetConstPixels();
|
||||||
|
|
||||||
|
layer.image->Update(pixels, NzRectui(glyph.rect.x + paddingX, glyph.rect.y + paddingY, glyphWidth, glyphHeight), 0, glyphWidth, glyphHeight);
|
||||||
|
glyph.image.Destroy(); // On libère l'image dès que possible (pour réduire la consommation)
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.queuedGlyphs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NzGuillotineImageAtlas::ResizeImage(Layer& layer, const NzVector2ui& size) const
|
||||||
|
{
|
||||||
|
NzImage newImage(nzImageType_2D, nzPixelFormat_A8, size.x, size.y);
|
||||||
|
if (layer.image)
|
||||||
|
{
|
||||||
|
NzImage& image = *static_cast<NzImage*>(layer.image.get());
|
||||||
|
newImage.Copy(image, NzRectui(size), NzVector2ui(0, 0)); // Copie des anciennes données
|
||||||
|
|
||||||
|
image = std::move(newImage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
layer.image.reset(new NzImage(std::move(newImage)));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue