void TextureAtlas::load(TextureManager& texture_manager, const rainbow::Texture& texture, const DataMap& data, float scale) { R_ASSERT(data, "Failed to load texture"); const rainbow::Image& image = rainbow::Image::decode(data, scale); if (!image.data) return; switch (image.format) { #ifdef GL_OES_compressed_ETC1_RGB8_texture case rainbow::Image::Format::ETC1: texture_manager.upload_compressed( texture, GL_ETC1_RGB8_OES, image.width, image.height, image.size, image.data); break; #endif // ETC1 #ifdef GL_IMG_texture_compression_pvrtc case rainbow::Image::Format::PVRTC: { R_ASSERT(image.depth == 2 || image.depth == 4, kInvalidColorDepth); R_ASSERT(image.channels == 3 || image.channels == 4, "Invalid number of colour channels"); GLint internal = 0; if (image.channels == 3) { internal = (image.depth == 2 ? GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG : GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG); } else { internal = (image.depth == 2 ? GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG : GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG); } texture_manager.upload_compressed( texture, internal, image.width, image.height, image.size, image.data); break; } #endif // PVRTC default: { GLint format = 0; GLint internal = 0; switch (image.channels) { case 1: R_ASSERT(image.depth == 8, kInvalidColorDepth); format = GL_LUMINANCE; internal = GL_LUMINANCE; break; case 2: R_ASSERT(image.depth == 16, kInvalidColorDepth); format = GL_LUMINANCE_ALPHA; internal = GL_LUMINANCE_ALPHA; break; case 3: R_ASSERT(image.depth == 16 || image.depth == 24, kInvalidColorDepth); format = GL_RGB; internal = (image.depth == 16 ? GL_RGBA4 : GL_RGBA8); break; case 4: R_ASSERT(image.depth == 16 || image.depth == 32, kInvalidColorDepth); format = GL_RGBA; internal = (image.depth == 16 ? GL_RGBA4 : GL_RGBA8); break; } texture_manager.upload( texture, internal, image.width, image.height, format, image.data); break; } } }
void FontAtlas::load(TextureManager& texture_manager, const rainbow::Texture& texture, const Data& font) { R_ASSERT(font, "Failed to load font"); for (uint_t i = 0; i < kNumCharacters; ++i) charset_[i].code = i + kASCIIOffset; #if FONTATLAS_EXTENDED > 0 const unsigned int characters[]{ 0x00c5, // LATIN CAPITAL LETTER A WITH RING ABOVE 0x00c6, // LATIN CAPITAL LETTER AE 0x00d8, // LATIN CAPITAL LETTER O WITH STROKE 0x00e5, // LATIN SMALL LETTER A WITH RING ABOVE 0x00e6, // LATIN SMALL LETTER AE 0x00f8, // LATIN SMALL LETTER O WITH STROKE }; for (size_t i = 0; i < array_size(characters); ++i) charset_[kNumCharacters + i].code = characters[i]; #endif FontLibrary library; if (!library) return; FontFace face(library, font); if (!face) return; FT_Set_Char_Size(face, 0, pt_ * kPixelFormat, kDPI, kDPI); const Vec2u& max = max_glyph_size(face, charset_, array_size(charset_)); if (max.is_zero()) return; const Vec2u size(rainbow::ceil_pow2(max.x * kNumGlyphsPerColRow), rainbow::ceil_pow2(max.y * kNumGlyphsPerColRow)); // GL_LUMINANCE8_ALPHA8 buffer Vec2u offset(size.x * size.y * 2, 0); auto buffer = std::make_unique<GLubyte[]>(offset.x); std::fill_n(buffer.get(), offset.x, 0); // Copy all glyph bitmaps to our texture. offset.x = 0; const Vec2f pixel(1.0f / size.x, 1.0f / size.y); for (auto& glyph : charset_) { FT_Load_Char(face, glyph.code, FT_LOAD_RENDER); const FT_GlyphSlot& slot = face->glyph; const FT_Bitmap& bitmap = slot->bitmap; // Make sure bitmap data has enough space to the right. Otherwise, start // a new line. const uint_t width = bitmap.width + kGlyphPadding2; if (offset.x + width > size.x) { offset.x = 0; offset.y += max.y; } // Copy bitmap data to texture. if (bitmap.buffer) { R_ASSERT(bitmap.num_grays == 256, ""); R_ASSERT(bitmap.pixel_mode == FT_PIXEL_MODE_GRAY, ""); copy_bitmap_into(buffer.get(), size, offset + kGlyphPadding, bitmap.buffer, Vec2u(bitmap.width, bitmap.rows)); } // Save font glyph data. glyph.advance = slot->advance.x / kPixelFormat; glyph.left = slot->bitmap_left; SpriteVertex* vx = glyph.quad; vx[0].position.x = -kGlyphMargin; vx[0].position.y = static_cast<float>(slot->bitmap_top - static_cast<int>(bitmap.rows)) - kGlyphMargin; vx[1].position.x = static_cast<float>(bitmap.width) + kGlyphMargin; vx[1].position.y = vx[0].position.y; vx[2].position.x = vx[1].position.x; vx[2].position.y = static_cast<float>(slot->bitmap_top) + kGlyphMargin; vx[3].position.x = vx[0].position.x; vx[3].position.y = vx[2].position.y; vx[0].texcoord.x = (kGlyphPadding - kGlyphMargin + offset.x) * pixel.x; vx[0].texcoord.y = (kGlyphPadding + bitmap.rows + kGlyphMargin + offset.y) * pixel.y; vx[1].texcoord.x = (kGlyphPadding + bitmap.width + kGlyphMargin + offset.x) * pixel.x; vx[1].texcoord.y = vx[0].texcoord.y; vx[2].texcoord.x = vx[1].texcoord.x; vx[2].texcoord.y = (kGlyphPadding - kGlyphMargin + offset.y) * pixel.y; vx[3].texcoord.x = vx[0].texcoord.x; vx[3].texcoord.y = vx[2].texcoord.y; #ifdef FONTATLAS_KERNING if (FT_HAS_KERNING(face)) { int i = -1; FT_Vector kerning; for (auto& left : charset_) { FT_Get_Kerning( face, left.code, glyph.code, FT_KERNING_DEFAULT, &kerning); glyph.kern[++i] = static_cast<short>(kerning.x / kPixelFormat); } } #endif // Advance to the next "slot" in our texture. offset.x += width; } height_ = face->size->metrics.height / kPixelFormat; texture_manager.upload(texture, GL_LUMINANCE_ALPHA, size.x, size.y, GL_LUMINANCE_ALPHA, buffer.get()); }