Similar library to TinyPNG for Java - compress image size - java

I'm working on a website that allows users to upload images and crop them.
I convert every image to .PNG for better quality. The problem I'm having is the picture size.
If I upload a 200 kb image, after cropping and making it PNG it has 600 kb. This is not a solution for me, because the images are stored in the database as BLOBs and the website loads slower.
I'm trying to find a way to compress the png, to have a smaller size, without reducing the quality.
I couldn't find any library or solution for this problem. I need something for Java like TinyPNG.
This is how I do it:
BufferedImage resizedImage = resizeImage(image,extension,width,height);
System.out.println("dimensiuni:" + resizedImage.getHeight()+ "x" + resizedImage.getWidth());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write( resizedImage, "png", baos );
baos.flush();
byte[] imageInByte = baos.toByteArray();
baos.close();
And this is the resizeImage function:
public BufferedImage resizeImage(BufferedImage image, String extension, int targetWidth, int targetHeight) {
int type = (image.getTransparency() == Transparency.OPAQUE) ?
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage)image;
int w, h;
w = image.getWidth();
h = image.getHeight();
do {
if (w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if ( h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(w, h, type);
Graphics2D g2 = tmp.createGraphics();
g2.setComposite(AlphaComposite.Src);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_DEFAULT);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
tmp.flush();
} while (w != targetWidth || h != targetHeight);
return ret;
}
Help me!!

On tinypng website they have explained in how doe it work question that how they compress the image see below:
How does it work?
Excellent question! When you upload a PNG (Portable Network Graphics)
file, similar colors in your image are combined. This technique is
called “quantization”. By reducing the number of colors, 24-bit PNG
files can be converted to much smaller 8-bit indexed color images. All
unnecessary metadata is stripped too. The result better PNG files with
100% support for transparency. Have your cake and eat it too!
Absolutely they are using some libraries for that,
You can use http://pngquant.org/ for compressing or reducing size of image,
In another SO question someone answered that tinypng also uses pngquant library, but i don't know how far the truth in it, but you can use this library in Java also,
In the pngquant website they have given the java library link, take a look at it, i have provided that same github repository link as provided in website:
https://github.com/ImageOptim/libimagequant/tree/master/org/pngquant
Basically the above library is written in c/c++ so it uses JNI(Java Native Interface) the interface which joins c/c++ with Java.

Related

Java Image BufferedImage loosing aspect ratio of upright image

