使用ImageIO导出8位深度的JPEG图片。

huangapple go评论70阅读模式
英文:

Exporting 8 bit-dpeth JPEG with ImageIO

问题

public void testJpegBitDepth() throws Exception {
    Path pIn = Paths.get("testing/jpg/box1.jpg"), pOut;
    BufferedImage bi;

    // *******************************************
    // Write 8 bit jpg

    // Init ImageWriter
    Iterator<ImageWriter> it = ImageIO.getImageWritersByFormatName("jpg");
    ImageWriter writer = null;
    while (it.hasNext()) {
        try {
            writer = it.next();

            // Read input
            bi = ImageIO.read(pIn.toFile());
            if (bi == null)
                throw new Exception("Failed to read input file: " + pIn);

            // Convert to gray
            bi = AWTImaging.convertToGray(bi);
            log.debug("Num bands from the image raster: " + bi.getRaster().getNumBands());

            pOut = test.outputDir.resolve("jpegBitDepth-8-"
                    + pIn.getFileName().toString() + ".jpg");

            // Init ImageTypeSpecifier
            ImageTypeSpecifier imageType = ImageTypeSpecifier.createGrayscale(
                    8,                        // 8 bits per pixel 
                    DataBuffer.TYPE_BYTE,     // stored in a byte
                    false);                   // unsigned

            // Init WriteParam
            ImageWriteParam param = writer.getDefaultWriteParam();
            param.setDestinationType(imageType);
            // Not sure if this is required or not, but the same Exception occurs either way
            // param.setSourceBands(new int[] {0});

            // Init meta
            IIOMetadata meta = writer.getDefaultImageMetadata(imageType, param);

            String metadataFormat = "javax_imageio_jpeg_image_1.0";
            IIOMetadataNode root = new IIOMetadataNode(metadataFormat);
            IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety");
            IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");

            // I think we want app0JFIF metadata here, as it can specify a grayscale image https://docs.oracle.com/javase/10/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
            IIOMetadataNode app0JFIF = new IIOMetadataNode("app0JFIF");

            root.appendChild(jpegVariety);
            root.appendChild(markerSequence);
            jpegVariety.appendChild(app0JFIF);

            meta.mergeTree(metadataFormat, root);

            // Export jpg
            Files.deleteIfExists(pOut);
            ImageOutputStream ios = ImageIO.createImageOutputStream(pOut.toFile());
            writer.setOutput(ios);
            writer.write(meta, new IIOImage(bi, null, meta), param);
            log.debug("Succeeded writing jpeg with writer: " + writer.getClass().toString());
            break;
        } catch (Exception e) {
            log.error("Failed writing jpeg with writer: " + (writer != null ? writer.getClass().toString() : "null"));
            log.error("Ex: " + e);
        }
    }
}

I'm getting an Exception thrown from JpegImageWriter, here is the relevant stack trace:

Ex: javax.imageio.IIOException: Metadata components != number of destination bands
File=null,Class=com.sun.imageio.plugins.jpeg.JPEGImageWriter,Method=checkSOFBands,Line=-1
File=null,Class=com.sun.imageio.plugins.jpeg.JPEGImageWriter,Method=writeOnThread,Line=-1
File=null,Class=com.sun.imageio.plugins.jpeg.JPEGImageWriter,Method=write,Line=-1

Also, I know that the BufferedImage is a TYPE_BYTE_BINARY, and the raster has 1 band (I printed this in a debug message above). So the Exception message would make me think that I need to define in the app0JFIF metadata that we are exporting 1 band. I don't know how to define this, though. Does anyone have any experience with this? This metadata is difficult to work with, or is it just me?

Thanks in advance.

英文:

Using Java ImageIO, is it possible to export a jpeg image that has a bit-depth of 8? How would I do this? Even when exporting a BufferedImage of TYPE_BYTE_BINARY, which is a grayscale image, the result is a JPEG with bit-depth of 24.

This is what I have so far.

