我想在基于 Java AWT 的应用程序中实现一个简单的位图字体绘制。应用程序利用 Graphics
对象,我想在其中实现一个简单的算法:
1)加载文件(可能使用 ImageIO.read(new File(fileName))
),它是 1 位 PNG,看起来像这样:
即它是 16*16 (或16*很多(如果我想支持Unicode)8*8字符的矩阵。黑色对应于背景色,白色对应于前景。
2) 逐个字符地绘制字符串,将该位图的相关部分传输到目标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
);
它有效,但是,唉,它按原样传输文本,即我无法用所需的前景色和背景色替换黑色和白色,而且我什至无法制作背景透明的。
所以,问题是:Java 中是否有一种简单(且快速!)的方法将一个 1 位位图的一部分位图传输到另一个位图,并在位图传输过程中对其进行着色(即用一种给定颜色替换所有 0 像素,用一种给定颜色替换所有 1 像素)与另一个)?
我研究了几个解决方案,所有这些对我来说看起来都不理想:
可能已经有任何库实现了此类功能?或者我是时候切换到某种更高级的图形库了,比如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);
}
然后我测量所用时间并计算速度:每秒字符串和每秒字符数。到目前为止,我测试过的各种实现产生以下结果:
- 位图字体,16*16 字符位图:10991 字符串/秒,780391 字符/秒
- 位图字体,预分割图像:11048 字符串/秒,784443 字符/
- 秒.drawString():8952 个字符串/秒,635631 个字符/秒
- 彩色位图字体,使用 LookupOp 和 ByteLookupTable 着色:404 个字符串/秒,28741 个字符/秒
I'd like to implement a simple bitmap font drawing in Java AWT-based application. Application draws on a Graphics
object, where I'd like to implement a simple algorithm:
1) Load a file (probably using ImageIO.read(new File(fileName))
), which is 1-bit PNG that looks something like that:
I.e. it's 16*16 (or 16*many, if I'd like to support Unicode) matrix of 8*8 characters. Black corresponds to background color, white corresponds to foreground.
2) Draw strings character-by-character, blitting relevant parts of this bitmap to target Graphics
. So far I've only succeeded with something like that:
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
);
It works, but, alas, it blits the text as is, i.e. I can't substitute black and white with desired foreground and background colors, and I can't even make background transparent.
So, the question is: is there a simple (and fast!) way in Java to blit part of one 1-bit bitmap to another, colorizing it in process of blitting (i.e. replacing all 0 pixels with one given color and all 1 pixels with another)?
I've researched into a couple of solutions, all of them look suboptimal to me:
- Using a custom colorizing BufferedImageOp, as outlined in this solution - it should work, but it seems that it would be very inefficient to recolorize a bitmap before every blit operation.
- Using multiple 32-bit RGBA PNG, with alpha channel set to 0 for black pixels and to maximum for foreground. Every desired foreground color should get its own pre-rendered bitmap. This way I can make background transparent and draw it as a rectangle separately before blitting and then select one bitmap with my font, pre-colorized with desired color and draw a portion of it over that rectangle. Seems like a huge overkill to me - and what makes this option even worse - it limits number of foreground colors to a relatively small amount (i.e. I can realistically load up and hold like hundreds or thousands of bitmaps, not millions)
- Bundling and loading a custom font, as outlined in this solution could work, but as far as I see in Font#createFont documentation, AWT's
Font
seems to work only with vector-based fonts, not with bitmap-based.
May be there's already any libraries that implement such functionality? Or it's time for me to switch to some sort of more advanced graphics library, something like lwjgl?
Benchmarking results
I've tested a couple of algorithms in a simple test: I have 2 strings, 71 characters each, and draw them continuously one after another, right on the same place:
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);
}
Then I measure time taken and calculate speed: string per second and characters per second. So far, various implementation I've tested yield the following results:
- bitmap font, 16*16 characters bitmap: 10991 strings / sec, 780391 chars / sec
- bitmap font, pre-split images: 11048 strings / sec, 784443 chars / sec
- g.drawString(): 8952 strings / sec, 635631 chars / sec
- colored bitmap font, colorized using LookupOp and ByteLookupTable: 404 strings / sec, 28741 chars / sec
发布评论
评论(2)
您可以将每个位图转换为一个
Shape
(或多个)并绘制Shape
。请参阅平滑锯齿状路径了解获得形状
的过程。EG
我必须弄清楚左上角的 FPS 计数存在十倍误差不过蓝色图像的。 50 FPS 我可以相信,但 500 FPS 似乎......错误。
You might turn each bitmap into a
Shape
(or many of them) and draw theShape
. See Smoothing a jagged path for the process of gaining theShape
.E.G.
I have to figure there is a factor of ten error in the FPS count on the top left of the blue image though. 50 FPS I could believe, but 500 FPS seems ..wrong.
好吧,看来我已经找到了最好的解决方案。成功的关键是访问底层 AWT 结构中的原始像素数组。初始化过程是这样的:
在此之后,您将获得一个可以在画布更新上进行 blit 的
buffer
和 ARGB 4 字节整数的底层数组 -data
。单个字符可以这样绘制:
这个使用一个简单的
boolean[][]
数组,其中第一个索引选择字符,第二个索引迭代原始 1 位字符像素数据(true => 前景,假=>背景)。我将尝试尽快发布一个完整的解决方案作为我的 Java 终端仿真类集的一部分。
该解决方案的基准测试结果为令人印象深刻的 26007 个字符串/秒或 1846553 个字符/秒 - 比以前最好的非彩色
drawImage()
快 2.3 倍。Okay, looks like I've found the best solution. The key to success was accessing raw pixel arrays in underlying AWT structures. Initialization goes something like that:
After this one, you've got both a
buffer
that you can blit on canvas updates and underlying array of ARGB 4-byte ints -data
.Single character can be drawn like that:
This one uses a simple
boolean[][]
array, where first index chooses character and second index iterates over raw 1-bit character pixel data (true => foreground, false => background).I'll try to publish a complete solution as a part of my Java terminal emulation class set soon.
This solution benchmarks for impressive 26007 strings / sec or 1846553 chars / sec - that's 2.3x times faster than previous best non-colorized
drawImage()
.