Custom quantization tables for JPEG compression in Java - java

As the title says, I'm trying to use custom quantization tables to compress an image in JPEG format. My problem is the resulting file can't be opened and the error is:
Quantization table 0x00 was not defined
This is how my code looks like:
JPEGImageWriteParam params = new JPEGImageWriteParam(null);
if (mQMatrix != null) {
JPEGHuffmanTable[] huffmanDcTables = {JPEGHuffmanTable.StdDCLuminance, JPEGHuffmanTable.StdDCChrominance};
JPEGHuffmanTable[] huffmanAcTables = {JPEGHuffmanTable.StdACLuminance, JPEGHuffmanTable.StdACChrominance};
dumpMatrices(mQMatrix);
params.setEncodeTables(mQMatrix, huffmanDcTables, huffmanAcTables);
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Iterator writers = ImageIO.getImageWritersByFormatName("JPEG");
ImageWriter imageWriter = (ImageWriter) writers.next();
ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(outputStream);
imageWriter.setOutput(imageOutputStream);
imageWriter.write(null, new IIOImage(mSourceImage, null, null), params);
mCompressedImageSize = outputStream.size();
try (FileOutputStream fileOutputStream = new FileOutputStream(mOutFileName)) {
fileOutputStream.write(outputStream.toByteArray());
}
mCompressedImage = ImageIO.read(new ByteArrayInputStream(outputStream.toByteArray()));
My guess is that it has something to do with the metadata, but I had no luck finding a solution.
Thanks,
R.
UPDATE: Using a hex viewer I determined that the quantization table (DQT - 0xFF, 0xDB section) isn't getting written to the output file. I'm assuming I have to force it to be written somehow.
UPDATE 2: So after actually debugging execution, what I found is that if the tables are set in the parameters object, then metadata isn't generated for neither the quantization not the Huffman tables. If the metadata is missing, then the tables aren't being written in the file. The thing is I see no way to customize the contents of the metadata.

Very interesting question, and unfortunately non-trivial... Here's what I've found:
First of all, using JPEGImageWriteParam.setEncodeTables(...) won't do. From the JavaDoc:
Sets the quantization and Huffman tables to use in encoding abbreviated streams.
And further from JPEG Metadata Format Specification and Usage Notes:
This ordering implements the design intention that tables should be included in JPEGImageWriteParams only as a means of specifying tables when no other source is available, and this can occur only when writing to an abbreviated stream without tables using known non-standard tables for compression.
I.e., the param option can only be used for writing "abbreviated streams" (customs JPEGs without tables, assuming the tables will be provided when reading back). Conclusion: The only way we can specify tables to be encoded with the JPEG, is to pass it in the meta data.
From the same document mentioned above, the tables in the metadata will be ignored and replaced unless compression mode is MODE_COPY_FROM_METADATA, so we need to specify that.
See the Image Metadata DTD for documentation on the metadata structure. The important parts are the dqt and dht nodes with sub-nodes, and their "User object"s (not to be confused with normal DOM "userData"). We need to update these nodes, with the new tables we want to use.
Here's the code I came up with:
// Obtain qtables
mQMatrix = ...;
// Read source image
ImageInputStream imageInputStream = ImageIO.createImageInputStream(...);
ImageReader reader = ImageIO.getImageReaders(imageInputStream).next();
reader.setInput(imageInputStream);
mSourceImage = reader.read(0);
IIOMetadata metadata = null;
// We need the imageWriter to create the default JPEG metadata
ImageWriter imageWriter = ImageIO.getImageWritersByFormatName("JPEG").next();
if (mQMatrix != null) {
dumpMatrices(mQMatrix);
// Obtain default image metadata data, in native JPEG format
metadata = imageWriter.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(m‌​SourceImage), null);
IIOMetadataNode nativeMeta = (IIOMetadataNode) metadata.getAsTree("javax_imageio_jpeg_image_1.0");
// Update dqt to values from mQMatrix
NodeList dqtables = nativeMeta.getElementsByTagName("dqtable");
for (int i = 0; i < dqtables.getLength(); i++) {
IIOMetadataNode dqt = (IIOMetadataNode) dqtables.item(i);
int dqtId = Integer.parseInt(dqt.getAttribute("qtableId"));
dqt.setUserObject(mQMatrix[dqtId]);
}
// For some reason, we need dht explicitly defined, when using MODE_COPY_FROM_METADATA...
NodeList dhtables = nativeMeta.getElementsByTagName("dhtable");
// Just use defaults for dht
JPEGHuffmanTable[] huffmanDcTables = {JPEGHuffmanTable.StdDCLuminance, JPEGHuffmanTable.StdDCChrominance};
JPEGHuffmanTable[] huffmanAcTables = {JPEGHuffmanTable.StdACLuminance, JPEGHuffmanTable.StdACChrominance};
// Update dht
for (int i = 0; i < dhtables.getLength(); i++) {
IIOMetadataNode dht = (IIOMetadataNode) dhtables.item(i);
int dhtClass = Integer.parseInt(dht.getAttribute("class")); // 0: DC, 1: AC
int dhtId = Integer.parseInt(dht.getAttribute("htableId"));
dht.setUserObject(dhtClass == 0 ? huffmanDcTables[dhtId] : huffmanAcTables[dhtId]);
}
// Merge updated tree back (important!)
metadata.mergeTree("javax_imageio_jpeg_image_1.0", nativeMeta);
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(outputStream);
imageWriter.setOutput(imageOutputStream);
// See http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#tables
JPEGImageWriteParam params = new JPEGImageWriteParam(null);
params.setCompressionMode(metadata == null ? MODE_DEFAULT : MODE_COPY_FROM_METADATA); // Unless MODE_COPY_FROM_METADATA, tables will be created!
imageWriter.write(null, new IIOImage(mSourceImage, null, metadata), params);
imageOutputStream.close();
mCompressedImageSize = outputStream.size();
try (FileOutputStream fileOutputStream = new FileOutputStream(mOutFileName)) {
fileOutputStream.write(outputStream.toByteArray());
}
mCompressedImage = ImageIO.read(new ByteArrayInputStream(outputStream.toByteArray()));

Related

Add Tiff Image Color Profile (sRGB or Adobe 1998) with Java

I searched the web high and low and I can't find a solution for how to add a sRGB or Abobe (1998) color profile to a Tiff Image with Java. There's some examples out there for jpgs and pngs, but they don't apply to tiffs. I have been trying to do it with Commons Imaging and java.awt but not having any luck. Is it possible?
Thanks
Update:
I've made some progress using the TwelveMonkeys imageio-tiff library and the following code:
File file = new File("/Users/user/Desktop/demo/sandal.tif");
BufferedImage image = ImageIO.read(file);
ICC_Profile ip = ICC_Profile.getInstance("/Users/user/Documents/icc/AdobeRGB1998.icc");
ICC_ColorSpace ics = new ICC_ColorSpace( ip );
ColorConvertOp cco = new ColorConvertOp( ics, null );
BufferedImage result = cco.filter(image, null);
ImageIO.write(result, "TIFF", new File("/Users/user/Desktop/demo/sandal2.tif"));
The color profile is applied, but the tiff is flattened and the alpha removed. How can the alpha channel be preserved?
As mentioned in the comments, the code you have should have worked, unless there's something special about your AdobeRGB1998 ICC profile...
The below code works for me, converting the image from sRGB to the AdobeRGB1998 profile. The resulting TIFF file has the correct ICC profile and contains the alpha channel intact (258/BitsPerSample: [8, 8, 8, 8], 277/SamplesPerPixels: 4, 34675/ICCProfile: [...]). The only minor issues I can see, is that the compression is changed from LZW to no compression, and DPI is changed from 300 to 72 (+ XMP metadata is lost).
BufferedImage image = ImageIO.read(new File("C:\\Downloads\\sandal.tif"));
ICC_ColorSpace ics = (ICC_ColorSpace) ColorSpaces.getColorSpace(ColorSpaces.CS_ADOBE_RGB_1998);
ColorConvertOp cco = new ColorConvertOp(ics, null);
BufferedImage result = cco.filter(image, null);
File tempFile = File.createTempFile("test-", ".tif");
System.out.println("tempFile: " + tempFile); // Just to know where to look
ImageIO.write(result, "TIFF", tempFile);
As you can see, the only real difference here is how the ICC profile/color space is obtained.
If you want to preserve the meta data and/or control compression, that is possible too. The below code does basically the same (but preserves the LZW compression and 300dpi), unfortunately it's a bit more verbose:
try (ImageInputStream input = ImageIO.createImageInputStream(new File("C:\\Downloads\\sandal.tif"))) {
ImageReader reader = ImageIO.getImageReaders(input).next();
reader.setInput(input);
IIOImage imageAndMeta = reader.readAll(0, reader.getDefaultReadParam());
reader.dispose();
ICC_ColorSpace ics = (ICC_ColorSpace) ColorSpaces.getColorSpace(ColorSpaces.CS_ADOBE_RGB_1998);
ColorConvertOp cco = new ColorConvertOp(ics, null);
BufferedImage result = cco.filter((BufferedImage) imageAndMeta.getRenderedImage(), null);
imageAndMeta.setRenderedImage(result);
File tempFile = File.createTempFile("test-", ".tif");
System.err.println("tempFile: " + tempFile);
ImageWriter tiffWriter = ImageIO.getImageWritersByFormatName("TIFF").next();
try (ImageOutputStream stream = ImageIO.createImageOutputStream(tempFile)) {
tiffWriter.setOutput(stream);
ImageWriteParam writeParam = tiffWriter.getDefaultWriteParam();
// If you want a specific compression, uncommment these lines
// The default setting is to copy from metadata
// writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
// Allowed compression type values are:
// "None", "CCITT RLE", "CCITT T.4", "CCITT T.6","LZW", "JPEG", "ZLib", "PackBits" and "Deflate"
// writeParam.setCompressionType("PackBits");
tiffWriter.write(null, imageAndMeta, writeParam);
}
tiffWriter.dispose();
}
(for some reason the XMP metadata is still stripped from the output, I believe that is a bug).
Tiling is currently not supported by the writer, but it may be controlled by the writeParam in the future (using the standard API to do so). Your original image isn't tiled, so I guess that is less of a concern.

How to get byte[] from javafx imageView?

How do i get byte[] from javafx image/imageview class? I want to store my image as a Blob into my database.This is the method that i use for it
public PreparedStatement prepareQuery(HSQLDBConnector connector) {
try {
Blob logoBlob = connector.connection.createBlob();
logoBlob.setBytes(0,logo.getImage());//stuck here
for (int i = 0, a = 1; i < data.length; i++, a++) {
connector.prepStatCreateProfile.setString(a, data[i]);
}
//store LOB
connector.prepStatCreateProfile.setBlob(11, logoBlob);
} catch (SQLException ex) {
ex.printStackTrace();
}
return connector.prepStatCreateProfile;
}
Is there a way to convert from my current object (imageview),image) into byte[]?, or shoud i start to think about using other class for my image/ alternatively point to the location with reference and work with paths/urls?
try this one:
BufferedImage bImage = SwingFXUtils.fromFXImage(logo.getImage(), null);
ByteArrayOutputStream s = new ByteArrayOutputStream();
ImageIO.write(bImage, "png", s);
byte[] res = s.toByteArray();
s.close(); //especially if you are using a different output stream.
should work depending on the logo class
you need to specify a format while writing and reading, and as far as I remember bmp is not supported so you will end up with a png byte array on the database
pure java fx solution trace ( == you will have to fill in missing points :)
Image i = logo.getImage();
PixelReader pr = i.getPixelReader();
PixelFormat f = pr.getPixelFormat();
WriteablePixelFromat wf = f.getIntArgbInstance(); //???
int[] buffer = new int[size as desumed from the format f, should be i.width*i.height*4];
pr.getPixels(int 0, int 0, int i.width, i.height, wf, buffer, 0, 0);
Lorenzo's answer is correct, this answer just examines efficiency and portability aspects.
Depending on the image type and storage requirements, it may be efficient to convert the image to a compressed format for storage, for example:
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
ImageIO.write(SwingFXUtils.fromFXImage(fxImage, null), "png", byteOutput);
Blob logoBlob = connector.connection.createBlob();
logoBlob.setBytes(0, byteOutput.toByteArray());
Another advantage of doing a conversion to a common format like png before persisting the image is that other programs which deal with the database would be able to read the image without trying to convert it from a JavaFX specific byte array storage format.