I am storing an Image in the filesystem like:
FileImageOutputStream fos = new FileImageOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
And all images are stored correctly in the filesystem. (Also with correct aspect ratio with width and height!)
However, later, I am loading my image like:
File imgFile ...
FileImageInputStream stream = new FileImageInputStream(imgFile);
BufferedImage srcImage = ImageIO.read(stream);
And I want to get the width and height for my images like:
int actualHeight = srcImage.getHeight();
int actualWidth = srcImage.getWidth();
This works totally fine for images in landscape format.
However, for images in upfront format, the width and height is always swapped, so that the width is the height of the orginial image. So, e.g. I have an image that is 200x500 (width x height) and the two integers above are actualHeight = 200 and actualWidth = 500.
Am I missing something?
Most likely, your images are Exif images ("Exif-in-JPEG") from a digital camera/phone. For such images, the pixel data is often stored in the "natural" orientation of the sensor, which is always the same (usually landscape). For portrait images, the orientation is only stored in the Exif metadata, and the ImageIO JPEG plugin doesn't take this orientation into account.
Some cameras, like my Canon DSLR, has an option to do in-camera rotation to match the orientation, but this feature is typically disabled by default. This is obviously only a possible fix if you control the input images.
To fix this in the Java side, you hava some options. You already mentioned using Thumbnailator:
BufferedImage srcImage = Thumbnails.of(new FileInputStream(imgFile))
.scale(1)
.asBufferedImage();
Another option is to use EXIFUtilities from TwelveMonkeys (I'm the author of that library):
IIOImage image = EXIFUtilities.readWithOrientation(imgFile);
BufferedImage srcImage = (BufferedImage) image.getRenderedImage();
Or, if you don't need the other metadata for anything:
BufferedImage srcImage = (BufferedImage) EXIFUtilities.readWithOrientation(imgFile)
.getRenderedImage();

ImageIO.write(img,"jpg",pathtosave) JAVA not saving Image file to selected filepath

I'm using JVM 14.0.2 in VSCode IDE.
The purpose of the code is to change the original input image to grayscale image and save the new gray image to the desired location.
The code runs with no exceptions and i tried to print some progress lines(System.out.println("Saving completed...");), those lines printed throughout the program where i plugged in. However, when i go to the selected filepath to search for the saved GrayScale image, i do not see the new image in the directory.
I then tried the BlueJ IDE, and the gray image was saved. Can you check if it's VSCode developing environment issue or my code issue? or I need a different class/method to edit images in VSCode? Thanks for your help.Let me know if you need more details.
public class GrayImage {
public static void main(String args[]) throws IOException {
BufferedImage img = null;
// read image
try {
File f = new File("C:\\original.jpg");
img = ImageIO.read(f);
// get image width and height
int width = img.getWidth();
int height = img.getHeight();
BufferedImage grayimg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
// convert to grayscale
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Color color = new Color(img.getRGB(x, y));
int r = (int) color.getRed();
int g = (int) color.getBlue();
int b = (int) color.getGreen();
// calculate average
int avg = (r + g + b) / 3;
// replace RGB value with avg
Color newColor = new Color(avg, avg, avg, color.getAlpha());
grayimg.setRGB(x, y, newColor.getRGB());
}
}
// write image
System.out.println("Trying to write the new image...");
File newf = new File("H:\\gray.jpg");
ImageIO.write(grayimg, "jpg", newf);
System.out.println("Finished writing the new image...");
} catch (IOException e) {
System.out.println(e);
}
}// main() ends here
}
If I understand this problem correctly, the important lesson here is that ImageIO.write(...) returns a boolean, indicating whether it succeeded or not. You should handle situations where the value is false, even if there is no exception. For reference, see the API doc.
Something like:
if (!ImageIO.write(grayimg, "JPEG", newf)) {
System.err.println("Could not store image as JPEG: " + grayimg);
}
Now, for the reason your code does indeed work in one JRE and not in another, is probably related to the image being of type TYPE_INT_ARGB (ie. contains alpha channel). This used to work in Oracle JDK/JREs but support was removed:
Previously, the Oracle JDK used proprietary extensions to the widely used IJG JPEG library in providing optional color space support.
This was used to support PhotoYCC and images with an alpha component on both reading and writing. This optional support has been removed in Oracle JDK 11.
The fix is easy; as your source is a JPEG file, it probably does not contain an alpha component anyway, so you could change to a different type with no alpha. As you want a gray image, I believe the best match would be:
BufferedImage grayimg = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
But TYPE_INT_RGB or TYPE_3BYTE_BGR should work too, should you later run into the same problem with color images.

Write to 16 bit BufferedImage TYPE_USHORT_GRAY

I'm trying to write 16 bit grayscale imagedata to a png using BufferedImage.TYPE_USHORT_GRAY. Normally I write to an image like so:
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
then:
image.setRGB(x,y,Color.getRGB);
to set the pixels, and finally:
ImageIO.write(image, "png", new File(path + ".png"));
to write to a png image.
But now I have this as image:
BufferedImage imageGray = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY);
How do I go about saving pixels to that format? Using setRGB() with a 16 bit integer doesn't seem to work, when I open the saved png file I see a lot of banding happening.
I tried saving a simple gradient from 0 to 65535 and then using the setRGB() on the grayscale image, and checked the results in Photoshop. I can see the image consists of smaller gradients every 256 rows. I'm guessing either setRGB() or imageIO doesn't work as I would like it to.
Are there workarounds for this? Does imageIO even support the BufferedImage.TYPE_USHORT_GRAY format? Or can it only save 8 bit data? And if it can save 16bit data, how would I go about saving pixel data, preferably in a way like setRGB() works (so for a certain x,y coordinate)?
As pst already commented below my question:
Try using the Raster directly?
Accessing the Raster directly solved the problem :
BufferedImage bi = BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY)
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
Short s = shortData[x][y]
bi.getRaster().setDataElements(x, y, Short[] {s})
}
}
From BufferedImage you can read
public static final int TYPE_USHORT_GRAY
Represents an unsigned short grayscale image, non-indexed). This image has a ComponentColorModel with a CS_GRAY ColorSpace.
So try instantiating your own ColorSpace with the CS_GRAY type (ColorSpace.getInstance(ColorSpace.CS_GRAY) should do it I suppose). This object has a method called fromRGB which you should be able to use...
You probably need to widen the signed 16bit shorts to ints and remove the sign:
int ushort = (int)(shortData[x][y]) & 0xFFFF;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY);
short[] dataArray = ((DataBufferUShort)image.getRaster().getDataBuffer()).getData();
dataArray[y*width+x] = color;
ImageIO.write(image, "png", new File(path + ".png"));

