Java Text on Image
Asked Answered
A

4

6

I am loading an image in bufferedimage and then writing some text on it .After I add text it makes image blurry and text distorted. I have TEXT ANTIALIASING ON . It can be seen as attached.

enter image description here

Alves answered 13/5, 2011 at 17:48 Comment(0)
E
8

You should be able to control the text quality with the rendering hints that are shown in the other responses. I know that it works because I have done it a lot, so I think it must be something else that causes the quality degradation in this case.

  1. How do you check the image quality? Do you paint the generated BufferedImage directly into a screen graphics in your java application or do you save to disk, i.e. as JPEG? If you save it to disk try to save it as PNG 24 and not as JPEG. I assume your desktop screen is running in True Color (32 or 24 bits color depth), right?
  2. Are you sure that the image is actually created as BufferedImage.TYPE_INT_RGB? If you have no control over the creation of the BufferedImage in your code try to create a new BufferedImage with TYPE_INT_RGB and paint the source into this one and then draw the text into it.
  3. Try setting the RenderingHints.KEY_DITHERING to RenderingHints.VALUE_DITHER_DISABLE (although this shouldn't be needed for true color images).

If this still doesn't help you to find the cause please provide some more information (VM version, operating system) and source code. The text rendering has become quite good with JDK 1.6 Update 10 but also earlier releases were able to produce clean text in images, it just didn't look as good because of less sophisticated antialiasing.

in response to your comment:

High contrast sharp edges as in text are a general problem with JPEG compression since it is not a lossless compression. If you really need to go with JPEG and can't switch to PNG, you can tune the compression quality of your saved image to find a better compromise between image quality and file size. See the following code on how to set the compression quality when you save a 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();
Evitaevitable answered 16/5, 2011 at 14:46 Comment(6)
Well I just tried to see if image type makes difference and found that png images are not getting distorted after adding text.Alves
So it looks like the JPEG compression is to blame. I extended my answer text above since it was too long for a comment.Evitaevitable
@x4u: "Do you paint the generated BufferedImage directly into a screen graphics in your java application or do you save to disk, i.e. as JPEG?" Great question. +1Probability
@x4u: Indeed, this looks like a normal lossy JPEG. +1Divagate
+1 . @Evitaevitable As png are working fine in my case should I add a special condition to save jpgs ? Or save all images as jpg ? Suggestions ?Alves
It depends on what you want to do with the images. JPEGs (especially from photos) can usually be compressed to much smaller file sizes then PNGs even when you use a rather high JPEG compression quality. PNGs on the other hand give you the best possible image quality. I'd suggest to try out different compression quality values and see for yourself if you can live with a better but still not perfect JPEG or if it has to be PNG. If file size is not important go with PNG.Evitaevitable
P
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();
            }
        } );
    }
}

Screen shot

Typical outputs

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
Probability answered 13/5, 2011 at 21:33 Comment(2)
+1 for including all so much of details ! Although I have same issue of blurred text after removing anti aliasing .Alves
You should use ItemListener for checkboxes as well.Divagate
E
8

You should be able to control the text quality with the rendering hints that are shown in the other responses. I know that it works because I have done it a lot, so I think it must be something else that causes the quality degradation in this case.

  1. How do you check the image quality? Do you paint the generated BufferedImage directly into a screen graphics in your java application or do you save to disk, i.e. as JPEG? If you save it to disk try to save it as PNG 24 and not as JPEG. I assume your desktop screen is running in True Color (32 or 24 bits color depth), right?
  2. Are you sure that the image is actually created as BufferedImage.TYPE_INT_RGB? If you have no control over the creation of the BufferedImage in your code try to create a new BufferedImage with TYPE_INT_RGB and paint the source into this one and then draw the text into it.
  3. Try setting the RenderingHints.KEY_DITHERING to RenderingHints.VALUE_DITHER_DISABLE (although this shouldn't be needed for true color images).

If this still doesn't help you to find the cause please provide some more information (VM version, operating system) and source code. The text rendering has become quite good with JDK 1.6 Update 10 but also earlier releases were able to produce clean text in images, it just didn't look as good because of less sophisticated antialiasing.

in response to your comment:

High contrast sharp edges as in text are a general problem with JPEG compression since it is not a lossless compression. If you really need to go with JPEG and can't switch to PNG, you can tune the compression quality of your saved image to find a better compromise between image quality and file size. See the following code on how to set the compression quality when you save a 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();
Evitaevitable answered 16/5, 2011 at 14:46 Comment(6)
Well I just tried to see if image type makes difference and found that png images are not getting distorted after adding text.Alves
So it looks like the JPEG compression is to blame. I extended my answer text above since it was too long for a comment.Evitaevitable
@x4u: "Do you paint the generated BufferedImage directly into a screen graphics in your java application or do you save to disk, i.e. as JPEG?" Great question. +1Probability
@x4u: Indeed, this looks like a normal lossy JPEG. +1Divagate
+1 . @Evitaevitable As png are working fine in my case should I add a special condition to save jpgs ? Or save all images as jpg ? Suggestions ?Alves
It depends on what you want to do with the images. JPEGs (especially from photos) can usually be compressed to much smaller file sizes then PNGs even when you use a rather high JPEG compression quality. PNGs on the other hand give you the best possible image quality. I'd suggest to try out different compression quality values and see for yourself if you can live with a better but still not perfect JPEG or if it has to be PNG. If file size is not important go with PNG.Evitaevitable
S
2

try this:

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);
}
Spirochete answered 13/5, 2011 at 18:0 Comment(2)
Tried it with GASP too it didnt make any difference. download.oracle.com/javase/tutorial/2d/text/renderinghints.htmlAlves
It worked. Does it needed all the hints?Comeback
O
1

thats just java. simply turn off text anti aliasing. or if you really need it, render the text on a separate image, if the text is black the get all pixels with color under about rgb(100,100,100) and set them to 0. then paint that buffer onto your main image.

Osbourne answered 13/5, 2011 at 18:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.