Skip to content
dragon66 edited this page Jun 3, 2019 · 139 revisions

Here I am going to show some examples of how to use "icafe" library:

  • Read and write images using "icafe"

    • Full support for reading GIF, BMP, PNG, PCX, and TGA image formats and writing GIF, PNG, TIFF, JPEG, BMP.
    • Partial support for reading TIFF. TIFF reading now supports RGB, YCbCr, CMYK w/o ICC_Profile, palette Color, both Uncompressed or compressed with LZW, Deflate, and Packbits. Floating point sample data up to 64 bitsPerSample are also supported.). Compression with CCITT fax and JPEG is not supported yet.
    • JPEG reading for now is delegated to javax.imageIO - (ICAFE JPEG reader is in progress which will add CMYK support.)

    Reading with "icafe" is as easy as using Java ImageIO while writing with "icafe", you have enough control over a lot of things like image color depth, compression method, color space, whether or not to include ICC_Profile, what kind of pre-processing filter, predictor to apply before data compression, whether or not dither should be applied and the threshold for dither etc. Let's look at an example:

      import java.awt.event.WindowAdapter;
      import java.awt.event.WindowEvent;
      import java.awt.image.BufferedImage;
      import java.io.FileOutputStream;
    
      import javax.swing.ImageIcon;
      import javax.swing.JFrame;
      import javax.swing.JLabel;
      import javax.swing.JScrollPane;
    
      import com.icafe4j.image.ImageIO;
      import com.icafe4j.image.ImageParam;
      import com.icafe4j.image.ImageType;
      import com.icafe4j.image.options.JPGOptions;
      import com.icafe4j.image.options.PNGOptions;
      import com.icafe4j.image.options.TIFFOptions;
      import com.icafe4j.image.png.Filter;
      import com.icafe4j.image.tiff.TiffFieldEnum.PhotoMetric;
      import com.icafe4j.image.tiff.TiffFieldEnum.Compression;
      import com.icafe4j.image.writer.ImageWriter;
    
    
      public class TestImageIO {
    
      	 public static void main(String args[]) throws Exception
      	 {
      		  long t1 = System.currentTimeMillis();
      		  BufferedImage img = ImageIO.read(args[0]);
      		  long t2 = System.currentTimeMillis();
      		
      		  System.out.println("decoding time "+(t2-t1)+"ms");
      			
      		  final JFrame jframe = new JFrame("Image Reader");
    
      		  jframe.addWindowListener(new WindowAdapter(){
      			  public void windowClosing(WindowEvent evt)
      			  {
      				  jframe.dispose();
      				  System.exit(0);
      			  }
      		  });
      		  
      		  ImageType imageType = ImageType.JPG;
      		  
      		  FileOutputStream fo = new FileOutputStream("NEW." + imageType.getExtension());
      				
      		  ImageWriter writer = ImageIO.getWriter(imageType);
      		
      		  ImageParam.ImageParamBuilder builder = ImageParam.getBuilder();
      		  
      		  switch(imageType) {
      			case TIFF:// Set TIFF-specific options
      				 TIFFOptions tiffOptions = new TIFFOptions();
      				 tiffOptions.setApplyPredictor(true);
      				 tiffOptions.setTiffCompression(Compression.JPG);
      				 tiffOptions.setJPEGQuality(60);
      				 tiffOptions.setPhotoMetric(PhotoMetric.SEPARATED);
      				 tiffOptions.setWriteICCProfile(true);
      				 builder.imageOptions(tiffOptions);
      				 break;
      			case PNG:
      				PNGOptions pngOptions = new PNGOptions();
      				pngOptions.setApplyAdaptiveFilter(true);
      				pngOptions.setCompressionLevel(6);
      				pngOptions.setFilterType(Filter.NONE);
      				builder.imageOptions(pngOptions);
      				break;
      			case JPG:
      				JPGOptions jpegOptions = new JPGOptions();
      				jpegOptions.setQuality(60);
      				jpegOptions.setColorSpace(JPEGOptions.COLOR_SPACE_YCCK);
      				jpegOptions.setWriteICCProfile(true);
      				builder.imageOptions(jpegOptions);
      				break;
      			default:
      		  }
      		  
      		  writer.setImageParam(builder.build());
      		  
      		  t1 = System.currentTimeMillis();
      		  writer.write(img, fo);
      		  t2 = System.currentTimeMillis();
      		
      		  fo.close();
      		
      		  System.out.println(imageType + " writer "+ "(encoding time "+(t2-t1)+"ms)");
      		
      		  JLabel theLabel = new JLabel(new ImageIcon(img));
      		  jframe.getContentPane().add(new JScrollPane(theLabel));
      		  jframe.setSize(400,400);
      		  jframe.setVisible(true);
      	 }
      }
    

    In the above example, we read an image the same way Java ImageIO does except we are using "icafe" ImageIO instead of Java ImageIO. The image types are automatically detected by reading the first 4 bytes of the input stream and compare with known image magic numbers. If the image type to be read is known before hand, we can grab the image reader using ImageIO.getReader(ImageType) directly and call it's read(InputStream) method, this will be more efficient.

    When writing image, we already know the image type we are going to use, so we ask ImageIO to create corresponding ImageWriter for us first, then we build an ImageParam using "ImageParamBuilder" to set parameters for the writer. There are common parameters and image specific "ImageOptions" which we can set as a whole on ImageParam.

    To save image, you can also simply call ImageIO.write(BufferedImage img, OutputStream os, ImageType imageType, ImageParam imageParam) or ImageIO.write(BufferedImage img, OutputStream os, ImageType imageType).

    In the example above, we create a JPEG image, set quality to 60, using YCCK color space and include an ICC_Profile as well.

    We run the above example "java images\butterfly.png" which prints out the following information:

      --- PNG IMAGE INFO ---
      image width: 398
      image height: 249
      image bit depth: 8
      Image color type: 6 - True-color-with-alpha: each pixel is an R,G,B triple, followed by an alpha sample.
      image compression: 0 - Deflate/inflate compression with a 32K sliding window
      image filter method: 0 - Adaptive filtering with five basic filter types
      image interlace method: 0 - No interlace
      --- END PNG IMAGE INFO ---
      decoding time 61ms
      Jpeg writer (encoding time 162ms)
    

    along with the created YCCK JPEG image "ycckButterfly.jpg" example


  • Color quantization and dither process used by "icafe"

    When saving images as GIF, most of the time we have to do some kind of color quantization to reduce colors to within 256. This more often than not will produce terrible artifacts in the resulting images especially if no subsequent dithering process is applied.

    The most popular algorithm by far for color quantization is the median cut algorithm. A more modern popular method is clustering using octrees. The NeuQuant Neural-Net image quantization algorithm is a replacement for the common Median Cut algorithm. It is a high quality but slow algorithm. Another high quality method is proposed by Xiaolin Wu which is a "Greedy orthogonal bipartition of RGB space for variance minimization aided by inclusion-exclusion tricks."(see Graphics Gems vol. II, pp. 126-133).

    "ICAFE" currently uses three color quantization methods - namely "population algorithm" (or "popularity method"), Xiaolin Wu's algorithm which is relatively fast compared to the third method NeuQuant - neural network algorithm. In most cases, Wu's algorithm, if combined with some kind of dither, creates great quality images. NeuQuant algorithm in general, creates better result compared with Wu's algorithm, but not always and it could be a lot slower with large images. Popularity algorithm essentially constructs a histogram of equal-sized ranges and assigns colors to the ranges containing the most points. Except for NeuQuant and Xiaolin Wu's algorithms, all the other quantization methods have to be combined with some kind of dither process in order to produce reasonable quality images. "ICAFE" includes two dither methods - error diffusion (Floyd Steinberg) and ordered dither (Bayer matrix) along with an efficient inverse color map implementation to map colors to the color lookup table indices.

    Now, let's see the result:

    [Original]

    original

    [Quantized - Wu's algorithm] quantized

    [Quantized+dither] withdither

    [NeuQuant - neural network algorithm] neuquant

    As can be seen, using "icafe", even when saved as GIF image, the quality is great!


  • Split animated GIF

    This is how you can split an animated GIF into separate GIF images:

      import com.icafe4j.image.ImageIO;
      import com.icafe4j.image.ImageType;
      import com.icafe4j.image.gif.GIFTweaker;
      import com.icafe4j.image.writer.ImageWriter;
    
      import java.io.FileInputStream;
    
      public class TestGIFTweaker {
    
      	public TestGIFTweaker() {}
    
      	public static void main(String[] args) throws Exception {
      	
      		ImageWriter writer = ImageIO.getWriter(ImageType.GIF);
      
      		FileInputStream is = new FileInputStream(args[0]);
      		
      		GIFTweaker.splitAnimatedGIF(is, writer, "split");
      		
      		is.close();
      	}
      }
    

    Here we first grab a writer for GIF image, create input stream from the animated GIF, then call splitFramesEx on GIFTweaker which is a utility class with all static methods. One of the arguments "split" for the splitFramesEx method is a prefix for the output GIF images. If you pass in a path followed by the prefix, it will put the split images under the path with names started with the prefix.

    Call the main method and pass in the path of the animated GIF - java TestGIFTweaker images/butterfly.gif, it will split it into 16 frames and preserve the transparency!

    Here are the original image and the first split frame:

Animated butterfly split-frame-0

We save the split frames by passing in an "ImageWriter" which is an interface for all the "ImageWriter" implementations. This makes it possible for us to save the frames in format other than GIF, such as JPEG, TIFF, PNG etc as long as they are supported by "icafe".

When splitting animated GIFs, we do not just grab the internal frames of the image but taking into account the graphic control parameters coming with every frame, especially the "disposal method". This way, we can "correctly" handle partial or transparent internal frames.

Note: There is another version of the splitAnimatedGIF method which does not take an ImageWriter as the parameter. In that case, the output frames will be in GIF format.


  • Create animated GIF from a bunch of Java BufferedImage

    It is equally easy to create an animated GIF from a number of Java BufferedImage:

      import java.awt.image.BufferedImage;
      import java.io.FileOutputStream;
    
      import com.icafe4j.image.gif.GIFTweaker;
      import com.icafe4j.util.FileUtils;
    
      public class TestGIFTweaker {
    
      	public TestGIFTweaker() {}
    
      	public static void main(String[] args) throws Exception {
      		FileOutputStream fout = new FileOutputStream("flypiggie.gif");
      		
      		File[] files = FileUtils.listFilesMatching(new File(args[0]), args[1]);
      		BufferedImage[] images = new BufferedImage[files.length];
      		int[] delays = new int[images.length];
      		
      		for(int i = 0; i < files.length; i++) {
      			FileInputStream fin = new FileInputStream(files[i]);
      			BufferedImage image = javax.imageio.ImageIO.read(fin);
      			images[i] = image;
      			delays[i] = 100;
      			fin.close();
      		}
    
      		GIFTweaker.writeAnimatedGIF(images, delays, fout);
              fout.close();
      	}
      }
    

    We run it like this: java TestGIFTweaker images tmp-\d{1,2}\.gif. The first argument is the folder containing the separate GIF images, and the second argument is a regular expression to select the GIF images to create the animated GIF. Here our frames are named "tmp-00.gif" through "tmp-04.gif" and the output is "flypiggie.gif" as shown below:

piggie-00 flypigge

Note: The above written animated GIF assumes all the frames' coordinate are at (0,0), disposal method as "restore to background", and frame loops forever. To fine tune the writing process, we can create instance of GIFFrame with parameters such as "delay", "disposal method", "user input flag", frame transparent color, as well as different coordinates for different frames.

Note: To save memory, we can also write animated GIF frame by frame!


  • Split multipage TIFF image into separate TIFF images

    The following example shows how you can use "icafe" to split a multipage TIFF image into single page TIFFs without JAI - yes, you don't need JAI or any native code to split a multipage TIFF anymore, just pure Java:

      import com.icafe4j.io.RandomAccessInputStream;
      import com.icafe4j.io.FileCacheRandomAccessInputStream;
      import com.icafe4j.util.FileUtils;
    
      public class TestTIFFTweaker {
    
      	public static void main(String[] args) throws Exception {
              FileOutputStream fin = new FileInputStream(args[0]);
      		RandomAccessInputStream rin = new FileCacheRandomAccessInputStream(fin);
      		TIFFTweaker.splitPages(rin, FileUtils.getNameWithoutExtension(new File(args[0])));
      	    rin.close();
              fin.close(); / / Need to close the underlying stream explicitly!!!
      	}
      }
    

    Run "java TestTIFFTweaker images/1.tif", it will split "1.tif" into 6 single page TIFFs named 1_page_#0 through 1_page_#5. One of the nice thing about this is the splitting process doesn't need to decompress the original image. So it's quick and the resulting images are faithful to the original image.


  • Create multipage TIFF image

    We can create a multipage TIFF images from a series of Java BufferedImages. We are not interested in how the BufferedImages are created in the first place. In this example, we read existing GIF images using Java imageio, feed them to our familiar "TIFFTweaker" utility, setting some parameters for the underlying "TIFFWriter" like compression type, color depth, whether or not to keep transparency etc., and we are done:

      import java.awt.image.BufferedImage;
      import java.io.FileInputStream;
      import java.io.FileOutputStream;
    
      import com.icafe4j.image.ImageParam;
      import com.icafe4j.image.ColorType;
      import com.icafe4j.image.options.TIFFOptions;
      import com.icafe4j.image.tiff.TIFFTweaker;
      import com.icafe4j.image.tiff.TiffFieldEnum.Compression;
      import com.icafe4j.io.FileCacheRandomAccessInputStream;
      import com.icafe4j.io.FileCacheRandomAccessOutputStream;
      import com.icafe4j.io.RandomAccessInputStream;
      import com.icafe4j.io.RandomAccessOutputStream;
      import com.icafe4j.util.FileUtils;
    
      public class TestTIFFTweaker {
    
      	public static void main(String[] args) throws Exception {
                              FileOutputStream fout = new FileOutputStream("multipage.tif");
      		RandomAccessOutputStream rout = new FileCacheRandomAccessOutputStream(fout);
      		
      		File[] files = FileUtils.listFilesMatching(new File(args[0]), args[1]);
      		BufferedImage[] images = new BufferedImage[files.length];				
      		for(int i = 0; i < files.length; i++) {
      			FileInputStream fin = new FileInputStream(files[i]);
      			BufferedImage image = javax.imageio.ImageIO.read(fin);
      			images[i] = image;
      			fin.close();
      		}				
      		ImageParam.ImageParamBuilder builder = ImageParam.getBuilder();
      		  
      		TIFFOptions tiffOptions = new TIFFOptions();
      		tiffOptions.setTiffCompression(Compression.LZW);
      		tiffOptions.setApplyPredictor(true);
      		tiffOptions.setDeflateCompressionLevel(6);
      		builder.imageOptions(tiffOptions);
      		
      		ImageParam[] param = new ImageParam[3];
      		param[0] =  builder.hasAlpha(true).build();
      		
      		tiffOptions = new TIFFOptions(tiffOptions); // Copy constructor		
      		tiffOptions.setTiffCompression(Compression.DEFLATE);
      						
      		param[1] =  builder.imageOptions(tiffOptions).build();
      		
      		tiffOptions = new TIFFOptions(tiffOptions);				
      		tiffOptions.setTiffCompression(Compression.CCITTFAX4);
      		
      		param[2] = builder.colorType(ImageColorType.BILEVLE).imageOptions(tiffOptions).build();
      		
      		TIFFTweaker.writeMultipageTIFF(rout, param, images);
    
      		rout.close();
              fout.close(); // Need to close the underlying stream explicitly!!!
      	}
      }
    

    Run "java TestTIFFTweaker images tmp-\d{1,2}.gif". The input images are the same ones used in the example to create animated GIF - the piggies. In this example, we set parameters to control how different pages of the multipage TIFF should be generated: what kind of compression to use, whether to use full color or black and white, to include transparency or not. The result is a 5 page TIFF piggie with the first and second pages as full color images and compressed using "LZW" and "Deflate". The third through fifth pages are black and white images compressed using "Group4" fax. Besides, the first and second images both have an extra sample channel for alpha, so they are transparent.

    Note: to save memory, we can also write multipage TIFF page by page!

  • Merge multiple page TIFFs

    We can also merge multiple page TIFF images together. Here is how you can merge two TIFF images:

      import java.io.FileInputStream;
      import java.io.FileNotFoundException;
      import java.io.IOException;
    
      import com.icafe4j.image.tiff.TIFFTweaker;
      import com.icafe4j.io.FileCacheRandomAccessInputStream;
      import com.icafe4j.io.FileCacheRandomAccessOutputStream;
      import com.icafe4j.io.RandomAccessInputStream;
      import com.icafe4j.io.RandomAccessOutputStream;
    
      import java.io.FileOutputStream;
    
      public class TestTiffMerge {
    
      	public static void main(String[] args) throws FileNotFoundException, IOException {
      		File image1 = new File(args[0]);
      		File image2 = new File(args[1]);
      		FileOutputStream out = new FileOutputStream(args[2]);
      		RandomAccessOutputStream dest = new FileCacheRandomAccessOutputStream(out);
      		TIFFTweaker.mergeTiffImagesEx(dest, image1, image2);
      		out.close();
      	}
      }
    

    When we call "java images\1.tif images\layers.tif merged.tif", we merged two TIFFs 1 and layers to one TIFF merged.

    You are not limited to merge two TIFFs at a time, you can actually pass in an array of TIFF image files and have "icafe" merge them all at once creating hundreds of pages in a few seconds provided you have enough memory! This version works with BitsPerPixel > 8 and is much more robust than other versions.

    Note: here are some useful tools to cross check while using "icafe" library:

    • "tweakpng" - for PNG images
    • "JPEGsnoop" - for JPEG images
    • "AsTiffTagViewer" - for TIFF images

    Metadata Manipulation and More Cool Things


Clone this wiki locally