[英]Java bitmap font: blitting 1-bit image with different colors
我想在基於Java AWT的應用程序中實現一個簡單的位圖字體繪圖。 應用程序在Graphics
對象上繪制,我想在其中實現一個簡單的算法:
1)加載一個文件(可能使用ImageIO.read(new File(fileName))
),它是1位PNG,看起來像這樣:
即它是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
);
它可以工作,但是,唉,它按原樣使文本快照,即我無法用所需的前景和背景顏色替換黑白,我甚至無法使背景透明。
所以,問題是:在Java中是否存在一種簡單(快速!)的方式將一個1位位圖的一部分與另一個位圖相關聯,在blitting過程中將其着色(即用一個給定顏色和所有1個像素替換所有0個像素)和另外一個)?
我研究了幾個解決方案,所有這些解決方案對我來說都不是最理想的:
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);
}
然后我測量所花費的時間並計算速度:每秒字符串和每秒字符數。 到目前為止,我測試的各種實現產生了以下結果:
您可以將每個位圖轉換為Shape
(或其中許多)並繪制Shape
。 請參閱平滑鋸齒狀路徑以獲取Shape
的過程。
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import java.util.Random;
/* Gain the outline of an image for further processing. */
class ImageShape {
private BufferedImage image;
private BufferedImage ImageShape;
private Area areaOutline = null;
private JLabel labelOutline;
private JLabel output;
private BufferedImage anim;
private Random random = new Random();
private int count = 0;
private long time = System.currentTimeMillis();
private String rate = "";
public ImageShape(BufferedImage image) {
this.image = image;
}
public void drawOutline() {
if (areaOutline!=null) {
Graphics2D g = ImageShape.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0,0,ImageShape.getWidth(),ImageShape.getHeight());
g.setColor(Color.RED);
g.setClip(areaOutline);
g.fillRect(0,0,ImageShape.getWidth(),ImageShape.getHeight());
g.setColor(Color.BLACK);
g.setClip(null);
g.draw(areaOutline);
g.dispose();
}
}
public Area getOutline(Color target, BufferedImage bi) {
// construct the GeneralPath
GeneralPath gp = new GeneralPath();
boolean cont = false;
int targetRGB = target.getRGB();
for (int xx=0; xx<bi.getWidth(); xx++) {
for (int yy=0; yy<bi.getHeight(); yy++) {
if (bi.getRGB(xx,yy)==targetRGB) {
if (cont) {
gp.lineTo(xx,yy);
gp.lineTo(xx,yy+1);
gp.lineTo(xx+1,yy+1);
gp.lineTo(xx+1,yy);
gp.lineTo(xx,yy);
} else {
gp.moveTo(xx,yy);
}
cont = true;
} else {
cont = false;
}
}
cont = false;
}
gp.closePath();
// construct the Area from the GP & return it
return new Area(gp);
}
public JPanel getGui() {
JPanel images = new JPanel(new GridLayout(1,2,2,2));
JPanel gui = new JPanel(new BorderLayout(3,3));
JPanel originalImage = new JPanel(new BorderLayout(2,2));
final JLabel originalLabel = new JLabel(new ImageIcon(image));
originalImage.add(originalLabel);
images.add(originalImage);
ImageShape = new BufferedImage(
image.getWidth(),
image.getHeight(),
BufferedImage.TYPE_INT_RGB
);
labelOutline = new JLabel(new ImageIcon(ImageShape));
images.add(labelOutline);
anim = new BufferedImage(
image.getWidth()*2,
image.getHeight()*2,
BufferedImage.TYPE_INT_RGB);
output = new JLabel(new ImageIcon(anim));
gui.add(output, BorderLayout.CENTER);
updateImages();
gui.add(images, BorderLayout.NORTH);
animate();
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
animate();
}
};
Timer timer = new Timer(1,al);
timer.start();
return gui;
}
private void updateImages() {
areaOutline = getOutline(Color.BLACK, image);
drawOutline();
}
private void animate() {
Graphics2D gr = anim.createGraphics();
gr.setColor(Color.BLUE);
gr.fillRect(0,0,anim.getWidth(),anim.getHeight());
count++;
if (count%100==0) {
long now = System.currentTimeMillis();
long duration = now-time;
double fraction = (double)duration/1000;
rate = "" + (double)100/fraction;
time = now;
}
gr.setColor(Color.WHITE);
gr.translate(0,0);
gr.drawString(rate, 20, 20);
int x = random.nextInt(image.getWidth());
int y = random.nextInt(image.getHeight());
gr.translate(x,y);
int r = 128+random.nextInt(127);
int g = 128+random.nextInt(127);
int b = 128+random.nextInt(127);
gr.setColor(new Color(r,g,b));
gr.draw(areaOutline);
gr.dispose();
output.repaint();
}
public static void main(String[] args) throws Exception {
int size = 150;
final BufferedImage outline = javax.imageio.ImageIO.read(new java.io.File("img.gif"));
ImageShape io = new ImageShape(outline);
JFrame f = new JFrame("Image Outline");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(io.getGui());
f.pack();
f.setResizable(false);
f.setLocationByPlatform(true);
f.setVisible(true);
}
}
我必須指出藍色圖像左上角的FPS計數有十倍的誤差。 50 FPS我可以相信,但500 FPS似乎......錯了。
好的,看起來我找到了最好的解決方案。 成功的關鍵是訪問底層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);
}
}
在這個之后,你有一個buffer
,你可以在畫布更新和ARGB 4字節整數的基礎數組 - data
上blit。
可以像這樣繪制單個字符:
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;
}
}
這個使用一個簡單的boolean[][]
數組,其中第一個索引選擇字符,第二個索引迭代原始的1位字符像素數據(true => foreground,false => background)。
我將盡快發布一個完整的解決方案作為我的Java終端仿真類集的一部分。
這個解決方案基准測試令人印象深刻的26007弦/秒或1846553個字符/秒 - 比以前最好的非彩色drawImage()
快2.3倍。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.