From 10609e0207123ad8fb20cf578672b95f8c7ad1d6 Mon Sep 17 00:00:00 2001 From: yum Date: Sat, 30 Nov 2024 16:45:30 -0800 Subject: Fix gen_atlas Now it generates things in a fixed-sized grid which is derived from charset's max codepoint. It does not interleave gaps, instead putting a big gap at the end/bottom of the atlas. --- Third_Party/gen_atlas | 169 +++++++++++++++++++++++++------------------------- 1 file changed, 85 insertions(+), 84 deletions(-) diff --git a/Third_Party/gen_atlas b/Third_Party/gen_atlas index 7cc6c41..dfb0e61 100644 --- a/Third_Party/gen_atlas +++ b/Third_Party/gen_atlas @@ -6,130 +6,131 @@ import os import json from PIL import Image -def calculate_cell_dimensions(total_size, num_cells): - """ - Distribute pixels evenly across cells, handling remainder. - Returns a list of cell sizes that add up to total_size. - """ - base_size = total_size // num_cells - remainder = total_size % num_cells - - # Create array of cell sizes - cell_sizes = [] - for i in range(num_cells): - # Add an extra pixel to cells until remainder is used up - # This distributes remainder pixels evenly across cells - extra = 1 if i < remainder else 0 - cell_sizes.append(base_size + extra) - - return cell_sizes +# Define the character ranges we want to include +CHAR_RANGES = [ + (32, 126), # Printable ASCII + # Add more ranges here as needed, e.g.: + # (160, 255), # Extended Latin +] -def generate_atlas(font_path, resolution): - # Calculate cell dimensions for 12x12 grid - cell_widths = calculate_cell_dimensions(resolution, 12) - cell_heights = calculate_cell_dimensions(resolution, 12) - - # Use the most common cell size - cell_width = max(cell_widths) - cell_height = max(cell_heights) +def calculate_grid_size(char_ranges): + """Calculate the smallest square grid that fits the highest character code""" + max_char = max(end for _, end in char_ranges) + grid_size = 1 + while grid_size * grid_size < max_char: + grid_size += 1 + return grid_size - # Construct the command with explicit glyph range for printable ASCII +def generate_atlas(font_path, resolution, draw_grid=False): + # Calculate grid size based on character ranges + grid_size = calculate_grid_size(CHAR_RANGES) + cell_size = resolution // grid_size + + # Convert character ranges to command-line format + chars_str = ", ".join(f"[{start}, {end}]" for start, end in CHAR_RANGES) + cmd = [ "msdf-atlas-gen/build/bin/Debug/msdf-atlas-gen.exe", "-font", font_path, - "-type", "msdf", + "-type", "mtsdf", "-format", "png", "-imageout", "atlas.png", "-json", "atlas.json", "-size", "32", "-pxrange", "2", "-dimensions", str(resolution), str(resolution), - "-chars", "[32, 126]", + "-chars", chars_str, "-uniformgrid", - "-uniformcols", "12", - "-uniformcell", str(cell_width), str(cell_height), - "-uniformorigin", "on" + "-uniformcols", str(grid_size), + "-uniformcell", str(cell_size), str(cell_size), + "-uniformorigin", "on", + "-coloringstrategy", "inktrap", + "-errorcorrection", "auto", + "-miterlimit", "1.0", + "-scanline" ] try: + print("Running msdf-atlas-gen...") result = subprocess.run(cmd, check=True, capture_output=True, text=True) + # Print the output + if result.stdout: + print("msdf-atlas-gen output:") + print(result.stdout) + # Rearrange the atlas to include gaps for non-printable characters - rearrange_atlas(resolution, cell_width, cell_height) + rearrange_atlas(resolution, cell_size, cell_size, draw_grid) return True except subprocess.CalledProcessError as e: print(f"Error generating atlas: {e}") print(f"Error output: {e.stderr}") return False -def rearrange_atlas(resolution, cell_width, cell_height): - """Rearrange the atlas to include gaps for non-printable characters""" - # Load the original atlas and JSON data - original = Image.open("atlas.png") - with open("atlas.json", 'r') as f: - data = json.load(f) - - # Create the new atlas image - new_atlas = Image.new('RGB', (resolution, resolution), (0, 0, 0)) - - # Calculate exact cell dimensions - exact_cell_width = resolution / 12 - exact_cell_height = resolution / 12 - - for glyph in data['glyphs']: - if 'unicode' not in glyph or 'atlasBounds' not in glyph: - continue - - char_code = glyph['unicode'] - - # Calculate grid position using floor division to avoid rounding errors - grid_x = (char_code % 12) * resolution // 12 - grid_y = (char_code // 12) * resolution // 12 - - # Extract original bounds - bounds = glyph['atlasBounds'] - orig_x = round(float(bounds['left'])) # Use round instead of int - orig_y = resolution - round(float(bounds['top'])) # Use round instead of int - width = round(float(bounds['right']) - float(bounds['left'])) - height = round(float(bounds['top']) - float(bounds['bottom'])) - - # Extract the glyph from the original atlas - glyph_region = original.crop(( - orig_x, - orig_y, - orig_x + width, - orig_y + height - )) - - # Paste the glyph directly at the grid position - new_atlas.paste(glyph_region, (grid_x, grid_y)) - - # Draw grid lines - from PIL import ImageDraw - draw = ImageDraw.Draw(new_atlas) +def draw_grid_lines(image, resolution): + """Draw red grid lines on the image""" + draw = ImageDraw.Draw(image) + grid_size = calculate_grid_size(CHAR_RANGES) # Draw vertical lines - for x in range(12): - line_x = x * resolution // 12 # Use integer division + for x in range(grid_size): + line_x = x * resolution // grid_size draw.line([(line_x, 0), (line_x, resolution-1)], fill=(255, 0, 0), width=1) # Draw horizontal lines - for y in range(12): - line_y = y * resolution // 12 # Use integer division + for y in range(grid_size): + line_y = y * resolution // grid_size draw.line([(0, line_y), (resolution-1, line_y)], fill=(255, 0, 0), width=1) # Draw the final borders draw.line([(resolution-1, 0), (resolution-1, resolution-1)], fill=(255, 0, 0), width=1) draw.line([(0, resolution-1), (resolution-1, resolution-1)], fill=(255, 0, 0), width=1) + +def rearrange_atlas(resolution, cell_width, cell_height, draw_grid=False): + """Rearrange the atlas to include gaps for non-printable characters""" + original = Image.open("atlas.png") + new_atlas = Image.new('RGBA', (resolution, resolution), (0, 0, 0, 255)) + + grid_size = calculate_grid_size(CHAR_RANGES) + cell_size = resolution // grid_size + + # Track current position in the source atlas + source_index = 0 + + # Process each character range + for start, end in CHAR_RANGES: + for ascii_code in range(start, end + 1): + # Calculate source position (original atlas) + source_x = (source_index % grid_size) * cell_size + source_y = (source_index // grid_size) * cell_size + + # Calculate target position (new atlas) + target_x = ((ascii_code + 1) % grid_size) * cell_size + target_y = ((ascii_code + 1) // grid_size) * cell_size + + # Extract and paste the glyph + glyph = original.crop(( + source_x, + source_y, + source_x + cell_size, + source_y + cell_size + )) + new_atlas.paste(glyph, (target_x, target_y)) + source_index += 1 + + # Draw the grid lines only if requested + if draw_grid: + draw_grid_lines(new_atlas, resolution) - # Save the rearranged atlas - new_atlas.save("atlas_rearranged.png") + # Save the rearranged atlas, overwriting the original + new_atlas.save("atlas.png") print("Atlas rearranged successfully!") def main(): parser = argparse.ArgumentParser(description='Generate a font atlas using msdf-atlas-gen') parser.add_argument('font_path', help='Path to the font file (.ttf/.otf)') parser.add_argument('resolution', type=int, help='Total atlas resolution (width=height)') + parser.add_argument('--grid', action='store_true', help='Draw grid lines on the output atlas') args = parser.parse_args() @@ -138,7 +139,7 @@ def main(): print(f"Error: Font file not found at {args.font_path}") return - generate_atlas(args.font_path, args.resolution) + generate_atlas(args.font_path, args.resolution, args.grid) if __name__ == "__main__": main() -- cgit v1.2.3