Java位图字体:使用不同颜色的1位图像

Gre*_*Cat 6 java fonts awt bitmap lwjgl

我想在基于Java AWT的应用程序中实现一个简单的位图字体绘图.应用程序绘制一个Graphics对象,我想在其中实现一个简单的算法:

1)加载一个文件(可能正在使用ImageIO.read(new File(fileName))),它是1位PNG,看起来像这样:

8*8位图字体

即它是16*16(或16*多,如果我想支持Unicode)矩阵8*8字符.黑色对应于背景颜色,白色对应于前景.

2)逐个字符地绘制字符串,将该位图的相关部分blit到目标Graphics.到目前为止,我只是成功了:

    int posX = ch % 16;
    int posY = ch / 16;

    int fontX = posX * CHAR_WIDTH;
    int fontY = posY * CHAR_HEIGHT;

    g.drawImage(
            font,
            dx, dy, dx + CHAR_WIDTH, dy + CHAR_HEIGHT,
            fontX, fontY, fontX + CHAR_WIDTH, fontY + CHAR_HEIGHT,
            null
    );
Run Code Online (Sandbox Code Playgroud)

它可以工作,但是,唉,它按原样使文本快照,即我无法用所需的前景和背景颜色替换黑白,我甚至无法使背景透明.

所以,问题是:在Java中是否存在一种简单(快速!)的方式将一个1位位图的一部分与另一个位图相关联,在blitting过程中将其着色(即用一个给定颜色和所有1个像素替换所有0个像素)和另外一个)?

我研究了几个解决方案,所有这些解决方案对我来说都不是最理想的:

  • 使用自定义着色BufferedImageOp,如此解决方案中所述 - 它应该可以工作,但似乎在每个blit操作之前重新着色位图是非常低效的.
  • 使用多个32位RGBA PNG,Alpha通道设置为0表示黑色像素,最大值设置为前景.每个所需的前景色都应该有自己的预渲染位图.这样我可以使背景透明并在blitting之前单独绘制为矩形,然后用我的字体选择一个位图,使用所需颜色预先着色并在该矩形上绘制一部分.对我来说似乎是一个巨大的矫枉过正 - 以及使这个选项变得更糟的原因 - 它将前景色的数量限制到相对较小的数量(即我可以逼真地加载和保持数百或数千个位图,而不是数百万)
  • 捆绑和加载自定义字体,如此解决方案中所述,但就我在Font#createFont文档中看到的情况而言,AWT Font似乎仅适用于基于矢量的字体,而不适用于基于位图的字体.

可能已经有任何库实现了这样的功能吗?或者是时候让我切换到某种更高级的图形库,比如lwjgl

基准测试结果

我在一个简单的测试中测试了几个算法:我有2个字符串,每个字符71个字符,并且一个接一个地连续绘制它们,就在同一个地方:

    for (int i = 0; i < N; i++) {
        cv.putString(5, 5, STR, Color.RED, Color.BLUE);
        cv.putString(5, 5, STR2, Color.RED, Color.BLUE);
    }
Run Code Online (Sandbox Code Playgroud)

然后我测量所花费的时间并计算速度:每秒字符串和每秒字符数.到目前为止,我测试的各种实现产生了以下结果:

  • 位图字体,16*16个字符位图:10991个字符串/秒,780391个字符/秒
  • 位图字体,预分割图像:11048字符串/秒,784443字符/秒
  • g.drawString():8952个字符串/秒,635631个字符/秒
  • 彩色位图字体,使用LookupOp和ByteLookupTable着色:404字符串/秒,28741字符/秒

Gre*_*Cat 1

好吧,看来我已经找到了最好的解决方案。成功的关键是访问底层 AWT 结构中的原始像素数组。初始化是这样的:

public class ConsoleCanvas extends Canvas {
    protected BufferedImage buffer;
    protected int w;
    protected int h;
    protected int[] data;

    public ConsoleCanvas(int w, int h) {
        super();
        this.w = w;
        this.h = h;
    }

    public void initialize() {
        data = new int[h * w];

        // Fill data array with pure solid black
        Arrays.fill(data, 0xff000000);

        // Java's endless black magic to get it working
        DataBufferInt db = new DataBufferInt(data, h * w);
        ColorModel cm = ColorModel.getRGBdefault();
        SampleModel sm = cm.createCompatibleSampleModel(w, h);
        WritableRaster wr = Raster.createWritableRaster(sm, db, null);
        buffer = new BufferedImage(cm, wr, false, null);
    }

    @Override
    public void paint(Graphics g) {
        update(g);
    }

    @Override
    public void update(Graphics g) {
        g.drawImage(buffer, 0, 0, null);
    }
}
Run Code Online (Sandbox Code Playgroud)

在此之后,您将获得buffer可以在画布更新上进行 blit 的 a 和 ARGB 4 字节整数的底层数组 - data

单个字符可以这样绘制:

private void putChar(int dx, int dy, char ch, int fore, int back) {
    int charIdx = 0;
    int canvasIdx = dy * canvas.w + dx;
    for (int i = 0; i < CHAR_HEIGHT; i++) {
        for (int j = 0; j < CHAR_WIDTH; j++) {
            canvas.data[canvasIdx] = font[ch][charIdx] ? fore : back;
            charIdx++;
            canvasIdx++;
        }
        canvasIdx += canvas.w - CHAR_WIDTH;
    }
}
Run Code Online (Sandbox Code Playgroud)

这个使用一个简单的boolean[][]数组,其中第一个索引选择字符,第二个索引迭代原始 1 位字符像素数据(true => 前景,false => 背景)。

我将尝试尽快发布一个完整的解决方案作为我的 Java 终端仿真类集的一部分。

该解决方案的基准测试速度为令人印象深刻的 26007 个字符串/秒或 1846553 个字符/秒 - 比以前最好的非彩色快 2.3 倍drawImage()

  • *“..1846553 字符/秒 - 速度快了 2.3 倍..”* 有红色条纹吗?;) 祝贺您成功。当您能够组织起来时,希望看到完整的解决方案。 (2认同)