I was looking at the ImageConverter class, trying to figure out how to convert a BufferedImage to 8-bit color, but I have no idea how I would do this. I was also searching around the internet and I could find no simple answer, they were all talking about 8 bit grayscale images. I simply want to convert the colors of an image to 8 bit... nothing else, no resizing no nothing. Does anyone mind telling me how to do this.
You can use JAI (Java Advanced Imaging), the official Sun (now Oracle) image library to do that.
The ColorQuantizerDescriptor shows the choice of quantization processes you can apply.
This code snippet from the article "Transparent gifs in Java" at G-Man's Uber Software Engineering Blog works well:
public static void main(String[] args) throws Exception {
BufferedImage src = convertRGBAToIndexed(ImageIO.read(new File("/src.jpg")));
ImageIO.write(src, "gif", new File("/dest.gif"));
}
public static BufferedImage convertRGBAToIndexed(BufferedImage src) {
BufferedImage dest = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_BYTE_INDEXED);
Graphics g = dest.getGraphics();
g.setColor(new Color(231, 20, 189));
// fill with a hideous color and make it transparent
g.fillRect(0, 0, dest.getWidth(), dest.getHeight());
dest = makeTransparent(dest, 0, 0);
dest.createGraphics().drawImage(src, 0, 0, null);
return dest;
}
public static BufferedImage makeTransparent(BufferedImage image, int x, int y) {
ColorModel cm = image.getColorModel();
if (!(cm instanceof IndexColorModel))
return image; // sorry...
IndexColorModel icm = (IndexColorModel) cm;
WritableRaster raster = image.getRaster();
int pixel = raster.getSample(x, y, 0); // pixel is offset in ICM's palette
int size = icm.getMapSize();
byte[] reds = new byte[size];
byte[] greens = new byte[size];
byte[] blues = new byte[size];
icm.getReds(reds);
icm.getGreens(greens);
icm.getBlues(blues);
IndexColorModel icm2 = new IndexColorModel(8, size, reds, greens, blues, pixel);
return new BufferedImage(icm2, raster, image.isAlphaPremultiplied(), null);
}
You can use the convert8 method in the ConvertUtil class.
For details have a look here.
Related
I have a large PNG image (600x600) and my application makes the image opaque and writes out the file. The problem is that the performance with ImageIO is terrible. Are there any other alternatives? I require the image to be opaque. Below is what I am doing:
BufferedImage buf = ImageIO.read(localUrl);
float[] scales = {1f, 1f, 1f, 1f}; // R, G, B, A
float[] offsets = {0f, 0f, 0f, 1f}; // R, G, B, A
RescaleOp rescaler = new RescaleOp(scales, offsets, null);
BufferedImage opaque = rescaler.filter(buf, null);
File outputfile = new File(localUrl.getPath());
ImageIO.write(opaque, "png", outputfile);
Using a RescaleOp isn't entirely necessary here if you just want to get rid of transparency. A simpler solution would be drawing the image on a background like so:
Color bgColor = Color.WHITE;
BufferedImage foreground = ImageIO.read(localUrl);
int width = foreground.getWidth();
int height = foreground.getHeight();
BufferedImage background = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = background.createGraphics();
g.setColor(bgColor);
g.fillRect(0, 0, width, height);
g.drawImage(foreground, 0, 0, null);
g.dispose();
File outputfile = new File(localUrl.getPath());
ImageIO.write(background, "png", outputfile);
This seems like a simpler method of doing things and would probably require less processing power, but I doubt there would be a huge difference. If you're not satisfied with the speed the image can be read / written from the hard drive, there's little you can do to speed that up.
With PNGJ:
private static void removeAlpha(File file1,File file2) {
PngReaderByte reader = new PngReaderByte(file1);
ImageInfo info = reader.imgInfo;
PngWriter writer = new PngWriter(file2,info);
writer.setFilterPreserve(true);
writer.setCompLevel(6);
writer.copyChunksFrom(reader.getChunksList(), ChunkCopyBehaviour.COPY_ALL_SAFE);
if( info.bitDepth != 8 ||info.channels!=4) throw new RuntimeException("expected 8bits RGBA ");
while(reader.hasMoreRows()) {
ImageLineByte line = reader.readRowByte();
byte [] buf = line.getScanlineByte();
for(int i=0,j=3;i<info.cols;i++,j+=4)
buf[j]=(byte)255;
writer.writeRow(line);
}
reader.end();
writer.end();
}
I'm not sure if this would enhance the perfomance. Bear in mind, also that (contrarily to Parker Hoyes' answer) this simply kills the alpha channel, but it does not blend with some background color (hence the "original" color will appear in the previously-transparent now-opaque regions).
How to blur a portion of an image, to hide some privates parts like credit card informations.
I try to use ConvolveOp.class like :
float[] matrix = new float[400];
for (int i = 0; i < 400; i++)
matrix[i] = 1.0f/500.0f;
BufferedImage sourceImage = (BufferedImage) image; ;
BufferedImage destImage = null ;
BufferedImageOp op = new ConvolveOp( new Kernel(20, 20, matrix), ConvolveOp.EDGE_NO_OP, null );
BufferedImage blurredImage = op.filter(sourceImage, destImage);
it seems to work, except that the image is completely blurred.
In the case you want to focus on the application and not on the specifics of image processing, you can use an image processing framework like Marvin. Thus, you can do more with less code.
Input image:
Output image:
Source code:
import static marvin.MarvinPluginCollection.*;
public class PortionBlur {
public PortionBlur(){
// 1. Load image
MarvinImage image = MarvinImageIO.loadImage("./res/credit_card.jpg");
// 2. Create masks for each blurred region
MarvinImageMask mask1 = new MarvinImageMask(image.getWidth(), image.getHeight(), 38,170,345,24);
MarvinImageMask mask2 = new MarvinImageMask(image.getWidth(), image.getHeight(), 52,212,65,24);
MarvinImageMask mask3 = new MarvinImageMask(image.getWidth(), image.getHeight(), 196,212,65,20);
MarvinImageMask mask4 = new MarvinImageMask(image.getWidth(), image.getHeight(), 38,240,200,20);
// 3. Process Image with each mask
GaussianBlur gaussianBlur = new GaussianBlur();
gaussianBlur.load();
gaussianBlur.attributes.set("radius",15);
gaussianBlur.process(image.clone(), image, mask1);
gaussianBlur.process(image.clone(), image, mask2);
gaussianBlur.process(image.clone(), image, mask3);
gaussianBlur.process(image.clone(), image, mask4);
// 4. Save the final image
MarvinImageIO.saveImage(image, "./res/credit_card_out.jpg");
}
public static void main(String[] args) {
new PortionBlur();
System.exit(0);
}
}
Gaussian blur algorithm source code:
https://github.com/gabrielarchanjo/marvinproject/blob/master/marvinproject/dev/MarvinPlugins/src/org/marvinproject/image/blur/gaussianBlur/GaussianBlur.java
I don't know whether this can be done by changing the matrix values, but this should definitely be possible by filtering a subimage, since, according to the BufferedImage.getSubimage() documentation:
The returned BufferedImage shares the same data array as the original image.
So the original BufferedImage should change with code like this:
BufferedImage image = /* ... */;
BufferedImage subImage = image.getSubimage(10, 20, 30, 40); // x, y, width, height
new ConvolveOp(new Kernel(20, 20, matrix), ConvolveOp.EDGE_NO_OP, null).filter(subImage, subImage);
I didn't test this though, and I can imagine that filter doesn't work as expected if source and destination are the same, in which case you could use a copy of the subimage, using the solution from this question:
BufferedImage image = /* ... */;
BufferedImage dest = image.getSubimage(10, 20, 30, 40); // x, y, width, height
ColorModel cm = dest.getColorModel();
BufferedImage src = new BufferedImage(cm, dest.copyData(dest.getRaster().createCompatibleWritableRaster()), cm.isAlphaPremultiplied(), null).getSubimage(0, 0, dest.getWidth(), dest.getHeight());
new ConvolveOp(new Kernel(20, 20, matrix), ConvolveOp.EDGE_NO_OP, null).filter(src, dest);
After that, continue working with image (not subImage, src or dest!)
I want to invert and gray scale an image. Here is my original image:
Here is the final result I want to achieve (produced with Paint.NET):
However using some (basic?) Java code found on Internet, I only get the picture below:
Here is the code I used:
private static final byte[] invertTable;
static {
invertTable = new byte[256];
for (int i = 0; i < 256; i++) {
invertTable[i] = (byte) (255 - i);
}
}
private static BufferedImage grayScale(BufferedImage source) {
ColorConvertOp grayScale = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
return grayScale.filter(source,null);
}
private static BufferedImage invertImage(final BufferedImage src) {
final int w = src.getWidth();
final int h = src.getHeight();
final BufferedImage dst = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
final BufferedImageOp invertOp = new LookupOp(new ByteLookupTable(0, invertTable), null);
return invertOp.filter(src, dst);
}
// ...
BufferedImage sourceImage = ...
BufferedImage convertedImage = grayScale(invertImage(sourceImage));
How can I improve the above code?
Using Imgproc and Core, I did
Mat src = new Mat();
Mat gray = new Mat();
src = Highgui.imread("...");
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
Core.bitwise_not(gray, gray);
Highgui.imwrite("...", gray);
and got something closer, though not identical:
In the title it says OpenCV but there's a tag for BoofCV. So here's how to do it in BoofCV:
BufferedImage orig = UtilImageIO.loadImage("filename");
ImageUInt8 gray = ConvertBufferedImage.convertFrom(orig, (ImageUInt8) null);
ImageUInt8 inverted = new ImageUInt8(gray.width,gray.height);
GrayImageOps.invert(gray,255,inverted);
While I'm not familiar with ColorConvertOp, I would suspect that it is converting the color image to greyscale in a different way then Paint.NET is. There is no correct way.
Take a look at the yellow. Should yellow be a dark or light grey? It looks like Paint.NET thinks it is light and the ColorConvertOp thinks it is dark.
Try doing the conversion yourself by just averaging the red/green/blue.
(red+green+blue)/3
I have an object which has many bufferedimages in it, I want to create a new object copying all the bufferedimages into the new object, but these new images may be altered and i don't want the original object images to be altered by altering the new objects images.
is that clear?
Is this possible to do and can anyone suggest a good way to do it please?
I have thought of getSubImage but read somewhere that any changes to the subimage are relected back to the parent image.
I just want to be able to get a fresh entirely separate copy or clone of a BufferedImage
Something like this?
static BufferedImage deepCopy(BufferedImage bi) {
ColorModel cm = bi.getColorModel();
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
WritableRaster raster = bi.copyData(null);
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
}
I do this:
public static BufferedImage copyImage(BufferedImage source){
BufferedImage b = new BufferedImage(source.getWidth(), source.getHeight(), source.getType());
Graphics g = b.getGraphics();
g.drawImage(source, 0, 0, null);
g.dispose();
return b;
}
It works fairly well and it is simple to use.
The previously mentioned procedure fails when applied to sub images. Here is a more complete solution:
public static BufferedImage deepCopy(BufferedImage bi) {
ColorModel cm = bi.getColorModel();
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
WritableRaster raster = bi.copyData(bi.getRaster().createCompatibleWritableRaster());
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
}
Another way is to use the Graphics2D class to draw the image onto a new blank image. This doesn't really clone the image, but it results in a copy of the image being produced.
public static final BufferedImage clone(BufferedImage image) {
BufferedImage clone = new BufferedImage(image.getWidth(),
image.getHeight(), image.getType());
Graphics2D g2d = clone.createGraphics();
g2d.drawImage(image, 0, 0, null);
g2d.dispose();
return clone;
}
I know that this question is pretty old, but for future visitors, here's the solution I'd use:
Image oldImage = getImage();
Image newImage = oldImage.getScaledInstance(oldImage.getWidth(null), oldImage.getHeight(null), Image.SCALE_DEFAULT);
Please correct me if changing the just obtained newImage also affects the original image in any way.
--> Javadoc for getScaledInstance
--> Javadoc for SCALE_DEFAULT (the other constants are listed just below that one)
Class BufferedImage does not implement the Cloneable interface. Thus the clone method is not overriden. Here's an alternative for a deep copy technique:
Java Tip 76: An alternative to the deep copy technique
The following solution using arraycopy is about 3-4 times faster than the accepted answer:
public static BufferedImage copyImage(BufferedImage source){
BufferedImage bi = new BufferedImage(source.getWidth(), source.getHeight(), source.getType());
byte[] sourceData = ((DataBufferByte)source.getRaster().getDataBuffer()).getData();
byte[] biData = ((DataBufferByte)bi.getRaster().getDataBuffer()).getData();
System.arraycopy(sourceData, 0, biData, 0, sourceData.length);
return bi;
}
By the way, the answers using Graphics2D provide similarly good results.
Here is a solution I wrote many years ago for JFreeChart.
It will also copy any Properties which may be present in the BufferedImage.
I believe it was tested with all known Colour Models (Image Types).
Whether it will work with Image Type 0 discussed by #JoséRobertoAraújoJúnior I don't know.
But: Image Type 0 is invalid & should not occur.
/**
* Copied from
* <a href=https://github.com/jfree/jfreechart/blob/master/src/main/java/org/jfree/chart/util/PaintAlpha.java>JFreeChart PaintAlpha</a>
*
* #param srcImage
* #return
*/
public static BufferedImage cloneImage(final BufferedImage srcImage) {
final WritableRaster srcRaster = srcImage.getRaster();
final WritableRaster dstRaster = srcRaster.createCompatibleWritableRaster();
/*
* This is the code that actually COPIES the pixels...
*/
dstRaster.setRect(srcRaster);
/*
* Images hardly ever have Properties, but we copy them anyway...
*/
final String[] propNames = srcImage.getPropertyNames();
final Hashtable<String, Object> props;
if (propNames == null) {
props = null;
} else {
props = new Hashtable<>();
for (int i = 0; i < propNames.length; i++) {
props.put(propNames[i], srcImage.getProperty(propNames[i]));
}
}
/*
* That's it folks! Return the new clone...
*/
return new BufferedImage(srcImage.getColorModel(), dstRaster, srcImage.isAlphaPremultiplied(), props);
}
I have made this solution using two functions it works on every possible image, even image by camcorder of laptop, I was facing problem that after croping image through camera it was not working but this solution will work
static BufferedImage deepCopy(BufferedImage bi)
{
try
{
ColorModel cm = bi.getColorModel();
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
WritableRaster raster = bi.copyData(bi.getRaster().createCompatibleWritableRaster());
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
}
catch(Exception ex)
{
}
try{
BufferedImage b = new BufferedImage(bi.getWidth(), bi.getHeight(), bi.getType());
Graphics g = b.getGraphics();
g.drawImage(bi, 0, 0, null);
g.dispose();
return b;
}
catch(Exception ex){
}
return null;
}
I am trying to write out a png file from a java.awt.image.BufferedImage. Everything works fine but the resulting png is a 32-bit file.
Is there a way to make the png file be 8-bit? The image is grayscale, but I do need transparency as this is an overlay image. I am using java 6, and I would prefer to return an OutputStream so that I can have the calling class deal with writing out the file to disk/db.
Here is the relevant portion of the code:
public static ByteArrayOutputStream createImage(InputStream originalStream)
throws IOException {
ByteArrayOutputStream oStream = null;
java.awt.Image newImg = javax.imageio.ImageIO.read(originalStream);
int imgWidth = newImg.getWidth(null);
int imgHeight = newImg.getHeight(null);
java.awt.image.BufferedImage bim = new java.awt.image.BufferedImage(imgWidth,
imgHeight, java.awt.image.BufferedImage.TYPE_INT_ARGB);
Color bckgrndColor = new Color(0x80, 0x80, 0x80);
Graphics2D gf = (Graphics2D)bim.getGraphics();
// set transparency for fill image
gf.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f));
gf.setColor(bckgrndColor);
gf.fillRect(0, 0, imgWidth, imgHeight);
oStream = new ByteArrayOutputStream();
javax.imageio.ImageIO.write(bim, "png", oStream);
oStream.close();
return oStream;
}
The build in imageio png writer will write 32bit png files on all the platforms I have used it on, no matter what the source image is. You should also be aware that many people have complained that the resulting compression is much lower than what is possible with the png format. There are several independent png libraries available that allow you to specify the exact format, but I don't actually have any experience with any of them.
I found the answer as to how to convert RGBA to Indexed here: http://www.eichberger.de/2007/07/transparent-gifs-in-java.html
However, the resulting 8-bit png file only has 100% or 0% transparency. You could probably tweak the IndexColorModel arrays, but we have decided to make the generated file (what was an overlay mask) into an underlay jpg and use what was the static base as the transparent overlay.
It is an interesting question... It is late, I will experiment tomorrow. I will first try and use a BufferedImage.TYPE_BYTE_INDEXED (perhaps after drawing) to see if Java is smart enough to generate an 8bit PNG.
Or perhaps some image library can allow that.
[EDIT] Some years later... Actually, I made the code at the time, but forgot to update this thread... I used the code pointed at by Kat, with a little refinement on the handling of transparency, and saving in PNG format instead of Gif format. It works in making a 8-bit PNG file with all-or-nothing transparency.
You can find a working test file at http://bazaar.launchpad.net/~philho/+junk/Java/view/head:/Tests/src/org/philhosoft/tests/image/AddTransparency.java
using my ImageUtil class.
Since the code isn't that big, for posterity sake, I post it here, without the JavaDoc to save some lines.
public class ImageUtil
{
public static int ALPHA_BIT_MASK = 0xFF000000;
public static BufferedImage imageToBufferedImage(Image image, int width, int height)
{
return imageToBufferedImage(image, width, height, BufferedImage.TYPE_INT_ARGB);
}
public static BufferedImage imageToBufferedImage(Image image, int width, int height, int type)
{
BufferedImage dest = new BufferedImage(width, height, type);
Graphics2D g2 = dest.createGraphics();
g2.drawImage(image, 0, 0, null);
g2.dispose();
return dest;
}
public static BufferedImage convertRGBAToIndexed(BufferedImage srcImage)
{
// Create a non-transparent palletized image
Image flattenedImage = transformTransparencyToMagenta(srcImage);
BufferedImage flatImage = imageToBufferedImage(flattenedImage,
srcImage.getWidth(), srcImage.getHeight(), BufferedImage.TYPE_BYTE_INDEXED);
BufferedImage destImage = makeColorTransparent(flatImage, 0, 0);
return destImage;
}
private static Image transformTransparencyToMagenta(BufferedImage image)
{
ImageFilter filter = new RGBImageFilter()
{
#Override
public final int filterRGB(int x, int y, int rgb)
{
int pixelValue = 0;
int opacity = (rgb & ALPHA_BIT_MASK) >>> 24;
if (opacity < 128)
{
// Quite transparent: replace color with transparent magenta
// (traditional color for binary transparency)
pixelValue = 0x00FF00FF;
}
else
{
// Quite opaque: get pure color
pixelValue = (rgb & 0xFFFFFF) | ALPHA_BIT_MASK;
}
return pixelValue;
}
};
ImageProducer ip = new FilteredImageSource(image.getSource(), filter);
return Toolkit.getDefaultToolkit().createImage(ip);
}
public static BufferedImage makeColorTransparent(BufferedImage image, int x, int y)
{
ColorModel cm = image.getColorModel();
if (!(cm instanceof IndexColorModel))
return image; // No transparency added as we don't have an indexed image
IndexColorModel originalICM = (IndexColorModel) cm;
WritableRaster raster = image.getRaster();
int colorIndex = raster.getSample(x, y, 0); // colorIndex is an offset in the palette of the ICM'
// Number of indexed colors
int size = originalICM.getMapSize();
byte[] reds = new byte[size];
byte[] greens = new byte[size];
byte[] blues = new byte[size];
originalICM.getReds(reds);
originalICM.getGreens(greens);
originalICM.getBlues(blues);
IndexColorModel newICM = new IndexColorModel(8, size, reds, greens, blues, colorIndex);
return new BufferedImage(newICM, raster, image.isAlphaPremultiplied(), null);
}
}
Thanks for responding, I was going to try an TYPE_BYTE_INDEXED with an IndexColorModel and may still but if ImageIO writes out 32-bit regardless it appears that I may be wasting my time there.
The image I am trying to write out can be very large (up to 8000x4000) but is just a simple mask for the image underneath, so will only have a ~30% transparent gray and a 100% transparent cutout. I would use a GIF but IE6 seems to have trouble with displaying one that large.
It only gets generated once and in an internal set-up type screen, so performance isn't an issue either, but it does have to be done within the java code and not by an offline utility.
The libraries that you specified might be used to transform it while writing... I am going to go check that out.
If anyone has a better way, please let me know!!
Thanks!