static uint32 find_best_size(uint32 img_count, const image_t** imgs) { static uint32 texture_size[] = { 128, 256, 512, 1024, 2048 }; bool success = true; uint32 size = 0; uint32 r; uint32 s; stbrp_rect* rects = (stbrp_rect*)malloc(sizeof(stbrp_rect) * img_count); for( r = 0; r < img_count; ++r ) { rects[r] = image_to_rect(r, imgs[r]); } for( s = 0; s < sizeof(texture_size) / sizeof(uint32); ++s ) { stbrp_context ctx; uint32 r; uint32 width = texture_size[s]; stbrp_node* nodes = (stbrp_node*)malloc(sizeof(stbrp_node) * width * 2); memset(nodes, 0, sizeof(stbrp_node) * width * 2); stbrp_init_target(&ctx, (sint32)width, (sint32)width, nodes, (sint32)width * 2); stbrp_pack_rects(&ctx, rects, (sint32)img_count); free(nodes); /* check if all rectangles were packed */ success = true; for( r = 0; r < img_count; ++r ) { if( !rects[r].was_packed ) { success = false; size = 0; break; } } if( success ) { size = width; break; } } free(rects); if( !success ) return 0; else return size; }
bool MTR_FontTryToPackTtf(uint32_t ttfNum, int textureSide, int currentAtlas, stbrp_rect *glyph) { stbrp_node *node; int nodesCount; stbrp_context rectPackContext; int i; int glyphWidth; int glyphHeight; bool success = true; nodesCount = textureSide * 2; node = malloc(sizeof(stbrp_node) * nodesCount); if (node == NULL) { MTR_LogWrite( "Unable to allocate memory for nodes needed by packing algorithm", 2, MTR_LMT_ERROR); return false; } for (i = 0; i < 256; i++) { glyph[i].id = i; glyph[i].x = 0; glyph[i].y = 0; glyph[i].was_packed = false; MTR_TtfGetGlyphSizes(ttfNum, (currentAtlas * 256) + i, &glyphWidth, &glyphHeight); glyph[i].w = glyphWidth; glyph[i].h = glyphHeight; } stbrp_init_target (&rectPackContext, textureSide, textureSide, node, nodesCount); // stbrp_setup_allow_out_of_mem (&rectPackContext, true); stbrp_pack_rects(&rectPackContext, glyph, 256); for (i = 0; i < 256; i++) { if (!glyph[i].was_packed) { success = false; break; } } free(node); return success; }
unsigned int TextureAtlas::addTexture(const Image& image) { if (m_isSheet) { JOP_DEBUG_WARNING("Texture being packed into atlas is a sheet, not a single image"); return 0; } // Find an empty spot in the texture // Add padding to avoid pixel overflow (1 empty pixel) stbrp_rect rectangle = {0, static_cast<stbrp_coord>(image.getSize().x + 1), static_cast<stbrp_coord>(image.getSize().y + 1)}; stbrp_pack_rects(&m_packer->context, &rectangle, 1); rectangle.w -= 1; rectangle.h -= 1; if (rectangle.was_packed) { // Pass pixel data to texture if (image.getPixels()) { m_texture.setPixels(glm::uvec2(rectangle.x, rectangle.y), image); m_textures.emplace_back(glm::vec2(rectangle.x, rectangle.y) / glm::vec2(m_texture.getSize()), (glm::vec2(rectangle.x, rectangle.y) + glm::vec2(image.getSize())) / glm::vec2(m_texture.getSize())); return m_textures.size() - 1; } else { JOP_DEBUG_ERROR("Texture being packed into atlas has no data"); return 0; } } else { JOP_DEBUG_ERROR("Failed to pack texture into atlas: Atlas full or texture too large"); return 0; } }
bool ImGuiFreeType::BuildFontAtlas(ImFontAtlas* atlas, unsigned int extra_flags) { IM_ASSERT(atlas->ConfigData.Size > 0); IM_ASSERT(atlas->TexGlyphPadding == 1); // Not supported ImFontAtlasBuildRegisterDefaultCustomRects(atlas); atlas->TexID = NULL; atlas->TexWidth = atlas->TexHeight = 0; atlas->TexUvScale = ImVec2(0.0f, 0.0f); atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); atlas->ClearTexData(); ImVector<FreeTypeFont> fonts; fonts.resize(atlas->ConfigData.Size); ImVec2 max_glyph_size(1.0f, 1.0f); // Count glyphs/ranges, initialize font int total_glyphs_count = 0; int total_ranges_count = 0; for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) { ImFontConfig& cfg = atlas->ConfigData[input_i]; FreeTypeFont& font_face = fonts[input_i]; IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas)); if (!font_face.Init(cfg, extra_flags)) return false; max_glyph_size.x = ImMax(max_glyph_size.x, font_face.Info.MaxAdvanceWidth); max_glyph_size.y = ImMax(max_glyph_size.y, font_face.Info.Ascender - font_face.Info.Descender); if (!cfg.GlyphRanges) cfg.GlyphRanges = atlas->GetGlyphRangesDefault(); for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[ 1 ]; in_range += 2, total_ranges_count++) total_glyphs_count += (in_range[1] - in_range[0]) + 1; } // We need a width for the skyline algorithm. Using a dumb heuristic here to decide of width. User can override TexDesiredWidth and TexGlyphPadding if they wish. // Width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. atlas->TexWidth = (atlas->TexDesiredWidth > 0) ? atlas->TexDesiredWidth : (total_glyphs_count > 4000) ? 4096 : (total_glyphs_count > 2000) ? 2048 : (total_glyphs_count > 1000) ? 1024 : 512; // We don't do the original first pass to determine texture height, but just rough estimate. // Looks ugly inaccurate and excessive, but AFAIK with FreeType we actually need to render glyphs to get exact sizes. // Alternatively, we could just render all glyphs into a big shadow buffer, get their sizes, do the rectangle packing and just copy back from the // shadow buffer to the texture buffer. Will give us an accurate texture height, but eat a lot of temp memory. Probably no one will notice.) const int total_rects = total_glyphs_count + atlas->CustomRects.size(); float min_rects_per_row = ceilf((atlas->TexWidth / (max_glyph_size.x + 1.0f))); float min_rects_per_column = ceilf(total_rects / min_rects_per_row); atlas->TexHeight = (int)(min_rects_per_column * (max_glyph_size.y + 1.0f)); // Create texture atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight); memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); // Start packing ImVector<stbrp_node> pack_nodes; pack_nodes.resize(total_rects); stbrp_context context; stbrp_init_target(&context, atlas->TexWidth, atlas->TexHeight, pack_nodes.Data, total_rects); // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). ImFontAtlasBuildPackCustomRects(atlas, &context); // Render characters, setup ImFont and glyphs for runtime for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) { ImFontConfig& cfg = atlas->ConfigData[input_i]; FreeTypeFont& font_face = fonts[input_i]; ImFont* dst_font = cfg.DstFont; if (cfg.MergeMode) dst_font->BuildLookupTable(); const float ascent = font_face.Info.Ascender; const float descent = font_face.Info.Descender; ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); const float font_off_x = cfg.GlyphOffset.x; const float font_off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f); bool multiply_enabled = (cfg.RasterizerMultiply != 1.0f); unsigned char multiply_table[256]; if (multiply_enabled) ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply); for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2) { for (uint32_t codepoint = in_range[0]; codepoint <= in_range[1]; ++codepoint) { if (cfg.MergeMode && dst_font->FindGlyphNoFallback((unsigned short)codepoint)) continue; FT_Glyph ft_glyph = NULL; FT_BitmapGlyph ft_glyph_bitmap = NULL; // NB: will point to bitmap within FT_Glyph GlyphInfo glyph_info; if (!font_face.CalcGlyphInfo(codepoint, glyph_info, ft_glyph, ft_glyph_bitmap)) continue; // Pack rectangle stbrp_rect rect; rect.w = (uint16_t)glyph_info.Width + 1; // Account for texture filtering rect.h = (uint16_t)glyph_info.Height + 1; stbrp_pack_rects(&context, &rect, 1); // Copy rasterized pixels to main texture uint8_t* blit_dst = atlas->TexPixelsAlpha8 + rect.y * atlas->TexWidth + rect.x; font_face.BlitGlyph(ft_glyph_bitmap, blit_dst, atlas->TexWidth, multiply_enabled ? multiply_table : NULL); FT_Done_Glyph(ft_glyph); float char_advance_x_org = glyph_info.AdvanceX; float char_advance_x_mod = ImClamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX); float char_off_x = font_off_x; if (char_advance_x_org != char_advance_x_mod) char_off_x += cfg.PixelSnapH ? (float)(int)((char_advance_x_mod - char_advance_x_org) * 0.5f) : (char_advance_x_mod - char_advance_x_org) * 0.5f; // Register glyph dst_font->AddGlyph((ImWchar)codepoint, glyph_info.OffsetX + char_off_x, glyph_info.OffsetY + font_off_y, glyph_info.OffsetX + char_off_x + glyph_info.Width, glyph_info.OffsetY + font_off_y + glyph_info.Height, rect.x / (float)atlas->TexWidth, rect.y / (float)atlas->TexHeight, (rect.x + glyph_info.Width) / (float)atlas->TexWidth, (rect.y + glyph_info.Height) / (float)atlas->TexHeight, char_advance_x_mod); } } } // Cleanup for (int n = 0; n < fonts.Size; n++) fonts[n].Shutdown(); ImFontAtlasBuildFinish(atlas); return true; }
atlas_t* image_atlas_make(uint32 image_count, const image_t** images) { stbrp_rect* rects = NULL; stbrp_node* nodes = NULL; stbrp_context ctx; uint32 r; uint32 best_size; image_t* tex = NULL; atlas_t* atlas = NULL; rect_t* drects = NULL; /* try to find the best texture size */ best_size = find_best_size(image_count, images); /* create the texture and fill in the pixels */ tex = image_allocate(best_size, best_size, PF_R8G8B8A8); assert( NULL != tex ); /* image to rect */ rects = (stbrp_rect*)malloc(sizeof(stbrp_rect) * image_count); assert( NULL != rects ); for( r = 0; r < image_count; ++r ) { rects[r] = image_to_rect(r, images[r]); } /* nodes */ nodes = (stbrp_node*)malloc(sizeof(stbrp_node) * best_size * 2); assert( NULL != nodes ); memset(nodes, 0, sizeof(stbrp_node) * best_size * 2); /* desitnation rectangles */ drects = (rect_t*)malloc(sizeof(rect_t) * image_count); assert( NULL != drects ); memset(drects, 0, sizeof(rect_t) * image_count); /* pack */ stbrp_init_target(&ctx, (int)best_size, (int)best_size, nodes, (int)best_size * 2); stbrp_pack_rects(&ctx, rects, (int)image_count); free(nodes); /* copy the rectangle */ for( r = 0; r < image_count; ++r ) { uint32 y; uint32 h = images[r]->height; uint32 w = images[r]->width; assert( rects[r].was_packed ); drects[r].x = rects[r].x; drects[r].y = rects[r].y; drects[r].width = rects[r].w; drects[r].height= rects[r].h; for( y = 0; y < h ; ++y ) { uint32 x; for( x = 0; x < w; ++x ) { color4b_t src = image_get_pixelb(images[r], x, y); image_set_pixelb(tex, rects[r].x + x, rects[r].y + y, src); } } } /* release resources */ free(rects); /* final result */ atlas = (atlas_t*)malloc(sizeof(atlas_t)); assert( NULL != atlas ); atlas->baked_image = tex; atlas->coordinates = drects; atlas->image_count = image_count; return atlas; }