Getting metadata from JPEG in byte array form

I have a jpeg image in the form of a byte array. How can I get the byte array in a form where I can strip the comments node of the metadata?
byte[] myimagedata = ...
ImageWriter writer = ImageIO.getImageWritersBySuffix("jpeg").next();
ImageReader reader = ImageIO.getImageReader(writer);
//Looking for file here but have byte array
reader.setInput(new FileImageInputStream(new File(Byte array cant go here)));
IIOMetadata imageMetadata = reader.getImageMetadata(0);
Element tree = (Element) imageMetadata.getAsTree("javax_imageio_jpeg_image_1.0");
NodeList comNL = tree.getElementsByTagName("com");
IIOMetadataNode comNode;
if (comNL.getLength() == 0) {
comNode = new IIOMetadataNode("com");
Node markerSequenceNode = tree.getElementsByTagName("markerSequence").item(0);
markerSequenceNode.insertBefore(comNode,markerSequenceNode.getFirstChild());
} else {
comNode = (IIOMetadataNode) comNL.item(0);
}
You seem you be (just) asking how to create an ImageInputStream that reads from a byte array. From reading the javadocs, I think this should work:
new MemoryCacheImageInputStream(new ByteArrayInputStream(myimagedata))
The FileImageInputStream class doesn't have a constructor that allows you to read from anything but a file in the file system.
The FileCacheImageInputStream would also be an option, but it involves providing a directory in the file system for temporary caching ... and that seems undesirable in this context.

