PNG - Is it possible to reduce the palette using Java 2D? - java

If I have a PNG image opened as a BufferedImage, is it possible to reduce the palette in the PNG image so that there is less colour (less bits per pixel / colour depth)?
For example, if you look at Colour depth in Wikipedia, I would like to use 16 colours in my PNG image (3rd image down the right hand side).
If it's not possible with Java 2D, is there a library out there that will allow me to do this effectively?

I think Martijn Courteaux was right:
Here is example implementation:
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ImagingTest2 {
public static void main(String[] args) throws IOException {
BufferedImage src = ImageIO.read(new File("in.png")); // 71 kb
// here goes custom palette
IndexColorModel cm = new IndexColorModel(
3, // 3 bits can store up to 8 colors
6, // here I use only 6
// RED GREEN1 GREEN2 BLUE WHITE BLACK
new byte[]{-100, 0, 0, 0, -1, 0},
new byte[]{ 0, -100, 60, 0, -1, 0},
new byte[]{ 0, 0, 0, -100, -1, 0});
// draw source image on new one, with custom palette
BufferedImage img = new BufferedImage(
src.getWidth(), src.getHeight(), // match source
BufferedImage.TYPE_BYTE_INDEXED, // required to work
cm); // custom color model (i.e. palette)
Graphics2D g2 = img.createGraphics();
g2.drawImage(src, 0, 0, null);
g2.dispose();
// output
ImageIO.write(img, "png", new File("out.png")); // 2,5 kb
}
}

Create a new BufferedImage with the lower palette and use createGraphic() to acquire a Graphics2D object. Draw the original image on the graphics. dispose() the graphics and here you are.
BufferedImage img = new BufferedImage(orig.getWidth(), orig.getHeight(),
BufferedImage.TYPE_USHORT_555_RGB);

Related

Drawing to a BufferedImage sometimes, but consistently, yields wrong colors

