Java图片上的文本

6

我正在使用BufferedImage加载图像,然后在其上写一些文本。添加文本后,图像会变得模糊,文本会变形。我已经开启了文本抗锯齿功能(TEXT ANTIALIASING ON)。如下图所示:

enter image description here

4个回答

12
import java.awt.image.BufferedImage;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.imageio.*;
import java.io.*;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import java.util.Locale;

class ImageCompressionDemo {

    private BufferedImage originalImage;
    private BufferedImage textImage;

    private JPanel gui;

    private JCheckBox antialiasing;
    private JCheckBox rendering;
    private JCheckBox fractionalMetrics;
    private JCheckBox strokeControl;
    private JCheckBox colorRendering;
    private JCheckBox dithering;

    private JComboBox textAntialiasing;
    private JComboBox textLcdContrast;

    private JLabel jpegLabel;
    private JLabel pngLabel;

    private JTextArea output;

    private JSlider quality;

    private int pngSize;
    private int jpgSize;

    final static Object[] VALUES_TEXT_ANTIALIASING = {
        RenderingHints.VALUE_TEXT_ANTIALIAS_OFF,
        RenderingHints.VALUE_TEXT_ANTIALIAS_ON,
        RenderingHints.VALUE_TEXT_ANTIALIAS_GASP,
        RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HBGR,
        RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB,
        RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VBGR,
        RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VRGB
    };

    final static Object[] VALUES_TEXT_LCD_CONTRAST = {
        new Integer(100),
        new Integer(150),
        new Integer(200),
        new Integer(250)
    };

    ImageCompressionDemo() {
        int width = 280;
        int height = 100;

        gui = new JPanel(new BorderLayout(3,4));

        quality = new JSlider(JSlider.VERTICAL, 0, 100, 75);
        quality.setSnapToTicks(true);
        quality.setPaintTicks(true);
        quality.setPaintLabels(true);
        quality.setMajorTickSpacing(10);
        quality.setMinorTickSpacing(5);
        quality.addChangeListener( new ChangeListener(){
            public void stateChanged(ChangeEvent ce) {
                updateImages();
            }
        } );
        gui.add(quality, BorderLayout.WEST);

        originalImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
        textImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);

        JPanel controls = new JPanel(new GridLayout(0,1,0,0));
        antialiasing = new JCheckBox("Anti-aliasing", false);
        rendering = new JCheckBox("Rendering - Quality", true);
        fractionalMetrics = new JCheckBox("Fractional Metrics", true);
        strokeControl = new JCheckBox("Stroke Control - Pure", false);
        colorRendering = new JCheckBox("Color Rendering - Quality", true);
        dithering = new JCheckBox("Dithering", false);

        controls.add(antialiasing);

        controls.add(fractionalMetrics);

        textLcdContrast = new JComboBox(VALUES_TEXT_LCD_CONTRAST);
        JPanel lcdContrastPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        lcdContrastPanel.add(textLcdContrast);
        lcdContrastPanel.add(new JLabel("Text LCD Contrast"));
        controls.add(lcdContrastPanel);

        textAntialiasing = new JComboBox(VALUES_TEXT_ANTIALIASING);
        controls.add(textAntialiasing);

        controls.add(dithering);
        controls.add(rendering);
        controls.add(colorRendering);
        controls.add(strokeControl);


        ItemListener itemListener = new ItemListener(){    
            public void itemStateChanged(ItemEvent e) {
                updateImages();
            }
        };
        antialiasing.addItemListener(itemListener);
        rendering.addItemListener(itemListener);
        fractionalMetrics.addItemListener(itemListener);
        strokeControl.addItemListener(itemListener);
        colorRendering.addItemListener(itemListener);
        dithering.addItemListener(itemListener);

        textAntialiasing.addItemListener(itemListener);
        textLcdContrast.addItemListener(itemListener);

        Graphics2D g2d = originalImage.createGraphics();
        GradientPaint gp = new GradientPaint(
            0f, 0f, Color.red,
            (float)width, (float)height, Color.orange);
        g2d.setPaint(gp);
        g2d.fillRect(0,0, width, height);

        g2d.setColor(Color.blue);
        for (int ii=0; ii<width; ii+=10) {
            g2d.drawLine(ii, 0, ii, height);
        }
        g2d.setColor(Color.green);
        for (int jj=0; jj<height; jj+=10) {
            g2d.drawLine(0, jj, width, jj);
        }

        gui.add(controls, BorderLayout.EAST);

        JPanel images = new JPanel(new GridLayout(0,1,2,2));
        images.add(new JLabel(new ImageIcon(textImage)));

