Add bitmap font loading functionality and implement Map2D for 2D key-value mapping
This commit is contained in:
@@ -4,6 +4,7 @@ import be.seeseemelk.diceos.system.gfx.GraphicsContext;
|
|||||||
import com.badlogic.gdx.ApplicationAdapter;
|
import com.badlogic.gdx.ApplicationAdapter;
|
||||||
import com.badlogic.gdx.graphics.Color;
|
import com.badlogic.gdx.graphics.Color;
|
||||||
import com.badlogic.gdx.graphics.Texture;
|
import com.badlogic.gdx.graphics.Texture;
|
||||||
|
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
||||||
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||||
import com.badlogic.gdx.utils.ScreenUtils;
|
import com.badlogic.gdx.utils.ScreenUtils;
|
||||||
import com.badlogic.gdx.utils.viewport.ScreenViewport;
|
import com.badlogic.gdx.utils.viewport.ScreenViewport;
|
||||||
@@ -32,6 +33,8 @@ public class DiceOS extends ApplicationAdapter {
|
|||||||
private int offsetY = 0;
|
private int offsetY = 0;
|
||||||
private GraphicsContext gc;
|
private GraphicsContext gc;
|
||||||
|
|
||||||
|
private BitmapFont font;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void create() {
|
public void create() {
|
||||||
log.info("DiceOS starting...");
|
log.info("DiceOS starting...");
|
||||||
@@ -47,6 +50,8 @@ public class DiceOS extends ApplicationAdapter {
|
|||||||
task.onStartup();
|
task.onStartup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
font = resourceLoader.loadFont("system/font");
|
||||||
|
|
||||||
log.info("DiceOS started!");
|
log.info("DiceOS started!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +97,9 @@ public class DiceOS extends ApplicationAdapter {
|
|||||||
param.flipX = false;
|
param.flipX = false;
|
||||||
display.draw(border, 0, display.getHeight() - 8, param);
|
display.draw(border, 0, display.getHeight() - 8, param);
|
||||||
|
|
||||||
|
font.setColor(1, 1, 1, 0.5f);
|
||||||
|
font.draw(display.getBatch(), "DiceOS", 50, display.getHeight() - 40);
|
||||||
|
|
||||||
// Finish rendering to screen buffer
|
// Finish rendering to screen buffer
|
||||||
display.getBatch().end();
|
display.getBatch().end();
|
||||||
display.getScreenBuffer().end();
|
display.getScreenBuffer().end();
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
package be.seeseemelk.diceos.system;
|
package be.seeseemelk.diceos.system;
|
||||||
|
|
||||||
|
import be.seeseemelk.diceos.system.utils.Map2D;
|
||||||
import com.badlogic.gdx.Gdx;
|
import com.badlogic.gdx.Gdx;
|
||||||
import com.badlogic.gdx.graphics.Pixmap;
|
import com.badlogic.gdx.graphics.Pixmap;
|
||||||
import com.badlogic.gdx.graphics.Texture;
|
import com.badlogic.gdx.graphics.Texture;
|
||||||
|
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
||||||
|
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||||
import io.avaje.inject.Component;
|
import io.avaje.inject.Component;
|
||||||
|
import lombok.Cleanup;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Component
|
@Component
|
||||||
public class ResourceLoader {
|
public class ResourceLoader {
|
||||||
private final Map<String, Pixmap> pixmaps = new HashMap<>();
|
private final Map<String, Pixmap> pixmaps = new HashMap<>();
|
||||||
private final Map<String, Texture> textures = new HashMap<>();
|
private final Map<String, Texture> textures = new HashMap<>();
|
||||||
|
private final Map<String, BitmapFont> fonts = new HashMap<>();
|
||||||
|
|
||||||
public Pixmap loadPixmap(String path) {
|
public Pixmap loadPixmap(String path) {
|
||||||
return pixmaps.computeIfAbsent(path, p -> new Pixmap(Gdx.files.internal(p)));
|
return pixmaps.computeIfAbsent(path, p -> new Pixmap(Gdx.files.internal(p)));
|
||||||
@@ -22,4 +29,194 @@ public class ResourceLoader {
|
|||||||
public Texture loadTexture(String path) {
|
public Texture loadTexture(String path) {
|
||||||
return textures.computeIfAbsent(path, p -> new Texture(Gdx.files.internal(p)));
|
return textures.computeIfAbsent(path, p -> new Texture(Gdx.files.internal(p)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BitmapFont loadFont(String path) {
|
||||||
|
return fonts.computeIfAbsent(path, this::loadBitmapfont);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BitmapFont loadBitmapfont(String path) {
|
||||||
|
log.info("Loading bitmap font from {}", path);
|
||||||
|
|
||||||
|
@Cleanup("dispose") var pixmap = loadPixmap(path + ".png");
|
||||||
|
var texture = new Texture(pixmap);
|
||||||
|
var region = new TextureRegion(texture);
|
||||||
|
|
||||||
|
var data = new BitmapFont.BitmapFontData();
|
||||||
|
|
||||||
|
var glyphMap = new Map2D<Integer, Integer, BitmapFont.Glyph>();
|
||||||
|
var glyphLines = Gdx.files.internal(path + ".txt").readString().split("\n");
|
||||||
|
for (var y = 0; y < glyphLines.length; y++) {
|
||||||
|
var line = glyphLines[y].toCharArray();
|
||||||
|
for (var x = 0; x < line.length; x++) {
|
||||||
|
var glyph = new BitmapFont.Glyph();
|
||||||
|
glyph.id = line[x];
|
||||||
|
glyphMap.put(x, y, glyph);
|
||||||
|
data.setGlyph(glyph.id, glyph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the vertical timing lines to find the horizontal ones and read glyph heights
|
||||||
|
VerticalTiming[] glyphHeights = readGlyphHeights(pixmap, glyphLines.length);
|
||||||
|
// Read the horizontal timing lines to read glyph widths
|
||||||
|
HorizontalTiming[][] glyphWidths = new HorizontalTiming[glyphLines.length][];
|
||||||
|
for (var y = 0; y < glyphHeights.length; y++) {
|
||||||
|
glyphWidths[y] = readHorizontalTimings(pixmap, glyphLines[y].length(), glyphHeights[y].timingLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign glyph sizes
|
||||||
|
for (var y = 0; y < glyphLines.length; y++) {
|
||||||
|
for (var x = 0; x < glyphLines[y].length(); x++) {
|
||||||
|
var glyph = glyphMap.get(x, y);
|
||||||
|
var widthTiming = glyphWidths[y][x];
|
||||||
|
var heightTiming = glyphHeights[y];
|
||||||
|
glyph.width = widthTiming.getGlyphWidth();
|
||||||
|
glyph.height = heightTiming.getGlyphHeight();
|
||||||
|
glyph.srcX = widthTiming.glyphStart;
|
||||||
|
glyph.srcY = heightTiming.glyphStart;
|
||||||
|
glyph.xoffset = 0;
|
||||||
|
glyph.yoffset = 0;
|
||||||
|
glyph.xadvance = glyph.width;
|
||||||
|
glyph.u = (float)widthTiming.glyphStart / pixmap.getWidth();
|
||||||
|
glyph.u2 = (float)widthTiming.glyphEnd / pixmap.getWidth();
|
||||||
|
glyph.v = (float)heightTiming.glyphStart / pixmap.getHeight();
|
||||||
|
glyph.v2 = (float)heightTiming.glyphEnd / pixmap.getHeight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BitmapFont(data, region, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the vertical timing line from the pixmap.
|
||||||
|
* <p>
|
||||||
|
* The timing line is a 1 pixel wide line on the left side of the pixmap that indicate the positions of the
|
||||||
|
* horizontal timing lines, and the heights of the glyphs.
|
||||||
|
* <p>
|
||||||
|
* A white pixel on the timing line indicates the line is unused, a black indicates either a timing line, or the
|
||||||
|
* height of a glyph.
|
||||||
|
* <p>
|
||||||
|
* It is expected that the line ends with a white pixel.
|
||||||
|
*
|
||||||
|
* @param pixmap The pixmap containing the timing line.
|
||||||
|
* @param glyphCountY The number of glyphs in the Y direction.
|
||||||
|
* @return An array of glyph heights.
|
||||||
|
*/
|
||||||
|
private VerticalTiming[] readGlyphHeights(Pixmap pixmap, int glyphCountY) {
|
||||||
|
log.info("Reading vertical timings for {} glyphs", glyphCountY);
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
SEARCHING_FOR_LINE,
|
||||||
|
WAITING_FOR_GLYPH_HEIGHTS,
|
||||||
|
READING_GLYPH_HEIGHTS
|
||||||
|
}
|
||||||
|
State state = State.SEARCHING_FOR_LINE;
|
||||||
|
|
||||||
|
VerticalTiming[] glyphHeights = new VerticalTiming[glyphCountY];
|
||||||
|
VerticalTiming currentTiming = null;
|
||||||
|
int currentGlyphLine = 0;
|
||||||
|
int y = 0;
|
||||||
|
while (y < pixmap.getHeight()) {
|
||||||
|
boolean isBlack = pixmap.getPixel(0, y) == 0x000000ff;
|
||||||
|
switch (state) {
|
||||||
|
case SEARCHING_FOR_LINE -> {
|
||||||
|
if (isBlack) {
|
||||||
|
currentTiming = new VerticalTiming();
|
||||||
|
glyphHeights[currentGlyphLine++] = currentTiming;
|
||||||
|
currentTiming.timingLine = y;
|
||||||
|
state = State.WAITING_FOR_GLYPH_HEIGHTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case WAITING_FOR_GLYPH_HEIGHTS -> {
|
||||||
|
if (isBlack) {
|
||||||
|
currentTiming.glyphStart = y;
|
||||||
|
state = State.READING_GLYPH_HEIGHTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case READING_GLYPH_HEIGHTS -> {
|
||||||
|
if (!isBlack) {
|
||||||
|
currentTiming.glyphEnd = y;
|
||||||
|
state = State.SEARCHING_FOR_LINE;
|
||||||
|
log.info("Found glyph height: {} (from {} to {})", currentTiming.getGlyphHeight(), currentTiming.glyphStart, currentTiming.glyphEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return glyphHeights;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class VerticalTiming {
|
||||||
|
/// The Y position of the horizontal timing line.
|
||||||
|
int timingLine;
|
||||||
|
/// The start Y position of the glyph line.
|
||||||
|
int glyphStart;
|
||||||
|
/// The end Y position of the glyph line.
|
||||||
|
int glyphEnd;
|
||||||
|
|
||||||
|
public int getGlyphHeight() {
|
||||||
|
return glyphEnd - glyphStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the horizontal timing lines from the pixmap.
|
||||||
|
* <p>
|
||||||
|
* The timing lines are 1 pixel high lines and can occur at any Y position in the pixmap.
|
||||||
|
* The Y position is indicated through the vertical timing.
|
||||||
|
* <p>
|
||||||
|
* A white pixel on the timing line indicates the line is unused, a line of black pixels indicates the width of a glyph.
|
||||||
|
* @param pixmap The pixmap containing the timing lines.
|
||||||
|
* @param glyphCountX The number of glyphs in the X direction.
|
||||||
|
* @param timingLineY The Y position of the timing line to read.
|
||||||
|
* @return An array of glyph widths.
|
||||||
|
*/
|
||||||
|
private HorizontalTiming[] readHorizontalTimings(Pixmap pixmap, int glyphCountX, int timingLineY) {
|
||||||
|
log.info("Reading horizontal timings for {} glyphs at y={}", glyphCountX, timingLineY);
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
WAITING_FOR_GLYPH_WIDTHS,
|
||||||
|
READING_GLYPH_WIDTHS
|
||||||
|
}
|
||||||
|
State state = State.WAITING_FOR_GLYPH_WIDTHS;
|
||||||
|
|
||||||
|
HorizontalTiming[] glyphWidths = new HorizontalTiming[glyphCountX];
|
||||||
|
HorizontalTiming currentTiming = null;
|
||||||
|
int currentGlyphIndex = 0;
|
||||||
|
int x = 1;
|
||||||
|
while (x < pixmap.getWidth()) {
|
||||||
|
boolean isBlack = pixmap.getPixel(x, timingLineY) == 0x000000ff;
|
||||||
|
switch (state) {
|
||||||
|
case WAITING_FOR_GLYPH_WIDTHS -> {
|
||||||
|
if (isBlack) {
|
||||||
|
currentTiming = new HorizontalTiming();
|
||||||
|
glyphWidths[currentGlyphIndex++] = currentTiming;
|
||||||
|
currentTiming.glyphStart = x;
|
||||||
|
state = State.READING_GLYPH_WIDTHS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case READING_GLYPH_WIDTHS -> {
|
||||||
|
if (!isBlack) {
|
||||||
|
currentTiming.glyphEnd = x;
|
||||||
|
state = State.WAITING_FOR_GLYPH_WIDTHS;
|
||||||
|
log.info("Found glyph width: {} (from {} to {})", currentTiming.getGlyphWidth(), currentTiming.glyphStart, currentTiming.glyphEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return glyphWidths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class HorizontalTiming {
|
||||||
|
/// The X position of the vertical timing line.
|
||||||
|
int glyphStart;
|
||||||
|
/// The end X position of the vertical timing line.
|
||||||
|
int glyphEnd;
|
||||||
|
|
||||||
|
public int getGlyphWidth() {
|
||||||
|
return glyphEnd - glyphStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
44
src/main/java/be/seeseemelk/diceos/system/utils/Map2D.java
Normal file
44
src/main/java/be/seeseemelk/diceos/system/utils/Map2D.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package be.seeseemelk.diceos.system.utils;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class Map2D<K1, K2, V> {
|
||||||
|
private final Map<Position, V> map = new HashMap<>();
|
||||||
|
|
||||||
|
@EqualsAndHashCode
|
||||||
|
private class Position{
|
||||||
|
K1 k1;
|
||||||
|
K2 k2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(K1 key1, K2 key2, V value) {
|
||||||
|
Position position = new Position();
|
||||||
|
position.k1 = key1;
|
||||||
|
position.k2 = key2;
|
||||||
|
map.put(position, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public V get(K1 key1, K2 key2) {
|
||||||
|
Position position = new Position();
|
||||||
|
position.k1 = key1;
|
||||||
|
position.k2 = key2;
|
||||||
|
return map.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsKey(K1 key1, K2 key2) {
|
||||||
|
Position position = new Position();
|
||||||
|
position.k1 = key1;
|
||||||
|
position.k2 = key2;
|
||||||
|
return map.containsKey(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(K1 key1, K2 key2) {
|
||||||
|
Position position = new Position();
|
||||||
|
position.k1 = key1;
|
||||||
|
position.k2 = key2;
|
||||||
|
map.remove(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 240 B After Width: | Height: | Size: 274 B |
BIN
src/main/resources/system/dice2.png
Normal file
BIN
src/main/resources/system/dice2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 231 B |
4
src/main/resources/system/font.fnt
Normal file
4
src/main/resources/system/font.fnt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
info face="Dice Font" size=8 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=0 aa=0 padding=1,1,1,1 spacing=1,1
|
||||||
|
common lineHeight=8 base=7 scaleW=160 scaleH=28 pages=1 packed=0 alphaChnl=0
|
||||||
|
page id=0 file="font.png"
|
||||||
|
char id=65 x=5 y=0 width=60 height=7 xoffset=5 yoffset=5 xadvance=6 page=0 chnl=15
|
||||||
BIN
src/main/resources/system/font.png
Normal file
BIN
src/main/resources/system/font.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 875 B |
2
src/main/resources/system/font.txt
Normal file
2
src/main/resources/system/font.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
aAbBcCdDeEfFgGhHiIjJkKlLmMnN
|
||||||
|
oOpPqQrRsStTuUvVwWxXyYzZ
|
||||||
Reference in New Issue
Block a user