I have a color indexed TIFF image (8-bits) and I want to convert it to a RGB 24-bits image (not indexed). What would be the way to do that?
I'm using JMagick. In a weird way, it works fine for indexed 8-bits images that are grayscale when i use:
image.transformRgbImage(info.getColorspace());
even if the image, though not indexed any more, is still 8-bits after that, which is lucky as it is grayscale and should actually be 8-bits. The weird stuff is that the transformRgbImage() performs that although I'd rather expect it to convert the image to a 24-bits one. Anyway...
The same way doesn't work for a color indexed 8-bits image. I just don't know how to use the JMagick API to achieve that goal. I tried setting:
image.setDepth(24);
or:
info.setDepth(24);
but both result in an EXCEPTION_ACCESS_VIOLATION. When I set:
info.setDepth(32);
no exception is raised, 1) but the image is 32-bits, which shouldn't be, and 2) it is all black (1 unique color). Why does the setDepth(24) raises such an exception?? How should I do?
Thanks in advance for your help.
I dont know about jmagick, but generally once you created an image object its properties are fixed (size and color model).
You don't change an images properties, you create a new image with the desired target properties and paint your original image into the new image. In plain core java you would simply do it like this:
public BufferedImage toRGB(Image i) {
BufferedImage rgb = new BufferedImage(i.getWidth(null), i.getHeight(null), BufferedImage.TYPE_INT_RGB);
rgb.createGraphics().drawImage(i, 0, 0, null);
return rgb;
}
Related
I need to read and process a large number of PNG files that are grayscale. By that I mean that if they are opened in either Photoshop or GIMP, the image mode is Grayscale - not an RGB image with grayscale values.
ImageIO does not seem to achieve this. It appears to treat all image files as sRGB. This mangles grayscale values. I need to read and process these PNG files where (in my code) each pixel has exactly the same value as if I had opened the grayscale file in Photoshop or GIMP. Does anybody know of some open source software that can achieve this, please? Or better how to achieve this using ImageIO.
Additional Information:
I am using getRGB() on a BufferedImage. The underlying pixel in the image file is 0x86. I understand that this does not necessarily correspond to an ARGB pixel containing 0xFF868686, as this depends upon luminance/gamma. However, in the absence of a getter with a gamma type argument, I would have expected the default mapping to be to ARGB=0xFF868686. If I use GIMP or Photoshop to convert a grayscale image containing a pixel with the value of 0x86 to RGB then the pixel becomes 0xFF868686. This is the obvious default.
However, ImageIO seems to use a weird gamma (whether you like it or not) with grayscale image files that makes the grayscale pixels very, very light after mapping to ARGB. In this case, 0x86 maps to 0xFFC0C0C0. This is not only very light, it can also result in considerable data loss as many grayscale values can be mapped to fewer ARGB values. The only time that this distortion will not result in data loss is for very dark grayscale images. An appropriate Gamma is context dependent, different physical media will distort luminance differently. However, in the absence of a context, the mapping: 0x86 --> 0xFF868686 makes most sense - witness the choices made for GIMP and Photoshop.
Leaving the getRGB() issue to one side, having loaded the grayscale image (using ImageIO.read( imageFile )), the getType() method of BufferedImage returns Type=0 (Custom) and not Type=10 (TYPE_BYTE_GRAY) as I would have expected.
In short, ImageIO does not seem to provide a nice and simple high level way of reading and manipulating existing grayscale images. I had hoped not to have to mess around under the covers with Rasters, ICC, sampling etc. Nor do I want to have to physically convert all the grayscale image files to RGB. All I wanted was an API load() method for BufferedImage that works just like open file does in GIMP or Photoshop. I have not been able to achieve this. I am hoping that this is my ignorance and not a limitation of Java ImageIO.
Possible Solution:
After digging around I have the following to offer as a possible technique for accessing the underlying grayscale values:
final File imageFile = new File( "test.png" );
final BufferedImage image = ImageIO.read( imageFile );
// --- Confirm that image has ColorSpace Type is GRAY and PixelSize==16
final Raster raster = image.getData();
// --- Confirm that: raster.getTransferType() == DataBuffer.TYPE_BYTE
for( int x=0, xLimit=image.getWidth(); x < xLimit; x++ ) {
for( int y=0, yLimit=image.getHeight(); y < yLimit; y++ ) {
final Object dataObject = raster.getDataElements( x, y, null );
// --- Confirm that dataObject is instance of byte[]
final byte[] pixelData = (byte[]) dataObject;
// --- Confirm that: pixelData.length == 2
final int grayscalePixelValue = pixelData[0] & 0xFF;
final int grayscalePixelAlpha = pixelData[1] & 0xFF;
// --- Do something with the grayscale pixel data
}
}
The javadoc is not great, so I cannot guarantee that this is correct, but it seems to work for me.
In case you want to try a third party (mine) lib: https://github.com/leonbloy/pngj/
If you are certain that the image is plain grayscale (8 bits, no alpha, no palette, no profile), it's quite simple:
PngReaderByte pngr = new PngReaderByte(new File(filename)); //
if (pngr.imgInfo.channels!=1 || pngr.imgInfo.bitDepth != 8 || pngr.imgInfo.indexed)
throw new RuntimeException("This method is for gray images");
for (int row = 0; row < pngr.imgInfo.rows; row++) {
ImageLineByte line = pngr.readRowByte();
byte [] buf = line.getScanlineByte();
// do what you want
}
pngr.end();
Java's ImageIO is known to be broken on images with a grayscale palette.
Java ImageIO Grayscale PNG Issue
javax.imageio.ImageIO reading incorrect RGB values on grayscale images
My batch jpg resizer works with color images, but grayscale ones become washed out
Wrong brightness converting image to grayscale in Java
Oracle: JDK-5051418 : Grayscale TYPE_CUSTOM BufferedImages are rendered lighter than TYPE_BYTE_GRAY
Oracle: JDK-6467250 : BufferedImage getRGB(x,y) problem
Im using xuggler to put a transparent mask in a video.
Im trying to open a valid png using the ImageIO.read(), but when it renders, theres always a white backgroun in my picture.
This is my code for reading.
url = new URL(stringUrl);
imagem = ImageIO.read(url);
boolean hasAlpha = imagem.getColorModel().hasAlpha();
This boolean is always false.
And in Xuggler when i make the render
mediaReader
.setBufferedImageTypeToGenerate(BufferedImage.TYPE_3BYTE_BGR);
What im doing wrong?
The "problem" with your original image file, is that it is a 24 bit RGB (3 channel) PNG, with a tRNS chunk.
The tRNS chunk specifies a color (255, 255, 255, or white, in this case) that is to be replaced with fully transparent pixels. In effect, applying this transparency works kind of like a bit mask, where all pixels are either fully transparent or fully opaque. However, being an "ancillary" (optional, or non-critical) chunk according to the PNG specificiation, PNG decoders may choose to ignore this information.
Unfortunately, it seems like the default PNGImageReader (the "PNG plugin") for ImageIO does not apply this bit mask, and instead keeps the image in fully opaque RGB (which is still spec compliant behavior).
Your second image file is a 32 bit RGBA (4 channels incl. alpha). In this case, any reader must (and does) decode all channels, including the full 8 bit alpha channel. ImageIO's PNGImageReader does decode this with the expected transparency.
PS: It is probably possibly to "fix" the transparency from your first image, using the meta data.
The meta data for your image contains the following information in the native PNG format:
<javax_imageio_png_1.0>
....
<tRNS>
<tRNS_RGB red="255" green="255" blue="255"/>
</tRNS>
</javax_imageio_png_1.0>
After parsing this, you could convert the image, by first creating a new 4 channel image (like TYPE_4BYTE_ABGR), copying the R, G and B channels to this new image, and finally set the alpha channel to either 0 for (all pixels that match the transparent color), or 255 (for all others).
PPS: You probably want to perform these operations directly on the Rasters, as using the BufferedImage.getRGB(..)/setRGB(..) methods will convert the values to an sRGB color profile, which may not exactly match the RGB value from the tRNS chunk.
There you go! :-)
I want to try up scale image up to 3 times.
For example,
Up Scaled Image
I am using this library for Image Resizing.
The following code snipped does the trick,
public static BufferedImage getScaledSampledImage(BufferedImage img,
int targetWidth, int targetHeight, boolean higherQuality) {
ResampleOp resampleOp = new ResampleOp(targetWidth, targetHeight);
resampleOp.setUnsharpenMask(AdvancedResizeOp.UnsharpenMask.Normal);
BufferedImage rescaledImage = resampleOp.filter(img, null);
return rescaledImage;
}
You can see there a resized images are lower quality. I want that I can up scale images at least 3 times than the original image without quality lost.
Is it Pposible? Should I need to change existing library ?
Thanks
The fact is that when you scale an image up, it has to create data where there was none. (Roughly speaking). To do this, you have to interpolate between the existing pixels to fill in the new ones. You may be able to get better results by trying different kinds of interpolation - but the best method will be different for different kinds of images.
Since your sample image is just squares, nearest-neighbour interpolation will likely give you the best results. If you are scaling up an image of scenery or maybe a portrait you'll need a more clever algorithm.
The result will rarely look perfect, and it's best to start with a larger image if possible!
Take a look here to get an understanding of the problem. http://en.wikipedia.org/wiki/Image_scaling#Scaling_methods
I'm using JAI to add a "border" to an image. Eg
ParameterBlock pb = new ParameterBlock();
pb.addSource(r);
pb.add(leftPad);
pb.add(rightPad);
pb.add(topPad);
pb.add(bottomPad);
pb.add(new BorderExtenderConstant(consts));
r = JAI.create("border", pb);
The value of 'consts' above depends on the ColorModel. When using ComponentColorModel each pixel has its own color so I don't have to mess with a palette. When an image has a palette (GIFs, PNGs, ...) the ColorModel is IndexColorModel.
When IndexColorModel is being used then 'consts' is a double[] array, with the size of one. The value in the array is the index in the color palette.
I have found how to modify the palette by creating a new IndexColorModel but the logic I would have to code would be insane! (Eg. How many colors I can have in the palette depends on many factors. Additionally if I need to remove a color from the palette in order to add the new one, I would need logic that determines which color would be best to remove and then modify all pixels previously referencing that color -- wow, that's a lot of code!)
So, my question is, how does one add a color to the palette? Is there an existing library? Or should I be using something from ioimage? (To be honest I'm a little confused where jai "ends" and ioimage "starts".)
Also, side question, for some reason my test images that only have about 10 colors in the palette are read in as having 256 colors. When I then save the image with jai all the 256 colors are saved (11 through 255 are all black). Any idea why it's doing this?
Thanks!
David
The best solution I've been able to come up with to my question above is to convert the image from IndexColorModel to ComponentColorModel. (Each pixel of a ComponentColorModel specifies its own color so there is no pallet you have to work with -- you can easily use any color you want.)
I cam up with this simple solution after reading http://java.sun.com/products/java-media/jai/forDevelopers/jaifaq.html#palette
This is what I am doing after reading an image in:
if(image.getColorModel() instanceof IndexColorModel) {
IndexColorModel icm = (IndexColorModel)image.getColorModel();
byte[][] data = new byte[4][icm.getMapSize()];
icm.getReds(data[0]);
icm.getGreens(data[1]);
icm.getBlues(data[2]);
icm.getAlphas(data[3]);
LookupTableJAI lut = new LookupTableJAI(data);
image = JAI.create("lookup", image, lut);
}
Once doing the manipulation you can then covert the image back. I haven't spent the time to figure that out. If someone else wants to figure it out you might want to read this: http://www.java.net/node/675577
I'm trying to read, rescale and save images within a Servlet. That's the relevant code:
BufferedImage image = ImageIO.read(file);
BufferedImage after = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
AffineTransform at = AffineTransform.getScaleInstance(factor, factor);
AffineTransformOp scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
after = scaleOp.filter(image, null);
ImageIO.write(after, "JPG", file));
The original file is a plain RGB-Jpeg, but when I open and save the file it comes out as a CMYK-Jpeg. That happens even if i don't rescale the image, just opening and closing the image causes the problem.
When I open PNGs or GIFs everything is fine. Does anybody know what to do here? I would expect ImageIO's read-Method to retain the original colorspace.
If there's another way comfortable way of reading jpeg's?
Thanks for any suggestions!
You create after and then overwrite it with scaleOp.filter. Is this correct? So your after image may not be RGB even though you think it is? If you want after to be RGB then you may need to 'draw' image onto after before you do the transform.
I have had the same problem, and found this page.
I tried the suggestion above of creating a BufferedImage with the right type and using it as the after image instead of null in the filter call; and that did indeed resolve the problem.
ImageIO.read ignores all embedded metadata, including an embedded color profile, which defines how RBG values map to physical devices such as screens or printers.
You could read the metadata separately and pass it to ImageIO.write, but it's easier to just convert the image to the (default) sRGB color space and ignore the metadata.
If you don't mind losing the metadata, replace
after = scaleOp.filter(image, null);
with
after = scaleOp.filter(image, after);
From the documentation of AffineTransformOp.filter:
If the color models for the two images do not match, a color conversion
into the destination color model is performed.