I've written a program to modify images.
First, I get the image, and get its drawing context like this:
BufferedImage image;
try {
image = ImageIO.read(inputFile);
} catch (IOException ioe) { /* exception handling ... */ }
Graphics g = image.createGraphics();
And then I modify the image like this:
for (int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
g.setColor( /* calculate color ... */ );
g.fillRect(x, y, 1, 1);
}
}
After I've finished modifying the image, I save the image like this:
try {
ImageIO.write(image, "PNG", save.getSelectedFile());
} catch (IOException ioe) { /* exception handling ... */ }
Now most of the time this works just fine.
However, when I tried recoloring this texture
to this
I get this instead:
Inside the debugger, though, the Graphics's color is the shade of pink I want it to be.
The comments seem to suggest that the image the user opens might have some color limitations, and since I'm drawing to the same image I'm reading from, my program has to abide by these limitations. The example image seems to be pretty grayscale-y, and apparently its bit depth is 8 bit. So maybe the pink I'm drawing on it is converted to grayscale, because the image has to stay 8-bit?
As suggested in the comments, the main problem here indeed is the wrong color model. When you load the original image, and print some information about it...
BufferedImage image = ImageIO.read(
new URL("https://i.stack.imgur.com/pSUFR.png"));
System.out.println(image);
it will say
BufferedImage#5419f379: type = 13 IndexColorModel: #pixelBits = 8 numComponents = 3 color space = java.awt.color.ICC_ColorSpace#7dc7cbad transparency = 1 transIndex = -1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 128 height = 128 #numDataElements 1 dataOff[0] = 0
The IndexColorModel does not necessarily support all the colors, but only a subset of them. (Basically, the image supports only the colors that it "needs", which allows for a more compact storage).
The solution here is to convert the image into one that has the appropriate color model. A generic method for this is shown in the following example:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
public class ImageColors
{
public static void main(String[] args) throws IOException
{
BufferedImage image = ImageIO.read(
new URL("https://i.stack.imgur.com/pSUFR.png"));
// This will show that the image has an IndexColorModel.
// This does not necessarily support all colors.
System.out.println(image);
// Convert the image to a generic ARGB image
image = convertToARGB(image);
// Now, the image has a DirectColorModel, supporting all colors
System.out.println(image);
Graphics2D g = image.createGraphics();
g.setColor(Color.PINK);
g.fillRect(50, 50, 50, 50);
g.dispose();
ImageIO.write(image, "PNG", new File("RightColors.png"));
}
public static BufferedImage convertToARGB(BufferedImage image)
{
BufferedImage newImage = new BufferedImage(
image.getWidth(), image.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
}

java 8 jpeg conversion bug?

Following topics on stackoverflow and this example : http://www.mkyong.com/java/convert-png-to-jpeg-image-file-in-java/
The code is :
public static void main(String[] args) throws IOException {
File file = new File("./1.jpg");
// File file = new File("./1.png");
File out = new File("./2.jpg");
BufferedImage image = ImageIO.read(file);
BufferedImage newBufferedImage = new BufferedImage(image.getWidth(),
image.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = newBufferedImage.createGraphics();
g2.drawImage(newBufferedImage, 0, 0, Color.WHITE, null);
g2.dispose();
ImageIO.write(newBufferedImage, "jpg", out);
}
Execut this code create an black jped picture with java 8.
This code worked with java 7
Bug in java 8 or changing API ?
It looks like this line is the problem:
g2.drawImage(newBufferedImage, 0, 0, Color.WHITE, null);
I think you're looking for:
g2.drawImage(image, 0, 0, Color.WHITE, null);
The original line was drawing the newly created BufferedImage onto itself instead of the loaded image.

Combining images where BG is a JPEG causes unexpected results

Why does combining images where BG is a JPEG cause unexpected results?
This is a follow-up to my answer in Overlaying of 2 images doesnt work properly. The source posted there (using a BG image created in memory) looks like this:
The BG image is on the left.
The FG image (a PNG with transparency) is in the middle.
The combined image is on the right.
So far, so good. But then the person who asked the question commented that if the BG was a JPEG, it failed. Thinking they were mistaken, I altered my example to encode the BG image to a JPEG. Now if I use BufferedImage.TYPE_INT_ARGB or BufferedImage.TYPE_INT_RGB for the final image I get what they were referring to:
TYPE_INT_ARGB
TYPE_INT_RGB
I expected the result to be the same as the original for at least one of those (more so the ARGB variant).
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.URL;
import javax.imageio.ImageIO;
class CombineImages {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
try {
URL urlImage1 =
new URL("http://i.stack.imgur.com/T5uTa.png");
// Load the FG image
Image fgImage = ImageIO.read(urlImage1);
int w = fgImage.getWidth(null);
int h = fgImage.getHeight(null);
// Create a non-trasparent BG image
BufferedImage bgImageTemp =
new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB);
ByteArrayOutputStream baos =
new ByteArrayOutputStream();
ImageIO.write(bgImageTemp, "jpg", baos);
ByteArrayInputStream bais =
new ByteArrayInputStream(baos.toByteArray());
BufferedImage bgImageJpeg = ImageIO.read(bais);
int result = JOptionPane.showConfirmDialog(
null,
"Use a final image with transparency?",
"Transparency",
JOptionPane.YES_NO_OPTION);
int type = (result==JOptionPane.OK_OPTION ?
BufferedImage.TYPE_INT_ARGB :
BufferedImage.TYPE_INT_RGB);
// Create the final image
BufferedImage finalImage =
new BufferedImage(w,h,type);
Graphics2D g = finalImage.createGraphics();
g.drawImage(bgImageJpeg, w, h, null);
g.drawImage(fgImage, w, h, null);
g.dispose();
JPanel gui = new JPanel(new GridLayout(1,0,5,5));
gui.add(new JLabel(new ImageIcon(bgImageJpeg)));
gui.add(new JLabel(new ImageIcon(fgImage)));
gui.add(new JLabel(new ImageIcon(finalImage)));
JOptionPane.showMessageDialog(null, gui);
} catch (Exception e) {
e.printStackTrace();
}
}
};
SwingUtilities.invokeLater(r);
}
}
Looks like this is due to a typo.
In your referenced answer, the code that formed the combined image was
Graphics2D g = finalImage.createGraphics();
g.drawImage(bgImage, 0, 0, null);
g.drawImage(fgImage, 0, 0, null);
But in this question, it's been changed to,
Graphics2D g = finalImage.createGraphics();
g.drawImage(bgImageJpeg, w, h, null);
g.drawImage(fgImage, w, h, null);
The latter begins drawing at the "top-left corner", which happens to be the bottom-right corner of the images, so nothing is really drawn. The former, however, draws the entire images, as expected.
gui.repaint();
Try that after since you effectively painted the panel and components while constructing the joptionpane, but, even then because construction threading matching visibility invocation will not hold truth you should invoke the g.drawImage in a paint override method after joptionpane invocation anyhow.
It cannot draw something that does not yet actually exist onscreen, however there is tolerance for the call because it theoretically exists as a set of objects enough for the method.

Drawing two overlayed images

