Due to resource or network speed requirements, in mobile games or general web games, we hope to compress images to the maximum extent possible to save resources. Recently, my company also had a need for this project, so I spent a long time on the Internet, I hope I can find out that the ready-made Java method can be used (using programs to compress without using tools, or else you want to get tired of more than 20 thousand images? Although PS has the batch function, it cannot be stored in the original path); disappointed, it seems that no code can be directly used, even if a solution is provided. Since the Internet cannot find the right one, you can do it yourself.
I am not talking about the formats of PNG images here. I am not here to talk about the theoretical knowledge of image compression. There is a lot of online information. The goal is to use Java to compress PNG images as much as possible. Of course, we cannot see obvious distortion.
I. bufferedimage class
In Java, we naturally think of the bufferedimage class for image processing. To learn more about it, you will find that Java has helped us to compress images, only the compressed image is slightly different from our needs ....... let's take a look at bufferedimage's most common constructor methods:
Public bufferedimage (INT width, int height, int imagetype );
ConstructBufferedimage, where imagetype has the following types:
Bufferedimage. type_int_rgb: 8-bit RGB color component without alpha channel.
Bufferedimage. type_int_argb: 8-bit rgba color component with alpha channel.
Bufferedimage. type_int_argb_pre: 8-bit rgba color component, pre-multiplied by Alpha.
Bufferedimage. type_int_bgr: 8-bit RGB color components for Windows or Solaris images without Alpha channels.
Bufferedimage. type_3byte_bgr: Eight-bit GBA color component. Three colors, blue, green, and red, are stored in three bytes without Alpha.
Bufferedimage. type_4byte_abgr: 8-bit rgba color component. Three colors, blue, green, and red, and one-byte Alpha, are stored in three bytes.
Bufferedimage. type_4byte_abgr_pre: it has three colors, blue, green, and red, and 1-byte Alpha, which are stored in 3 bytes.
Bufferedimage. type_ushort_565_rgb: an image with 5-6-5rgb color components (5-bit red, 6-Bit green, and 5-bit blue) without Alpha.
Bufferedimage. type_ushort_555_rgb: Images with 5-5rgb color components (5-bit red, 5-Bit green, and 5-bit blue) without Alpha.
Bufferedimage. type_byte_gray: indicates an unsigned byte grayscale image (no index ).
Bufferedimage. type_ushort_gray: indicates an unsigned short grayscale image (unindexed ).
Bufferedimage. type_byte_binary: indicates an opaque 1, 2, or 4-bit image packaged in bytes.
Bufferedimage. type_byte_indexed: indicates the byte image with an index.
In fact, imagetype is a compression method corresponding to different formats in Java, with numbers 1-13 respectively. Next we will use the following code to call different parameters to generate an image:
For (INT I = 1; I <= 13; I ++) {<br/> tempimage = new bufferedimage (width, height, I ); <br/> g2d = (graphics2d) tempimage. getgraphics (); <br/> g2d. drawimage (sourceimage, 0, 0, null); <br/> ImageIO. write (tempimage, "PNG", new file ("Cut/c_com _" + I + ". PNG "); <br/>}
The source image is in PNG format and the size is 24.0kb:
Compressed image:
The pictures show that the black and white pictures are the least, but this is not what we want to exclude; the last type_byte_indexed type (in fact, png8) is color, not big, but the distortion is too high, exclude; the remaining transparency values are the same and excluded. compared with those with the remaining background opacity, type_ushort_555_rgb is the compression type.
Ii. Bitmap in the 555 format
The 555 format is actually a type of 16-Bit Bitmap. A 16-Bit Bitmap can contain up to 65536 colors. Each pigment is represented by 16 bits (2 bytes. This format is called high color, enhanced 16-bit color, or 64 K color. In 16 bits, the lowest five bits represent the blue component, the middle five bits represent the green component, and the highest five bits represent the red component. A total of 15 bits are occupied, and the highest bits are retained, set to 0. In the 555 format, the red, green, and blue masks are 0x7c00, 0x03e0, and 0x001f (also defined in bufferedimage source code ).
III. Further handling
From the image effect, we can see that the 555 format is very close to true color, and the image data is much smaller than the true color image, which meets our requirements. However, we need to make the background transparent, but the background of the image generated with type_ushort_555_rgb is not transparent. Naturally, we thought of replacing the opaque background with a transparent background./** <Br/> * convert an image whose background is black or opaque to a background transparent image <br/> * @ Param image the background is black or opaque (converted in 555 format) black and opaque) <br/> * @ return converted image <br/> */<br/> Private Static bufferedimage getconvertedimage (bufferedimage image) {<br/> int width = image. getwidth (); <br/> int Height = image. getheight (); <br/> bufferedimage convertedimage = NULL; <br/> graphics2d g2d = NULL; <br/> // use type_4byte_abgr with 1-byalpha, the Boolean transparency of pixels can be modified. <br/> convertedimage = new bufferedimage (width, height, bufferedimage. type_4byte_abgr); <br/> g2d = (graphics2d) convertedimage. getgraphics (); <br/> g2d. drawimage (image, 0, 0, null); <br/> // Replace the pixel of the background color with 0 <br/> for (INT I = 0; I <width; I ++) {<br/> for (Int J = 0; j <peight; j ++) {<br/> int RGB = convertedimage. getrgb (I, j); <br/> If (isbackpixel (RGB) {<br/> convertedimage. setrgb (I, j, 0); <br/>}< br/> g2d. drawimage (convertedimage, 0, 0, null); <br/> return convertedimage; <br/>}
Isbackpixel (RGB) is used to determine whether the current pixel is a background pixel:/** <Br/> * determine whether the current pixel is a black and opaque pixel (-16777216) <br/> * @ Param pixel determine the pixel <br/> * @ return indicates that the background pixel returns true, otherwise, false is returned. <br/> */<br/> Private Static Boolean isbackpixel (INT pixel) {<br/> int back [] = {-16777216 }; <br/> for (INT I = 0; I <back. length; I ++) {<br/> If (back [I] = pixel) return true; <br/>}< br/> return false; <br/>}
The converted image is as follows:
It is a little larger after conversion, which is acceptable; it is terrible to have a black border. Why? The reason is very simple. The pixels in the border part of the source image are between transparency and opacity, and after 555 format compression, all pixels become Boolean transparency, that is to say, all pixels are either transparent or not transparent.
The easiest way to think of it is to change the pixel of the border to the pixel of the original image border. The key is how to determine whether the current pixel is the border pixel of the image. This algorithm may take some time, the following is just an implementation that I think:
/** <Br/> * Image Compression <br/> * @ Param sourceimage the image to be compressed <br/> * @ return the compressed image <br/> * @ throws ioexception: An error occurred while reading and writing images. <br/> */<br/> Public static bufferedimage compressimage (bufferedimage sourceimage) throws ioexception {<br/> If (sourceimage = NULL) throw new nullpointerexception ("Empty image"); <br/> bufferedimage cutedimage = NULL; <br/> bufferedimage tempimage = NULL; <br/> bufferedimage compressedimage = NULL; <br/> Graphics2d g2d = NULL; <br/> // Image Auto crop <br/> cutedimage = cutimageauto (sourceimage); <br/> int width = cutedimage. getwidth (); <br/> int Height = cutedimage. getheight (); <br/> // The image format is 555 <br/> tempimage = new bufferedimage (width, height, bufferedimage. type_ushort_555_rgb); <br/> g2d = (graphics2d) tempimage. getgraphics (); <br/> g2d. drawimage (sourceimage, 0, 0, null); <br/> compressedimage = getconvertedimage (Tempimage); <br/> // converted image in pixels <br/> compressedimage = new bufferedimage (width, height, bufferedimage. type_4byte_abgr); <br/> g2d = (graphics2d) compressedimage. getgraphics (); <br/> g2d. drawimage (tempimage, 0, 0, null); <br/> int pixel [] = new int [width * Height]; <br/> int sourcepixel [] = new int [width * Height]; <br/> int currentpixel [] = new int [width * Height]; <br/> sourcepixel = cutedimage. getrgb (0, 0, widt H, height, sourcepixel, 0, width); <br/> currentpixel = tempimage. getrgb (0, 0, width, height, currentpixel, 0, width); <br/> for (INT I = 0; I <currentpixel. length; I ++) {<br/> if (I = 0 | I = currentpixel. length-1) {<br/> pixel [I] = 0; <br/> // internal pixel <br/>} else if (I> width & I <currentpixel. length-width) {<br/> int BEF = currentpixel [I-1]; <br/> int now = currentpixel [I]; <br/> int aft = currentpixel [I + 1]; <br/> int Up = currentpixel [I-width]; <br/> int down = currentpixel [I + width]; <br/> // set the background pixel to 0 <br/> If (isbackpixel (now) {<br/> pixel [I] = 0; <br/> // The border pixel is the same as the source image <br/>} else if ((! Isbackpixel (now) & isbackpixel (BEF) <br/> | (! Isbackpixel (now) & isbackpixel (AFT) <br/> | (! Isbackpixel (now) & isbackpixel (up) <br/> | (! Isbackpixel (now) & isbackpixel (down) <br/>) {<br/> pixel [I] = sourcepixel [I]; <br/> // other pixels are the same as those after compression of 555 <br/>} else {<br/> pixel [I] = now; <br/>}< br/> // boundary pixel <br/>} else {<br/> int BEF = currentpixel [I-1]; <br/> int now = currentpixel [I]; <br/> int aft = currentpixel [I + 1]; <br/> If (isbackpixel (now )) {<br/> pixel [I] = 0; <br/>} else if ((! Isbackpixel (now) & isbackpixel (BEF) <br/> | (! Isbackpixel (now) & isbackpixel (AFT) {<br/> pixel [I] = sourcepixel [I]; <br/>} else {<br/> pixel [I] = now; <br/>}< br/> compressedimage. setrgb (0, 0, width, height, pixel, 0, width); <br/> g2d. drawimage (compressedimage, 0, 0, null); <br/> ImageIO. write (cutedimage, "PNG", new file ("Cut/a_cut.png"); <br/> ImageIO. write (tempimage, "PNG", new file ("Cut/B _555.png"); <br/> ImageIO. write (compressedimage, "PNG", new file ("Cut/c_com.png"); <br/> return compressedimage; <br/>}
Cutedimage = cutimageauto (sourceimage) is used to crop the source image. The Code is as follows:/** <Br/> * Automatic Image cropping <br/> * @ Param image: The image to be cropped <br/> * @ return: The cropped image <br/> */ <br/> Public static bufferedimage cutimageauto (bufferedimage image) {<br/> rectangle area = getcutareaauto (image); <br/> return image. getsubimage (area. x, area. y, area. width, area. height ); <br/>}</P> <p>/** <br/> * obtain the reserved area of the cropped image. <br/> * @ Param image: The image to be cropped. <br/> * @ return reserved region <br/> */<br/> Private Static rectangle getcutarea Auto (bufferedimage image) {<br/> If (image = NULL) throw new nullpointerexception ("image is blank"); <br/> int width = image. getwidth (); <br/> int Height = image. getheight (); <br/> int startx = width; <br/> int starty = height; <br/> int endx = 0; <br/> int Endy = 0; <br/> int [] pixel = new int [width * Height]; </P> <p> pixel = image. getrgb (0, 0, width, height, pixel, 0, width); <br/> for (INT I = 0; I <pixel. length; I ++) {<br/> If (is Cutbackpixel (pixel [I]) continue; <br/> else {<br/> int W = I % width; <br/> int H = I/width; <br/> startx = (W <startx )? W: startx; <br/> starty = (H <starty )? H: starty; <br/> endx = (W> endx )? W: endx; <br/> Endy = (h> Endy )? H: Endy; <br/>}< br/> If (startx> endx | starty> Endy) {<br/> startx = starty = 0; <br/> endx = width; <br/> Endy = height; <br/>}< br/> return New rectangle (startx, starty, endx-startx, endy-starty ); <br/>}</P> <p>/** <br/> * whether the current pixel is a background pixel <br/> * @ Param pixel <br/> *@ return <br/> */<br/> Private Static Boolean iscutbackpixel (INT pixel) {<br/> int back [] = {0, 8224125, 16777215,8947848, 460551,4141853, 8289918}; <br/> for (INT I = 0; I <back. length; I ++) {<br/> If (back [I] = pixel) return true; <br/>}< br/> return false; <br/>}
Improved Image:
In fact, this method is only applicable to images with clear colors (border color and unique background color) and few black pixels. Some special images have to be specially processed, for example, in slice:
After compression
The reason is that the black and opaque pixels are also part of the image entity, so that they are replaced with white and transparent. You can change the code, but the image size will increase a lot, that is, replace the pixels that the program considers as background colors with the pixels of the original image; compressimage () replace rows 33rd, 43, and 61 in the method with Pixel [I] = sourcepixel [I.