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

后端 未结 2 1589
长发绾君心
长发绾君心 2021-01-18 10:28

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

相关标签:
2条回答
  • 2021-01-18 11:19

    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.

    500+ FPS?!?

    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.

    0 讨论(0)
  • 2021-01-18 11:22

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

    0 讨论(0)
提交回复
热议问题