What is the best way to scale images in Java?

I have a web application written in Java (Spring, Hibernate/JPA, Struts2) where users can upload images and store them in the file system. I would like to scale those images so that they are of a consistent size for display on the site. What libraries or built in functions will offer the best results? I will consider the following criteria in making my decision (in this order):
Free/Open Source (essential)
Easy to implement
Quality of results
Performance
Size of executable
I would really recommend giving imgscalr a look.
It is released under an Apache 2 license, hosted on GitHub, been deployed in a handful of web applications already, has a very simple, but pedantically documented API, has code that works around 2 major image bugs in the JDK for you transparently that you'll only ever notice if you suddenly start getting "black" images after a scale operation or horrible-looking results, gives you the best possible looking results available in Java, is available via Maven as well as a ZIP and is just a single class.
Basic use looks like this:
BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, 320);
This is the simplest call where the library will make a best-guess at the quality, honor your image proportions, and fit the result within a 320x320 bounding box. NOTE, the bounding box is just the maximum W/H used, since your image proportions are honored, the resulting image would still honor that, say 320x200.
If you want to override the automatic mode and force it to give you the best-looking result and even apply a very mild anti-alias filter to the result so it looks even better (especially good for thumbnails), that call would look like:
BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, Method.QUALITY,
150, 100, Scalr.OP_ANTIALIAS);
These are all just examples, the API is broad and covers everything from super-simple use cases to very specialized. You can even pass in your own BufferedImageOps to be applied to the image (and the library automatically fixes the 6-year BufferedImageOp JDK bug for you!)
There is a lot more to scaling images in Java successfully than the library does for you, for example always keeping the image in one of the best supported RGB or ARGB image types while operating on it. Under the covers the Java2D image processing pipeline falls back to an inferior software pipeline if the image type used for any image operations is poorly supported.
If all that sounded like a lot of headaches, it sort of is... that's why I wrote the library and open-sourced it, so folks could just resize their images and move on with their lives without needing to worry about it.
Hope that helps.
Have a look at the Java Image I/O API to read/write the image. Then use AffineTransform to resize.
Also, here's a complete example using java.awt.Image.
Look into also to java-image-scaling library. It created better quality images that ImageIO.
The best tool for image editing is ImageMagick and it is open source.
There are two interfaces for the Java Language:
JMagick which uses JNI interface to ImageMagick
and
im4java what is a command line interface for ImageMagick
Found this to be faster:
public static BufferedImage getScaledInstance(final BufferedImage img, final int targetWidth, final int targetHeight,
final Object hint) {
final int type = BufferedImage.TYPE_INT_RGB;
int drawHeight = targetHeight;
int drawWidth = targetWidth;
final int imageWidth = img.getWidth();
final int imageHeight = img.getHeight();
if ((imageWidth <= targetWidth) && (imageHeight <= targetHeight)) {
logger.info("Image " + imageWidth + "/" + imageHeight + " within desired scale");
return img;
}
final double sar = ((double) imageWidth) / ((double) imageHeight);
if (sar != 0) {
final double tar = ((double) targetWidth) / ((double) targetHeight);
if ((Math.abs(tar - sar) > .001) && (tar != 0)) {
final boolean isSoureWider = sar > (targetWidth / targetHeight);
if (isSoureWider) {
drawHeight = (int) (targetWidth / sar);
}
else {
drawWidth = (int) (targetHeight * sar);
}
}
}
logger.info("Scaling image from " + imageWidth + "/" + imageHeight + " to " + drawWidth + "/" + drawHeight);
final BufferedImage result = new BufferedImage(drawWidth, drawHeight, type);
try {
final Graphics2D g2 = result.createGraphics();
try {
if (hint != null) {
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
}
g2.drawImage(img, 0, 0, drawWidth, drawHeight, null);
}
finally {
g2.dispose();
}
return result;
}
finally {
result.flush();
}
}
I know this is a very old question, but I got my own solution for this using standard Java API
import java.awt.*;
import java.awt.event.*;
import javax.imageio.*
import java.awt.image.*;
BufferedImage im, bi, bi2;
Graphics2D gfx;
int imWidth, imHeight, dstWidth, dstHeight;
int DESIRED_WIDTH = 500, DESIRED_HEIGHT = 500;
im = ImageIO.read(new File(filePath));
imWidth = im.getWidth(null);
imHeight = im.getHeight(null);
dstWidth = DESIRED_WIDTH;
dstHeight = (dstWidth * imHeight) / imWidth;
bi = new BufferedImage(dstWidth, dstHeight, im.getType());
gfx = bi.createGraphics();
gfx.drawImage(im, 0, 0, dstWidth, dstHeight, 0, 0, imWidth, imHeight, null);
bi2 = new BufferedImage(DESIRED_WIDTH, DESIRED_HEIGHT, im.getType());
gfx = bi2.createGraphics();
gfx.drawImage(bi, 0, 0, DESIRED_WIDTH, DESIRED_HEIGHT, null);
ImageIO.write(bi2, "jpg", new File(filePath));
I am sure it can be improved and adapted.
I tried imgscalr comparing to standard Java 1.6 and I cannot say it is better.
What I've tried is
BufferedImage bufferedScaled = Scalr.resize(sourceImage, Method.QUALITY, 8000, height);
and
Image scaled = sourceImage.getScaledInstance(-1, height, Image.SCALE_SMOOTH);
BufferedImage bufferedScaled = new BufferedImage(scaled.getWidth(null), scaled.getHeight(null), BufferedImage.TYPE_INT_RGB);
bufferedScaled.getGraphics().drawImage(scaled, 0, 0, null);
some 5 minute testing by my eye got impression that second thing (pure Java 1.6) produces better results.

