Java bitmap font: blitting 1-bit image with different colors

懵懂的女人 提交于 2019-12-01 18:12:44
Andrew Thompson

You might turn each bitmap into a Shape (or many of them) and draw the Shape. See Smoothing a jagged path for the process of gaining the Shape.

E.G.

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);
    }
}

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.

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:

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);
    }
}

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:

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;
    }
}

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().

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!