public void testJpegBitDepth() throws Exception{
Path pIn = Paths.get(&quot;testing/jpg/box1.jpg&quot;), pOut;
BufferedImage bi;
//*******************************************
//Write 8 bit jpg
//Init ImageWriter
Iterator&lt;ImageWriter&gt; it = ImageIO.getImageWritersByFormatName(&quot;jpg&quot;);
ImageWriter writer = null;
while(it.hasNext()) {
try {
writer = it.next();
//Read input
bi = ImageIO.read(pIn.toFile());
if(bi == null)
throw new Exception(&quot;Failed to read input file: &quot; + pIn);
//Convert to gray
bi = AWTImaging.convertToGray(bi);
log.debug(&quot;Num bands from the image raster: &quot; + bi.getRaster().getNumBands());
pOut = test.outputDir.resolve(&quot;jpegBitDepth-8-&quot;
+ pIn.getFileName().toString() + &quot;.jpg&quot;);
//Init ImageTypeSpecifier
ImageTypeSpecifier imageType = ImageTypeSpecifier.createGrayscale(
8,						//8 bits per pixel 
DataBuffer.TYPE_BYTE, 	//stored in a byte
false);					//unsigned
//Init WriteParam
ImageWriteParam param = writer.getDefaultWriteParam();
param.setDestinationType(imageType);
//Not sure if this is required or not, but the same Exception occurs either way
//param.setSourceBands(new int[] {0});
//Init meta
IIOMetadata meta = writer.getDefaultImageMetadata(imageType, param);
String metadataFormat = &quot;javax_imageio_jpeg_image_1.0&quot;;
IIOMetadataNode root = new IIOMetadataNode(metadataFormat);
IIOMetadataNode jpegVariety = new IIOMetadataNode(&quot;JPEGvariety&quot;);
IIOMetadataNode markerSequence = new IIOMetadataNode(&quot;markerSequence&quot;);
//I think we want app0JFIF metadata here, as it can specify a grayscale image https://docs.oracle.com/javase/10/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
IIOMetadataNode app0JFIF = new IIOMetadataNode(&quot;app0JFIF&quot;);
root.appendChild(jpegVariety);
root.appendChild(markerSequence);
jpegVariety.appendChild(app0JFIF);
meta.mergeTree(metadataFormat, root);
//Export jpg
Files.deleteIfExists(pOut);
ImageOutputStream ios = ImageIO.createImageOutputStream(pOut.toFile());
writer.setOutput(ios);
writer.write(meta, new IIOImage(bi, null, meta), param);
log.debug(&quot;Succeded writing jpeg with writer: &quot; + writer.getClass().toString());
break;
}catch(Exception e) {
log.error(&quot;Failed writing jpeg with writer: &quot; + (writer != null ? writer.getClass().toString():&quot;null&quot;));
log.error(&quot;Ex: &quot; + e);				
}
}
}

I'm getting an Exception thrown from JpegImageWriter, here is the relevant stack trace:

    Ex: javax.imageio.IIOException: Metadata components != number of destination bands
File=null,Class=com.sun.imageio.plugins.jpeg.JPEGImageWriter,Method=checkSOFBands,Line=-1
File=null,Class=com.sun.imageio.plugins.jpeg.JPEGImageWriter,Method=writeOnThread,Line=-1
File=null,Class=com.sun.imageio.plugins.jpeg.JPEGImageWriter,Method=write,Line=-1

Also I know that the Buffered Image is a TYPE_BYTE_BINARY, and the raster has 1 band (I printed this in a debug message above). So the Exception message would make me think that I need to define in the app0JFIF metadata that we are exporting 1 band. I don't know how to define this though, does anyone have any experience with this? This metadata is difficult to work with, or is it just me?

Thanks in advance.

答案1

得分: 0

你关于只需要一个通道是正确的。以下是我的操作方法:

if (bi.getSampleModel().getNumBands() != 1) {
    ColorModel colorModel = new ComponentColorModel(
        ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false,
        Transparency.OPAQUE, DataBuffer.TYPE_BYTE);

    BufferedImage oneBandedImage = new BufferedImage(colorModel,
        colorModel.createCompatibleWritableRaster(
            bi.getWidth(), bi.getHeight()),
        false, new Properties());
        
    Graphics g = oneBandedImage.createGraphics();
    g.drawImage(bi, 0, 0, null);
    g.dispose();

    bi = oneBandedImage;
}

经过这样的处理,我不需要直接获取一个ImageWriter,也不需要设置任何元数据;只需要使用ImageIO.write(bi, "JPEG", file)就足够了。

我在结果上运行了/usr/bin/file,得到了以下结果:

JPEG image data,JFIF standard 1.02,aspect ratio,density 1x1,segment length 16,baseline,precision 8,315x180,components 1

我认为components 1部分表示它只有一个通道。

英文:

You are correct about needing one band. Here’s how I did it:

if (bi.getSampleModel().getNumBands() != 1) {
ColorModel colorModel = new ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false,
Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
BufferedImage oneBandedImage = new BufferedImage(colorModel,
colorModel.createCompatibleWritableRaster(
bi.getWidth(), bi.getHeight()),
false, new Properties());
Graphics g = oneBandedImage.createGraphics();
g.drawImage(bi, 0, 0, null);
g.dispose();
bi = oneBandedImage;
}

After doing that, I didn’t need to directly obtain an ImageWriter and I didn’t need to set any metadata; ImageIO.write(bi, &quot;JPEG&quot;, file) was sufficient.

I ran /usr/bin/file on the result, and got this:

JPEG image data, JFIF standard 1.02, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 315x180, components 1

I assume the components 1 part means that it has only one channel.

huangapple
  • 本文由 发表于 2020年4月11日 09:40:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/61151066.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定