Converting transparent gif / png to jpeg using java

I'd like to convert gif images to jpeg using Java. It works great for most images, but I have a simple transparent gif image:
Input gif image http://img292.imageshack.us/img292/2103/indexedtestal7.gif
[In case the image is missing: it's a blue circle with transparent pixels around it]
When I convert this image using the following code:
File file = new File("indexed_test.gif");
BufferedImage image = ImageIO.read(file);
File f = new File("indexed_test.jpg");
ImageIO.write(image, "jpg", f);
This code works without throwing an Exception, but results an invalid jpeg image:
[In case the image is missing: IE cannot show the jpeg, Firefox shows the image with invalid colors.]
I'm using Java 1.5.
I also tried converting the sample gif to png with gimp and using the png as an input for the Java code. The result is the same.
Is it a bug in the JDK? How can I convert images correctly preferably without 3rd party libraries?
UPDATE:
Answers indicate that jpeg conversion cannot handle transparency correctly (I still think that this is a bug) and suggest a workaround for replacing transparent pixels with predefined color. Both of the suggested methods are quite complex, so I've implemented a simpler one (will post as an answer). I accept the first published answer with this workaround (by Markus). I don't know which implementation is the better. I go for the simplest one still I found a gif where it's not working.
For Java 6 (and 5 too, I think):
BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
g = bufferedImage.createGraphics();
//Color.WHITE estes the background to white. You can use any other color
g.drawImage(image, 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), Color.WHITE, null);
As already mentioned in the UPDATE of the question I've implemented a simpler way of replacing transparent pixels with predefined color:
public static BufferedImage fillTransparentPixels( BufferedImage image,
Color fillColor ) {
int w = image.getWidth();
int h = image.getHeight();
BufferedImage image2 = new BufferedImage(w, h,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = image2.createGraphics();
g.setColor(fillColor);
g.fillRect(0,0,w,h);
g.drawRenderedImage(image, null);
g.dispose();
return image2;
}
and I call this method before jpeg conversion in this way:
if( inputImage.getColorModel().getTransparency() != Transparency.OPAQUE) {
inputImage = fillTransparentPixels(inputImage, Color.WHITE);
}
The problem (at least with png to jpg conversion) is that the color scheme isn't the same, because jpg doesn't support transparency.
What we've done successfully is something along these lines (this is pulled from various bits of code - so please forgive the crudeness of the formatting):
File file = new File("indexed_test.gif");
BufferedImage image = ImageIO.read(file);
int width = image.getWidth();
int height = image.getHeight();
BufferedImage jpgImage;
//you can probably do this without the headless check if you just use the first block
if (GraphicsEnvironment.isHeadless()) {
if (image.getType() == BufferedImage.TYPE_CUSTOM) {
//coerce it to TYPE_INT_ARGB and cross fingers -- PNGs give a TYPE_CUSTOM and that doesn't work with
//trying to create a new BufferedImage
jpgImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
} else {
jpgImage = new BufferedImage(width, height, image.getType());
}
} else {
jgpImage = GraphicsEnvironment.getLocalGraphicsEnvironment().
getDefaultScreenDevice().getDefaultConfiguration().
createCompatibleImage(width, height, image.getTransparency());
}
//copy the original to the new image
Graphics2D g2 = null;
try {
g2 = jpg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2.drawImage(image, 0, 0, width, height, null);
}
finally {
if (g2 != null) {
g2.dispose();
}
}
File f = new File("indexed_test.jpg");
ImageIO.write(jpgImage, "jpg", f);
This works for png to jpg and gif to jpg. And you will have a white background where the transparent bits were. You can change this by having g2 fill the image with another color before the drawImage call.
3 months late, but I am having a very similar problem (although not even loading a gif, but simply generating a transparent image - say, no background, a colored shape - where when saving to jpeg, all colors are messed up, not only the background)
Found this bit of code in this rather old thread of the java2d-interest list, thought I'd share, because after a quick test, it is much more performant than your solution:
final WritableRaster raster = img.getRaster();
final WritableRaster newRaster = raster.createWritableChild(0, 0, img.getWidth(), img.getHeight(), 0, 0, new int[]{0, 1, 2});
// create a ColorModel that represents the one of the ARGB except the alpha channel
final DirectColorModel cm = (DirectColorModel) img.getColorModel();
final DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(), cm.getRedMask(), cm.getGreenMask(), cm.getBlueMask());
// now create the new buffer that we'll use to write the image
return new BufferedImage(newCM, newRaster, false, null);
Unfortunately, I can't say I understand exactly what it does ;)
If you create a BufferedImage of type BufferedImage.TYPE_INT_ARGB and save to JPEG weird things will result. In my case the colors are scewed into orange. In other cases the produced image might be invalid and other readers will refuse loading it.
But if you create an image of type BufferedImage.TYPE_INT_RGB then saving it to JPEG works fine.
I think this is therefore a bug in Java JPEG image writer - it should write only what it can without transparency (like what .NET GDI+ does). Or in the worst case thrown an exception with a meaningful message e.g. "cannot write an image that has transparency".
JPEG has no support for transparency. So even when you get the circle color correctly you will still have a black or white background, depending on your encoder and/or renderer.
BufferedImage originalImage = ImageIO.read(getContent());
BufferedImage newImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
for (int x = 0; x < originalImage.getWidth(); x++) {
for (int y = 0; y < originalImage.getHeight(); y++) {
newImage.setRGB(x, y, originalImage.getRGB(x, y));
}
}
ImageIO.write(newImage, "jpg", f);
7/9/2020 Edit: added imageIO.write

Categories