简体   繁体   中英

Increase quality of grayscale image produced from BufferedImage

I am attempting to increase the quality of an image produced from a BufferedImage. The final aim is for a JPEG to be input (here it is retrieved from a file on the computer), be converted to a grayscale TIFF and then output as a byte array. I have included code to save the final image to the PC so it is easier to discern the problem.

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.SampleModel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.media.jai.ImageLayout;
import javax.media.jai.JAI;
import javax.media.jai.KernelJAI;
import javax.media.jai.LookupTableJAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.operator.ErrorDiffusionDescriptor;

public class ByteConversionService {

    private static ByteArrayOutputStream baos;
    private static ImageWriter writer;
    private static ImageOutputStream ios;
    private static ImageWriteParam writeParam;

    public static void main(String args[]) {
        try {
            convertBufferedImageToByteArray();
        } catch (Exception e) {

        }
    }

    private static byte[] convertBufferedImageToByteArray()
            throws Exception {
        byte[] convertedByteArray = null;
        resourceSetup();
        try {                   
            File file = new File("../proj/src/image.jpg");
            BufferedImage image = ImageIo.read(file);
            convertImageToTif(image);
            createImage(baos);
            convertedByteArray = baos.toByteArray();
        } finally {
            resourceCleanup();
        }   
        return convertedByteArray;
    }

    private static void resourceSetup() throws Exception {
        baos = new ByteArrayOutputStream();
        writer = ImageIO.getImageWritersByFormatName(
                "tif").next();
        ios = ImageIO.createImageOutputStream(baos);
        writer.setOutput(ios);
        writeParam = writer.getDefaultWriteParam();
        writeParamSetUp(writeParam);
    }

    private static void writeParamSetUp(ImageWriteParam writeParam) {
        writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        writeParam.setCompressionType("CCITT T.4");
    }

    private static void convertImageToTif(BufferedImage image) throws Exception {
        try {
            BufferedImage blackAndWhiteImage = imageToBlackAndWhite(image);
            writeToByteArrayStream(blackAndWhiteImage);
            IIOImage iioImage = new IIOImage(blackAndWhiteImage, null, null);
            writer.write(null, iioImage, writeParam);
        } finally {
            image.flush();
        }
    }

    private static BufferedImage imageToBlackAndWhite(BufferedImage image) {
        PlanarImage surrogateImage = PlanarImage.wrapRenderedImage(image);
        LookupTableJAI lut = new LookupTableJAI(new byte[][] {
                { (byte) 0x00, (byte) 0xff }, { (byte) 0x00, (byte) 0xff },
                { (byte) 0x00, (byte) 0xff } });
        ImageLayout layout = new ImageLayout();
        byte[] map = new byte[] { (byte) 0x00, (byte) 0xff };
        ColorModel cm = new IndexColorModel(1, 2, map, map, map);
        layout.setColorModel(cm);
        SampleModel sm = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
                surrogateImage.getWidth(), surrogateImage.getHeight(), 1);
        layout.setSampleModel(sm);
        RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout);
        PlanarImage op = ErrorDiffusionDescriptor.create(surrogateImage, lut,
                KernelJAI.ERROR_FILTER_FLOYD_STEINBERG, hints);
        BufferedImage blackAndWhiteImage = op.getAsBufferedImage();
        return blackAndWhiteImage;
    }

    private static void writeToByteArrayStream(BufferedImage image) throws Exception {
        ImageIO.write(image, "tif", baos);
    }

    private static void createImage(ByteArrayOutputStream baos) throws Exception {
        ByteArrayInputStream bis = new ByteArrayInputStream(baos.toByteArray());
        ImageReader reader = (ImageReader) ImageIO.getImageReadersByFormatName(
                "tif").next();
        Object source = bis;
        ImageInputStream iis = ImageIO.createImageInputStream(source);
        reader.setInput(iis, true);
        ImageReadParam param = reader.getDefaultReadParam();
        Image image = reader.read(0, param);
        BufferedImage buffered = new BufferedImage(image.getWidth(null),
                image.getHeight(null), BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = buffered.createGraphics();
        g2.drawImage(image, null, null);
        File file = new File("../proj/src/image2.tif");
        ImageIO.write(buffered, "tif", file);
    }

    private static void resourceCleanup() throws Exception {
        ios.flush();
        ios.close();
        baos.flush();
        baos.close();
        writer.dispose();
    }
}

The current issue is that the final image is of low quality - zooming in shows a lot of white space between the pixels composing the picture. My understanding is this is possibly due to the dithering algorithm used (Floyd-Steinberg) so the image is not technically reproduced in grayscale.

I have attempted multiple solutions which I will post in the comments, but with no success. My question is if the final quality can be increased with my current solution or if my conversion to grayscale is flawed and the imageToBlackAndWhite method is incorrect for my needs.

Now that we've established that the desired outcome is indeed a gray scale image, we can fix the code so that is produces a gray scale TIFF.

Two things needs to be changed, first the color space conversion from RGB to Gray:

private static BufferedImage imageToBlackAndWhite(BufferedImage image) {
    ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
    ColorConvertOp op = new ColorConvertOp(cs, null);  
    return op.filter(image, null);
}

I prefer the ColorConvertOp , as it is the most "correct", and on most platforms uses native code. But any of the other methods you listed should also work. You may also want to consider renaming the method to imageToGrayScale for clarity.

In addition, you need to change the TIFF compression setting, as CCITT T.4 compression can only be used with binary black/white images (it's created for FAX transmissions). I suggest you use the Deflate or LZW compression, or perhaps JPEG, if you can live with a lossy compression. These all work well with grayscale data:

private static void writeParamSetUp(ImageWriteParam writeParam) {
    writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    writeParam.setCompressionType("Deflate"); // or LZW or JPEG
} 

PS: You should also get rid of the writeToByteArrayStream method, as your current code writes the TIFF twice, once uncompressed using ImageIO.write(...) , then once compressed using writer.write(...) .

PPS: The createImage method can also be simplified a lot, given that the ByteArrayOutputStream already contains a full TIFF.

private static void createImage(ByteArrayOutputStream baos) throws Exception {
    File file = new File("../proj/src/image2.tif");
    Files.write(file.toPath(), baos.toByteArray(), StandardOpenOption.CREATE);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM