mirror of https://github.com/odrling/Aegisub
306 lines
8.8 KiB
C++
306 lines
8.8 KiB
C++
// Copyright (c) 2007, Rodrigo Braz Monteiro
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
// * Neither the name of the Aegisub Group nor the names of its contributors
|
|
// may be used to endorse or promote products derived from this software
|
|
// without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
// Aegisub Project http://www.aegisub.org/
|
|
|
|
/// @file gl_text.cpp
|
|
/// @brief Create and render text using OpenGL
|
|
/// @ingroup video_output
|
|
///
|
|
|
|
#include "gl_text.h"
|
|
|
|
#include "compat.h"
|
|
#include "utils.h"
|
|
|
|
#include <libaegisub/color.h>
|
|
|
|
#include <wx/bitmap.h>
|
|
#include <wx/dcmemory.h>
|
|
|
|
#include <algorithm>
|
|
#include <boost/noncopyable.hpp>
|
|
|
|
#ifdef HAVE_OPENGL_GL_H
|
|
#include <OpenGL/gl.h>
|
|
#else
|
|
#include <GL/gl.h>
|
|
#endif
|
|
|
|
namespace {
|
|
/// @class OpenGLTextGlyph
|
|
/// @brief Struct storing the information needed to draw a glyph
|
|
struct OpenGLTextGlyph {
|
|
wxString str; ///< String containing the glyph(s) this is for
|
|
int tex = 0; ///< OpenGL texture to draw for this glyph
|
|
float x1 = 0; ///< Left x coordinate of this glyph in the containing texture
|
|
float x2 = 0; ///< Right x coordinate of this glyph in the containing texture
|
|
float y1 = 0; ///< Left y coordinate of this glyph in the containing texture
|
|
float y2 = 0; ///< Right y coordinate of this glyph in the containing texture
|
|
int w = 0; ///< Width of the glyph in pixels
|
|
int h = 0; ///< Height of the glyph in pixels
|
|
wxFont font; ///< Font used for this glyph
|
|
|
|
OpenGLTextGlyph(int value, wxFont const& font)
|
|
: str(wxChar(value))
|
|
, font(font)
|
|
{
|
|
wxCoord desc,lead;
|
|
|
|
wxBitmap tempBmp(32, 32, 24);
|
|
wxMemoryDC dc(tempBmp);
|
|
dc.SetFont(font);
|
|
dc.GetTextExtent(str, &w, &h, &desc, &lead);
|
|
}
|
|
|
|
void Draw(float x, float y) const {
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
|
|
float tex_coords[] = {
|
|
x1, y1,
|
|
x1, y2,
|
|
x2, y2,
|
|
x2, y1
|
|
};
|
|
|
|
float vert_coords[] = {
|
|
x, y,
|
|
x, y + h,
|
|
x + w, y + h,
|
|
x + w, y
|
|
};
|
|
|
|
glVertexPointer(2, GL_FLOAT, 0, vert_coords);
|
|
glTexCoordPointer(2, GL_FLOAT, 0, tex_coords);
|
|
glDrawArrays(GL_QUADS, 0, 4);
|
|
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
}
|
|
};
|
|
|
|
/// @class OpenGLTextTexture
|
|
/// @brief OpenGL texture which stores one or more glyphs as sprites
|
|
class OpenGLTextTexture final : boost::noncopyable {
|
|
int x = 0; ///< Next x coordinate at which a glyph can be inserted
|
|
int y = 0; ///< Next y coordinate at which a glyph can be inserted
|
|
int nextY = 0; ///< Y coordinate of the next line; tracked due to that lines
|
|
///< are only as tall as needed to fit the glyphs in them
|
|
int width; ///< Width of the texture
|
|
int height; ///< Height of the texture
|
|
GLuint tex = 0; ///< The texture
|
|
|
|
/// Insert the glyph into this texture at the current coordinates
|
|
void Insert(OpenGLTextGlyph &glyph) {
|
|
int w = glyph.w;
|
|
int h = glyph.h;
|
|
|
|
// Fill glyph structure
|
|
glyph.x1 = float(x)/width;
|
|
glyph.y1 = float(y)/height;
|
|
glyph.x2 = float(x+w)/width;
|
|
glyph.y2 = float(y+h)/height;
|
|
glyph.tex = tex;
|
|
|
|
// Create bitmap and bind it to a DC
|
|
wxBitmap bmp(w + (w & 1), h + (h & 1), 24);
|
|
wxMemoryDC dc(bmp);
|
|
|
|
// Draw text and convert to image
|
|
dc.SetBackground(*wxBLACK_BRUSH);
|
|
dc.Clear();
|
|
dc.SetFont(glyph.font);
|
|
dc.SetTextForeground(*wxWHITE);
|
|
dc.DrawText(glyph.str, 0, 0);
|
|
|
|
// Convert RGB24 to Luminance + Alpha by using an arbitrary channel as A
|
|
wxImage img = bmp.ConvertToImage();
|
|
int imgw = img.GetWidth();
|
|
int imgh = img.GetHeight();
|
|
std::vector<unsigned char> alpha(imgw * imgh * 2, 255);
|
|
const unsigned char *read = img.GetData();
|
|
for (size_t write = 1; write < alpha.size(); write += 2, read += 3)
|
|
alpha[write] = *read;
|
|
|
|
// Upload image to video memory
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, imgw, imgh, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, &alpha[0]);
|
|
if (glGetError()) throw "Internal OpenGL text renderer error: Error uploading glyph data to video memory.";
|
|
}
|
|
|
|
public:
|
|
OpenGLTextTexture(OpenGLTextGlyph &glyph)
|
|
: width(std::max(SmallestPowerOf2(glyph.w), 64))
|
|
, height(std::max(SmallestPowerOf2(glyph.h), 64))
|
|
{
|
|
width = height = std::max(width, height);
|
|
|
|
// Generate and bind
|
|
glGenTextures(1, &tex);
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
|
|
|
// Texture parameters
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
|
|
|
|
// Allocate texture
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, nullptr);
|
|
if (glGetError()) throw "Internal OpenGL text renderer error: Could not allocate Text Texture";
|
|
|
|
TryToInsert(glyph);
|
|
}
|
|
|
|
OpenGLTextTexture(OpenGLTextTexture&& rhs) BOOST_NOEXCEPT
|
|
: x(rhs.x)
|
|
, y(rhs.y)
|
|
, nextY(rhs.nextY)
|
|
, width(rhs.width)
|
|
, height(rhs.height)
|
|
, tex(rhs.tex)
|
|
{
|
|
rhs.tex = 0;
|
|
}
|
|
|
|
~OpenGLTextTexture() {
|
|
if (tex) glDeleteTextures(1, &tex);
|
|
}
|
|
|
|
/// @brief Try to insert a glyph into this texture
|
|
/// @param[in][out] glyph Texture to insert
|
|
/// @return Was the texture successfully added?
|
|
bool TryToInsert(OpenGLTextGlyph &glyph) {
|
|
if (glyph.w > width) return false;
|
|
if (y + glyph.h > height) return false;
|
|
|
|
// Can fit in this row?
|
|
if (x + glyph.w < width) {
|
|
Insert(glyph);
|
|
x += glyph.w;
|
|
nextY = std::max(nextY, y + glyph.h);
|
|
return true;
|
|
}
|
|
|
|
// Can fit the next row?
|
|
if (nextY + glyph.h > height) return false;
|
|
x = 0;
|
|
y = nextY;
|
|
return TryToInsert(glyph);
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
OpenGLText::OpenGLText() { }
|
|
OpenGLText::~OpenGLText() { }
|
|
|
|
void OpenGLText::SetFont(std::string const& face, int size, bool bold, bool italics) {
|
|
// No change required
|
|
if (size == fontSize && face == fontFace && bold == fontBold && italics == fontItalics) return;
|
|
|
|
// Set font
|
|
fontFace = face;
|
|
fontSize = size;
|
|
fontBold = bold;
|
|
fontItalics = italics;
|
|
font.SetFaceName(to_wx(fontFace));
|
|
font.SetPointSize(size);
|
|
font.SetWeight(bold ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL);
|
|
|
|
// Delete all old data
|
|
textures.clear();
|
|
glyphs.clear();
|
|
}
|
|
|
|
void OpenGLText::SetColour(agi::Color col) {
|
|
r = col.r / 255.f;
|
|
g = col.g / 255.f;
|
|
b = col.b / 255.f;
|
|
a = col.a / 255.f;
|
|
}
|
|
|
|
void OpenGLText::Print(const std::string &text, int x, int y) {
|
|
glEnable(GL_BLEND);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
// Draw border
|
|
glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
|
|
DrawString(text, x-1, y);
|
|
DrawString(text, x+1, y);
|
|
DrawString(text, x, y-1);
|
|
DrawString(text, x, y+1);
|
|
|
|
// Draw primary string
|
|
glColor4f(r, g, b, a);
|
|
DrawString(text, x, y);
|
|
|
|
// Disable blend
|
|
glDisable(GL_TEXTURE_2D);
|
|
glDisable(GL_BLEND);
|
|
}
|
|
|
|
void OpenGLText::DrawString(const std::string &text, int x, int y) {
|
|
for (char curChar : text) {
|
|
OpenGLTextGlyph const& glyph = GetGlyph(curChar);
|
|
glyph.Draw(x, y);
|
|
x += glyph.w;
|
|
}
|
|
}
|
|
|
|
void OpenGLText::GetExtent(std::string const& text, int &w, int &h) {
|
|
w = h = 0;
|
|
|
|
for (char curChar : text) {
|
|
OpenGLTextGlyph const& glyph = GetGlyph(curChar);
|
|
w += glyph.w;
|
|
h = std::max(h, glyph.h);
|
|
}
|
|
}
|
|
|
|
OpenGLTextGlyph const& OpenGLText::GetGlyph(int i) {
|
|
auto res = glyphs.find(i);
|
|
return res != glyphs.end() ? res->second : CreateGlyph(i);
|
|
}
|
|
|
|
OpenGLTextGlyph const& OpenGLText::CreateGlyph(int n) {
|
|
OpenGLTextGlyph &glyph = glyphs.insert(std::make_pair(n, OpenGLTextGlyph(n, font))).first->second;
|
|
|
|
// Insert into some texture
|
|
for (auto& texture : textures) {
|
|
if (texture.TryToInsert(glyph))
|
|
return glyph;
|
|
}
|
|
|
|
// No texture could fit it, create a new one
|
|
textures.emplace_back(glyph);
|
|
return glyph;
|
|
}
|