// 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 #include #include #include #include #include #ifdef HAVE_OPENGL_GL_H #include #else #include #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 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 agi::EnvironmentError("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 agi::EnvironmentError("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.emplace(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; }