        try {
            pngLabel = new JLabel(new ImageIcon(getPngCompressedImage(textImage)));
            images.add(pngLabel);
            jpegLabel = new JLabel(new ImageIcon(getJpegCompressedImage(textImage)));
            images.add(jpegLabel);
        } catch(IOException ioe) {
        }

        gui.add(images, BorderLayout.CENTER);
        output = new JTextArea(4,40);
        output.setEditable(false);
        gui.add(new JScrollPane(output), BorderLayout.SOUTH);

        updateImages();

        JOptionPane.showMessageDialog(null, gui);
    }

    private Image getPngCompressedImage(BufferedImage image) throws IOException {
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        ImageIO.write( image, "png", outStream );

        pngSize = outStream.toByteArray().length;

        BufferedImage compressedImage =
            ImageIO.read(new ByteArrayInputStream(outStream.toByteArray()));

        return compressedImage;
    }

    private Image getJpegCompressedImage(BufferedImage image) throws IOException {
        float qualityFloat = (float)quality.getValue()/100f;
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();

        ImageWriter imgWriter = ImageIO.getImageWritersByFormatName( "jpg" ).next();
        ImageOutputStream ioStream = ImageIO.createImageOutputStream( outStream );
        imgWriter.setOutput( ioStream );

        JPEGImageWriteParam jpegParams = new JPEGImageWriteParam( Locale.getDefault() );
        jpegParams.setCompressionMode( ImageWriteParam.MODE_EXPLICIT );
        jpegParams.setCompressionQuality( qualityFloat );

        imgWriter.write( null, new IIOImage( image, null, null ), jpegParams );

        ioStream.flush();
        ioStream.close();
        imgWriter.dispose();

        jpgSize = outStream.toByteArray().length;

        BufferedImage compressedImage = ImageIO.read(new ByteArrayInputStream(outStream.toByteArray()));
        return compressedImage;
    }

    private void updateText() {
        StringBuilder builder = new StringBuilder();

        builder.append("Fractional Metrics: \t");
        builder.append( fractionalMetrics.isSelected() );
        builder.append("\n");
        builder.append( textAntialiasing.getSelectedItem() );
        builder.append("\nPNG size: \t");
        builder.append(pngSize);
        builder.append(" bytes\n");
        builder.append("JPG size: \t");
        builder.append(jpgSize);
        builder.append(" bytes \tquality: ");
        builder.append(quality.getValue());

        output.setText(builder.toString());
    }

    private void updateImages() {
        int width = originalImage.getWidth();
        int height = originalImage.getHeight();

        Graphics2D g2dText = textImage.createGraphics();

        if (antialiasing.isSelected()) {
            g2dText.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        } else {
            g2dText.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_OFF);
        }

        if (rendering.isSelected()) {
            g2dText.setRenderingHint(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_QUALITY);
        } else {
            g2dText.setRenderingHint(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_SPEED);
        }

        if (fractionalMetrics.isSelected()) {
            g2dText.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
                RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        } else {
            g2dText.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
                RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
        }

        if (strokeControl.isSelected()) {
            g2dText.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                RenderingHints.VALUE_STROKE_NORMALIZE);
        } else {
            g2dText.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                RenderingHints.VALUE_STROKE_PURE);
        }

        if (dithering.isSelected()) {
            g2dText.setRenderingHint(RenderingHints.KEY_DITHERING,
                RenderingHints.VALUE_DITHER_ENABLE);
        } else {
            g2dText.setRenderingHint(RenderingHints.KEY_DITHERING,
                RenderingHints.VALUE_DITHER_DISABLE);
        }

        if (colorRendering.isSelected()) {
            g2dText.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
                RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        } else {
            g2dText.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
                RenderingHints.VALUE_COLOR_RENDER_SPEED);
        }

        g2dText.setRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST,
            textLcdContrast.getSelectedItem());

        g2dText.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
            textAntialiasing.getSelectedItem());

        g2dText.drawImage(originalImage, 0,0, null);
        g2dText.setColor(Color.black);
        g2dText.drawString("The quick brown fox jumped over the lazy dog.", 10,50);

        try {
            jpegLabel.setIcon(new ImageIcon(getJpegCompressedImage(textImage)));
            pngLabel.setIcon(new ImageIcon(getPngCompressedImage(textImage)));
        } catch(IOException ioe) {
        }

        gui.repaint();
        updateText();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater( new Runnable() {
            public void run() {
                ImageCompressionDemo iwt = new ImageCompressionDemo();
            }
        } );
    }
}

截图

典型输出

Fractional Metrics:     true
Nonantialiased text mode
PNG size:   7390 bytes
JPG size:   7036 bytes  quality: 35