I'm trying to draw 2 images, one on top of the other. The 1'st image is an arrow (that should appear like a header in the final image). The 1'st image (arrow) is 32x32 px while the 2'nd is 24x24.
Ideally I would like to draw the 2'nd image on top of the 1'st, starting from the right-bottom corner of the 1'st image.
Currently I'm using such code
// load source images
BufferedImage baseImage = ImageIO.read(new File(baseImg.getFileLocation()));
BufferedImage backgroundImage = ImageIO.read(new File(backgroundImg.getFileLocation()));
// create the new image, canvas size is the max. of both image sizes
int w = Math.max(baseImage.getWidth(), backgroundImage.getWidth());
int h = Math.max(baseImage.getHeight(), backgroundImage.getHeight());
BufferedImage combined = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
// paint both images, preserving the alpha channels
Graphics g = combined.getGraphics();
g.drawImage(baseImage, 0, 0, null);
g.drawImage(backgroundImage, 0, 0, null);
int index = baseImg.getFileLocation().lastIndexOf(".png");
String newFileName = baseImg.getFileLocation().substring(0, index);
// Save as new image
ImageIO.write(combined, "PNG", new File(newFileName + "_combined.png"));
but this won't quite work for me because the end result is a 32x32 image with the 2nd image being drawn only.
Any help is appreciated.
Thanks !
It looks like the issue here is you are drawing the 32x32 background image last, meaning it will be printed on top of the other image making it seem as if the 24x24 image was never drawn at all.
If you swap these two lines around, you should see both images. From:
g.drawImage(baseImage, 0, 0, null);
g.drawImage(backgroundImage, 0, 0, null);
to:
g.drawImage(backgroundImage, 0, 0, null);
g.drawImage(baseImage, 0, 0, null);
However this will draw the 24x24 image in the top-left corner, and you said you'd like it in the bottom-right. This can be done with some basic subtraction:
g.drawImage(baseImage, w - baseImage.getWidth(), h - baseImage.getHeight(), null);

How to increase Graphics2D text quality?