Java how to set jpg quality

Just wanting to get some code edited so that the output jpg quality isn't the default low quality setting that
try
{
ImageIO.write(var6, "jpg", var7);
}
.....is using currently.
I've looked at some other java examples of setting quality, not being very familiar with Java I'm having trouble understanding how to plug stuff in and rework some examples, that I've seen on using Java to set image quality.
ImageWriteParam iwparam = new JPEGImageWriteParam(Locale.getDefault());
iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwparam.setCompressionQuality(quality);
writer.write(null, new IIOImage(image, null, null), iwparam);
Here is the code I'm trying to get work........
public static String func_74292_a(File par0File, String par1Str, int par2, int par3)
{
File var4 = new File(par0File, "screenshots");
var4.mkdir();
int var5 = par2 * par3;
if (field_74293_b == null || field_74293_b.capacity() < var5)
{
field_74293_b = BufferUtils.createIntBuffer(var5);
field_74294_c = new int[var5];
}
GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
field_74293_b.clear();
GL11.glReadPixels(0, 0, par2, par3, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, field_74293_b);
field_74293_b.get(field_74294_c);
func_74289_a(field_74294_c, par2, par3);
BufferedImage var6 = new BufferedImage(par2, par3, 1);
var6.setRGB(0, 0, par2, par3, field_74294_c, 0, par2);
if (par1Str == null)
{
var7 = func_74290_a(var4);
}
else
{
var7 = new File(var4, par1Str);
}
try
{
ImageIO.write(var6, "jpg", var7);
}
catch (IOException var8)
{
;
}
Thread var7x = new Thread(new ScreenShotHelper$1());
var7x.start();
return "\u00a7aUploading Screenshot....";
}
private static File func_74290_a(File par0File)
{
String var1 = dateFormat.format(new Date()).toString();
int var2 = 1;
while (true)
{
File var3 = new File(par0File, var1 + (var2 == 1 ? "" : "_" + var2) + ".jpg");
if (!var3.exists())
{
return var3;
}
++var2;
}
}
Finally did it with this code ...
try
{
ImageOutputStream ios = ImageIO.createImageOutputStream(var7);
Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");
ImageWriter writer = iter.next();
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(0.85f);
writer.setOutput(ios);
writer.write(null, new IIOImage(var6,null,null),iwp);
writer.dispose();
//ImageIO.write(var6, "jpg", var7);
}
You might want to elaborate on what your actual problem with the code is.
Generally speaking, the second sniplet you were using is (more or less) the correct approach:
1) ImageIO.write(...) uses default values for pretty much everything, it requires no extra configuration.
2) If you want to tweak parameters, e.g. for the compression ratio, you should instead use an ImageWriter. You can obtain a suitable writer for any format (in your case jpg) using ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg");
3) You then set the configuration parameters to be used by the writer on an instance of ImageWriteParam. You could instanciate a JPEGImageWriteParam directly, but if you're just looking to change the compression ratio it is easier to request a default instance using ImageWriteParam param = writer.getDefaultWriteParam();
4) Set the compression quality as shown in the above code snipplet, and set the compression type to explicit accordingly.
5) The call to writer.write(null, new IIOImage(image, null, null), iwparam); basically tells your writer instance to create an image without meta data or embedded thumbnails, containing nothing but your BufferedImage and using the configuration object you created in 3).
I came across a similar problem and the answer was not very clear to me, since at the time i did not have a knowledge on ImageIO, so to the people like me that came across this post i made a example.
try {
// Image to be altered
BufferedImage imagem = ImageIO.read(new File("c://nota.jpg"));
// The output image
File outPutImage = new File("c://nota2.jpg");
// Encapsulate the outPut image
ImageOutputStream ios = ImageIO.createImageOutputStream(outPutImage);
// List of ImageWritre's for jpeg format
Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");
// Capture the first ImageWriter
ImageWriter writer = iter.next();
// define the o outPut file to the write
writer.setOutput(ios);
// Here you define the changes you wanna make to the image
ImageWriteParam iwParam = writer.getDefaultWriteParam();
iwParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwParam.setCompressionQuality(.90f);
// Compression and saving to file the altered image
writer.write(null, new IIOImage(imagem, null, null), iwParam);
writer.dispose();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
If you know of a easier way or you found out something wrong in my comments or code, please let me know in the comments so i can alter.

How can I save a PNG with a tEXt or iTXt chunk from Java?

I am currently using javax.imageio.ImageIO to write a PNG file. I would like to include a tEXt chunk (and indeed any of the chunks listed here), but can see no means of doing so.
By the looks of com.sun.imageio.plugins.png.PNGMetadata it should be possible.
I should be most grateful for any clues or answers.
M.
The solution I struck upon after some decompilation, goes as follows ...
RenderedImage image = getMyImage();
Iterator<ImageWriter> iterator = ImageIO.getImageWritersBySuffix( "png" );
if(!iterator.hasNext()) throw new Error( "No image writer for PNG" );
ImageWriter imagewriter = iterator.next();
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
imagewriter.setOutput( ImageIO.createImageOutputStream( bytes ) );
// Create & populate metadata
PNGMetadata metadata = new PNGMetadata();
// see http://www.w3.org/TR/PNG-Chunks.html#C.tEXt for standardized keywords
metadata.tEXt_keyword.add( "Title" );
metadata.tEXt_text.add( "Mandelbrot" );
metadata.tEXt_keyword.add( "Comment" );
metadata.tEXt_text.add( "..." );
metadata.tEXt_keyword.add( "MandelbrotCoords" ); // custom keyword
metadata.tEXt_text.add( fractal.getCoords().toString() );
// Render the PNG to memory
IIOImage iioImage = new IIOImage( image, null, null );
iioImage.setMetadata( metadata ); // Attach the metadata
imagewriter.write( null, iioImage, null );
I realise this question is long since answered, but if you want to do it without dipping into the "com.sun" hierarchy, here's an quick and very ugly example as I couldn't find this documented anywhere else.
BufferedImage img = new BufferedImage(300, 300, BufferedImage.TYPE_INT_ARGB);
// Create a DOM Document describing the metadata;
// I've gone the quick and dirty route. The description for PNG is at
// [http://download.oracle.com/javase/1.4.2/docs/api/javax/imageio/metadata/doc-files/png_metadata.html][1]
Calendar c = Calendar.getInstance();
String xml = "<?xml version='1.0'?><javax_imageio_png_1.0><tIME year='"+c.get(c.YEAR)+"' month='"+(c.get(c.MONTH)+1)+"' day='"+c.get(c.DAY_OF_MONTH)+"' hour='"+c.get(c.HOUR_OF_DAY)+"' minute='"+c.get(c.MINUTE)+"' second='"+c.get(c.SECOND)+"'/><pHYs pixelsPerUnitXAxis='"+11811+"' pixelsPerUnitYAxis='"+11811+"' unitSpecifier='meter'/></javax_imageio_png_1.0>";
DOMResult domresult = new DOMResult();
TransformerFactory.newInstance().newTransformer().transform(new StreamSource(new StringReader(xml)), domresult);
Document document = dom.getResult();
// Apply the metadata to the image
ImageWriter writer = (ImageWriter)ImageIO.getImageWritersBySuffix("png").next();
IIOMetadata meta = writer.getDefaultImageMetadata(new ImageTypeSpecifier(img), null);
meta.setFromTree(meta.getMetadataFormatNames()[0], document.getFirstChild());
FileOutputStream out = new FileOutputStream("out.png");
writer.setOutput(ImageIO.createImageOutputStream(out));
writer.write(new IIOImage(img, null, meta));
out.close();
Using Java 1.6, I edited Mike's code to
Node document = domresult.getNode();
instead of his line
Document document = dom.getResult();
Moreover, I'd suggest to add a line
writer.dispose()
after the job has been done, so that any resources held by the writer are released.
We do this in the JGraphX project. Download the source code and have a look in the com.mxgraph.util.png package, there you'll find three classes for encoding that we copied from the Apache Batik sources. An example of usage is in com.mxgraph.examples.swing.editor.EditorActions in the saveXmlPng method. Slightly edited the code looks like:
mxPngEncodeParam param = mxPngEncodeParam
.getDefaultEncodeParam(image);
param.setCompressedText(new String[] { "mxGraphModel", xml });
// Saves as a PNG file
FileOutputStream outputStream = new FileOutputStream(new File(
filename));
try
{
mxPngImageEncoder encoder = new mxPngImageEncoder(outputStream,
param);
if (image != null)
{
encoder.encode(image);
}
}
finally
{
outputStream.close();
}
Where image is the BufferedImage that will form the .PNG and xml is the string we wish to place in the iTxt section. "mxGraphModel" is the key for that xml string (the section comprises some number of key/value pairs), obviously you replace that with your key.
Also under com.mxgraph.util.png we've written a really simple class that extracts the iTxt without processing the whole image. You could apply the same idea for the tEXt chunk using mxPngEncodeParam.setText instead of setCompressedText(), but the compressed text section does allow for considerable larger text sections.
Try the Sixlegs Java PNG library (http://sixlegs.com/software/png/).
It claims to have support for all chunk types and does private chunk handling.
Old question, but... PNGJ gives full control for reading and writing PNG chunks

Categories

Resources