Fractional Metrics:     true
Antialiased text mode
PNG size:   8741 bytes
JPG size:   8663 bytes  quality: 55

Fractional Metrics:     false
Antialiased text mode
PNG size:   8720 bytes
JPG size:   8717 bytes  quality: 55

+1 因为包含了这么多细节!虽然我在去除抗锯齿后也遇到了模糊文本的问题。 - Pit Digger
2
你应该对复选框使用 ItemListener - Martijn Courteaux

8
您应该能够通过渲染提示来控制文本质量,这些提示在其他响应中显示。我知道它有效,因为我经常使用它,所以我认为在这种情况下导致质量降低的必须是其他原因。
  1. 您如何检查图像质量?您是否直接在Java应用程序的屏幕图形中绘制生成的BufferedImage,还是将其保存到磁盘中,例如JPEG?如果您将其保存到磁盘,请尝试将其保存为PNG 24而不是JPEG。我假设您的桌面屏幕正在运行True Color(32或24位颜色深度),对吗?
  2. 您确定该图像实际上是创建为BufferedImage.TYPE_INT_RGB吗?如果您无法控制代码中BufferedImage的创建,请尝试创建一个新的BufferedImage并将源绘制到其中,然后再将文本绘制到其中。
  3. 尝试将RenderingHints.KEY_DITHERING设置为RenderingHints.VALUE_DITHER_DISABLE(虽然对于真彩色图像不需要)。

如果这仍然无法帮助您找到原因,请提供更多信息(VM版本,操作系统)和源代码。JDK 1.6 Update 10的文本呈现已经非常好了,但早期版本也能够在图像中产生清晰的文本,只是由于较少的抗锯齿而看起来不太好。

回复您的评论:

高对比度锐利的边缘(如文本)是JPEG压缩的普遍问题,因为它不是无损压缩。如果您确实需要使用JPEG并且无法切换到PNG,则可以调整保存图像的压缩质量,以找到更好的图像质量和文件大小之间的平衡点。请参阅以下代码,了解如何在保存JPEG时设置压缩质量。

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.ImageOutputStream;

//----

float quality = 0.85f;
File outfile = new File( "MyImage.jpg" );
BufferedImage image = ...;

ImageWriter imgWriter = ImageIO.getImageWritersByFormatName( "jpg" ).next();
ImageOutputStream ioStream = ImageIO.createImageOutputStream( outfile );
imgWriter.setOutput( ioStream );

JPEGImageWriteParam jpegParams = new JPEGImageWriteParam( Locale.getDefault() );
jpegParams.setCompressionMode( ImageWriteParam.MODE_EXPLICIT );
jpegParams.setCompressionQuality( quality );

imgWriter.write( null, new IIOImage( image, null, null ), jpegParams );

ioStream.flush();
ioStream.close();
imgWriter.dispose();

我刚刚尝试了一下,看看图像类型是否有所不同,发现在添加文本后,PNG图像不会失真。 - Pit Digger
1
看起来是JPEG压缩的问题。我在上面扩展了我的答案文本,因为它太长了,不适合作为评论。 - x4u
3
你是直接将生成的BufferedImage绘制到Java应用程序的屏幕图形中,还是保存为JPEG格式文件?这是个很好的问题,点赞。 - Andrew Thompson
1
@x4u:确实,这看起来像是普通的有损JPEG。+1 - Martijn Courteaux
+1 . @x4u 在我的情况下,png 文件可以正常工作,我是否应该添加特殊条件来保存 jpg 文件?还是将所有图像都保存为 jpg?有什么建议吗? - Pit Digger
1
这取决于您对图像的使用需求。JPEG(尤其是来自照片的JPEG)通常可以在使用较高的JPEG压缩质量时,将文件大小压缩得比PNG小得多。而PNG则提供了最佳的图像质量。我建议您尝试不同的压缩质量值,并自行判断是否可以接受更好但仍不完美的JPEG,或者必须使用PNG。如果文件大小不重要,选择PNG格式。 - x4u

1

这只是Java。简单地关闭文本反锯齿即可。 或者,如果您真的需要它,请在单独的图像上呈现文本,如果文本是黑色的,则获取所有颜色低于大约rgb(100,100,100)的像素并将它们设置为0。然后将该缓冲区绘制到主图像上。


1

试试这个:

public static void setGraphicsQuality(Graphics2D g2D) {
    g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
    g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    g2D.setRenderingHint(RenderingHints.KEY_RENDERING,
            RenderingHints.VALUE_RENDER_QUALITY);
}

我也试过使用GASP,但并没有什么区别。请访问http://download.oracle.com/javase/tutorial/2d/text/renderinghints.html。 - Pit Digger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接