I have a question concerning printing additional information on barcodes. I am using http://barbecue.sourceforge.net/ to create my barcodes.
After I created my barcodes I want to add some additional information. At the moment i do this with the following way! For example:
Graphics2D g2d5 = container4Barcode.createGraphics();
g2d5.setBackground(Color.WHITE);
g2d5.clearRect(0, 33, 200, 200);
g2d5.setColor(Color.BLACK);
g2d5.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d5.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
g2d5.setFont(new Font("Arial", Font.PLAIN, 8));
g2d5.drawString(barcode, 8, 40);
g2d5.drawString(generateRandomNumber(ekPreis), 57, 40);
String datumResult = datum;
g2d5.drawString(location, 98, 40);
g2d5.drawString(datum.substring(2), 114, 40);
g2d5.dispose();
The output is in a pdf the following:
As you can see is the quality of my text (above and under the barcode) is really bad ... How can I increase the quality of the text to make the text more smoother and not that pixelated?!
(When I print my barcodes, the barcodes look very pixelated ...)
Any tips?
UPDATE:
So, I added here the a picture of my latest outcome ... When I print out these barcodes they look horrible! So here is the code what I did:
Graphics2D g2d6 = container4Barcode.createGraphics();
g2d6.setColor(Color.black);
g2d6.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d6.setFont(new Font("Verdana", Font.BOLD, 7));
g2d6.drawString("FLORETT", 9, 20);
g2d6.drawString("50-521-60", 57, 20);
Graphics2D g2d4 = container4Barcode.createGraphics();
g2d4.setColor(Color.black);
g2d4.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d4.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d4.setFont(new Font("Verdana", Font.BOLD, 11));
g2d4.drawString("SSYYS", 105, 19);
g2d4.dispose();
With that Code I get the best results! Of course I played with "Metrics, AA_GASP, LCS_HRGB, different fonts (Verdana is the best in my opinion) ..." and a lot more, but some of them I couldn't use, because then my barcode got blurred! So actioally I am forcing the problem that I am unable to improve the quality of my text-quality of the drawstring from graphics2d!
So, I want to ask if there is a possibility to let the "SSYYS" (Font Size 11) and the "FLORETT" (Font Size 7) look much nicer! Is there a possibility in JAVA to draw "smooth" text on an image with a font size less than "12" ? Is there a workaround to to that ? As you can see in the picture the letters "S and Y" look very awful...
2nd Update:
Some Example code so far... Please be sure that the following folder exists: C:\TestBarcodes\
Hope I reduced my code to the minimum that you can imagine what my problem is...
package generator;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import net.sourceforge.barbecue.Barcode;
import net.sourceforge.barbecue.BarcodeException;
import net.sourceforge.barbecue.BarcodeFactory;
import net.sourceforge.barbecue.output.OutputException;
import org.apache.pdfbox.exceptions.COSVisitorException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
import org.apache.pdfbox.pdmodel.graphics.xobject.PDJpeg;
import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
public class BarcodeGen {
// sets the picWidth
private static int picWidth = 149;
// sets the picHeigth
private static int picHeigth = 60;
public static void main(String[] args)
throws BarcodeException, OutputException, COSVisitorException, IOException {
generateBarcode("11138500");
}
public static void generateBarcode(String barcode)
throws IOException, COSVisitorException, BarcodeException, OutputException {
Barcode barcode2 = BarcodeFactory.createCode39(barcode, false);
int gw = barcode2.getWidth();
// change this to suit if you want higher, default 50
// barcode2.setBarWidth(50);
// this sets DPI
barcode2.setResolution(100);
// barcode2.setFont(font);
int gh = barcode2.getHeight();
// change this if you want a coloured background
// image = new BufferedImage(t, s, BufferedImage.TYPE_INT_RGB)
BufferedImage image = new BufferedImage(gw, gh, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) image.getGraphics();
// default is black so draw a white box first
// change type to INT_RGB if you want a coloured background
g2.setColor(Color.white);
g2.fillRect(0, 0, gw, gh);
barcode2.draw(g2, 0, 0);
// CREATE ADDITIONAL INFORMATION ON BARCODE
BufferedImage container4Barcode = new BufferedImage(
picWidth, picHeigth, image.getType());
Graphics2D g2d = container4Barcode.createGraphics();
g2d.setBackground(Color.WHITE);
g2d.clearRect(0, 0, picWidth, picHeigth);
g2d.setColor(Color.black);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
g2d.drawImage(image, 8, 21, 130, 18, null);
g2d.dispose();
Graphics2D g2d6 = container4Barcode.createGraphics();
g2d6.setColor(Color.black);
g2d6.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d6.setFont(new Font("Verdana", Font.BOLD, 7));
g2d6.drawString("FLORETT", 9, 20);
g2d6.drawString("50-521-60", 57, 20);
Graphics2D g2d4 = container4Barcode.createGraphics();
g2d4.setColor(Color.black);
g2d4.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d4.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d4.setFont(new Font("Verdana", Font.BOLD, 11));
g2d4.drawString("SSYYS", 105, 19);
g2d4.dispose();
// PRINT PDF
int ver = 782;
PDDocument doc = new PDDocument();
PDPage page = new PDPage(PDPage.PAGE_SIZE_A4);
doc.addPage(page);
PDXObjectImage image2 = new PDJpeg(doc, container4Barcode);
PDPageContentStream content = new PDPageContentStream(doc, page);
content.drawImage(image2, 5, ver);
content.close();
doc.save(new FileOutputStream("C:\\TestBarcodes\\barcode.pdf"));
// opens the pdf file
Process p = Runtime
.getRuntime()
.exec("rundll32 url.dll,FileProtocolHandler C:\\TestBarcodes\\barcode.pdf");
try {
p.waitFor();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
If someone wants to use pixel images in such cases, and not vector, then image should be upscaled for better printing quality:
static final int PIXELS_PER_POINT = 4; // 4x
Then define all dimensions in points, not in pixels:
// Image size in points
static final int IMAGE_WIDTH = 150;
static final int IMAGE_HEIGHT = 60;
// Font size in points
static final int FONT_SIZE = 11;
Now, when do any drawing, always use points converted to pixels:
static int toPixels(int value) {
return value * PIXELS_PER_POINT;
}
BufferedImage draw() {
BufferedImage image =
new BufferedImage(toPixels(IMAGE_WIDTH), toPixels(IMAGE_HEIGHT), TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
// <graphics init code goes here>
Font font = new Font("Arial", Font.PLAIN, toPixels(FONT_SIZE));
g.setFont(font);
g.drawString("Grapes", toPixels(5), toPixels(40)); // coordinates are in points
g.dispose()
return image;
}
So, with this approach you can operate with 'standard' dimentions. This approach works quite well for me for low- and medium complexity drawings.
You can go further and convert PIXELS_PER_POINT to a parameter: use 1x for images on web-pages with ordinary display, 2x for Retina displays and 4x for printing!
To fix the jagged edges in text or shapes in Graphics2d you need to set RenderingHint.
Graphics2D g2d = bufferedImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
Explanation
A typical antialiasing algorithm works by blending the existing colors of the pixels along the boundary of a shape with the requested fill paint according to the estimated partial pixel coverage of the shape.
Text antialiasing hint key. The TEXT_ANTIALIASING hint can control the use of antialiasing algorithms for text independently of the choice used for shape rendering. Often an application may want to use antialiasing for text only and not for other shapes. Additionally, the algorithms for reducing the aliasing artifacts for text are often more sophisticated than those that have been developed for general rendering so this hint key provides additional values which can control the choices of some of those text-specific algorithms. If left in the DEFAULT state, this hint will generally defer to the value of the regular KEY_ANTIALIASING hint key.

Categories

Resources