2 * Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium
3 * Copyright (c) 2002-2014, Professor Benoit Macq
4 * Copyright (c) 2002-2007, Patrick Piscaglia, Telemis s.a.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS'
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
31 import java.util.Vector;
33 /** This class encodes one image into the J2K format,
34 * using the OpenJPEG.org library.
35 * To be able to log messages, the called must register a IJavaJ2KEncoderLogger object.
37 public class OpenJPEGJavaEncoder {
39 public interface IJavaJ2KEncoderLogger {
40 public void logEncoderMessage(String message);
41 public void logEncoderError(String message);
44 private static boolean isInitialized = false;
46 // ===== Compression parameters =============>
47 // These value may be changed for each image
48 private String[] encoder_arguments = null;
49 /** number of resolutions decompositions */
50 private int nbResolutions = -1;
51 /** the quality layers, expressed as compression rate */
52 private float[] ratioLayers = null;
53 /** the quality layers, expressed as PSNR values. This variable, if defined, has priority over the ratioLayers variable */
54 private float[] psnrLayers = null;
56 /** Contains the 8 bpp version of the image. May NOT be filled together with image16 or image24.<P>
57 * We store the 8 or 16 bpp version of the original image while the encoder uses a 32 bpp version, because <UL>
58 * <LI> the storage capacity required is smaller
59 * <LI> the transfer Java --> C will be faster
60 * <LI> the conversion byte/short ==> int will be done faster by the C
62 private byte[] image8 = null;
63 /** Contains the 16 bpp version of the image. May NOT be filled together with image8 or image24*/
64 private short[] image16 = null;
65 /** Contains the 24 bpp version of the image. May NOT be filled together with image8 or image16 */
66 private int[] image24 = null;
67 /** Holds the result of the compression, i.e. the J2K compressed bytecode */
68 private byte compressedStream[] = null;
69 /** Holds the compressed stream length, which may be smaller than compressedStream.length if this byte[] is pre-allocated */
70 private long compressedStreamLength = -1;
71 /** Holds the compressed version of the index file, returned by the encoder */
72 private byte compressedIndex[] = null;
73 /** Width and Height of the image */
74 private int width = -1;
75 private int height = -1;
76 private int depth = -1;
77 /** Tile size. We suppose the same size for the horizontal and vertical tiles.
78 * If size == -1 ==> no tiling */
79 private int tileSize = -1;
80 // <===== Compression parameters =============
82 private Vector<IJavaJ2KEncoderLogger> loggers = new Vector();
84 public OpenJPEGJavaEncoder(String openJPEGlibraryFullPathAndName, IJavaJ2KEncoderLogger messagesAndErrorsLogger) throws ExceptionInInitializerError
86 this(openJPEGlibraryFullPathAndName);
87 loggers.addElement(messagesAndErrorsLogger);
90 public OpenJPEGJavaEncoder(String openJPEGlibraryFullPathAndName) throws ExceptionInInitializerError
94 String absolutePath = (new File(openJPEGlibraryFullPathAndName)).getCanonicalPath();
95 System.load(absolutePath);
97 } catch (Throwable t) {
99 throw new ExceptionInInitializerError("OpenJPEG Java Encoder: probably impossible to find the C library");
104 public void addLogger(IJavaJ2KEncoderLogger messagesAndErrorsLogger) {
105 loggers.addElement(messagesAndErrorsLogger);
108 public void removeLogger(IJavaJ2KEncoderLogger messagesAndErrorsLogger) {
109 loggers.removeElement(messagesAndErrorsLogger);
112 /** This method compresses the given image.<P>
113 * It returns the compressed J2K codestream into the compressedStream byte[].<P>
114 * It also returns the compression index as a compressed form, into the compressedIndex byte[].<P>
115 * One of the image8, image16 or image24 arrays must be correctly initialized and filled.<P>
116 * The width, height and depth variables must be correctly filled.<P>
117 * The nbResolutions, nbLayers and if needed the float[] psnrLayers or ratioLayers must also be filled before calling this method.
119 public void encodeImageToJ2K() {
120 // Need to allocate / reallocate the compressed stream buffer ? (size = max possible size = original image size)
121 if (compressedStream== null || (compressedStream.length != width*height*depth/8)) {
122 logMessage("OpenJPEGJavaEncoder.encodeImageToJ2K: (re-)allocating " + (width*height*depth/8) + " bytes for the compressedStream");
123 compressedStream = new byte[width*height*depth/8];
126 // - number of resolutions "-n 5" : 2
127 // - size of tile "-t 512,512" : 2
129 // Image width, height, depth and pixels are directly fetched by C from the Java class
130 int nbArgs = 2 + (tileSize == -1 ? 0 : 2) + (encoder_arguments != null ? encoder_arguments.length : 0);
131 if (psnrLayers != null && psnrLayers.length>0 && psnrLayers[0] != 0)
132 // If psnrLayers is defined and doesn't just express "lossless"
134 else if (ratioLayers != null && ratioLayers.length>0 && ratioLayers[0]!=0.0)
136 String[] arguments = new String[nbArgs];
138 arguments[offset] = "-n"; arguments[offset+1] = "" + nbResolutions; offset += 2;
140 arguments[offset++] = "-t";
141 arguments[offset++] = "" + tileSize + "," + tileSize;
143 // If PSNR layers are defined, use them to encode the images
144 if (psnrLayers != null && psnrLayers.length>0 && psnrLayers[0]!=-1) {
145 arguments[offset++] = "-q";
147 for (int i=0; i<psnrLayers.length; i++)
148 s += psnrLayers[i] + ",";
149 arguments[offset++] = s.substring(0, s.length()-1);
150 } else if (ratioLayers != null && ratioLayers.length>0 && ratioLayers[0]!=0.0) {
151 // Specify quality ratioLayers, as compression ratios
152 arguments[offset++] = "-r";
154 for (int i=0; i<ratioLayers.length; i++)
155 s += ratioLayers[i] + ",";
156 arguments[offset++] = s.substring(0, s.length()-1);
158 if (encoder_arguments != null) {
159 for (int i=0; i<encoder_arguments.length; i++) {
160 arguments[i+offset] = encoder_arguments[i];
163 logMessage("Encoder additional arguments = " + arrayToString(arguments));
164 long startTime = (new java.util.Date()).getTime();
165 compressedStreamLength = internalEncodeImageToJ2K(arguments);
166 logMessage("compression time = " + ((new java.util.Date()).getTime() - startTime) + " msec");
170 * Fills the compressedStream byte[] and the compressedIndex byte[]
171 * @return the codestream length.
173 private native long internalEncodeImageToJ2K(String[] parameters);
175 /** Image depth in bpp */
176 public int getDepth() {
180 /** Image depth in bpp */
181 public void setDepth(int depth) {
185 /** Image height in pixels */
186 public int getHeight() {
190 /** Image height in pixels */
191 public void setHeight(int height) {
192 this.height = height;
195 /** This method must be called in depth in [9,16].
196 * @param an array of shorts, containing width*height values
198 public void setImage16(short[] image16) {
199 this.image16 = image16;
202 /** This method must be called in depth in [17,24] for RGB images.
203 * @param an array of int, containing width*height values
205 public void setImage24(int[] image24) {
206 this.image24 = image24;
209 /** This method must be called in depth in [1,8].
210 * @param an array of bytes, containing width*height values
212 public void setImage8(byte[] image8) {
213 this.image8 = image8;
216 /** Return the ratioLayers, i.e. the compression ratio for each quality layer.
217 * If the last value is 0.0, last layer is lossless compressed.
219 public float[] getRatioLayers() {
224 * sets the quality layers.
225 * At least one level.
226 * Each level is expressed as a compression ratio (float).
227 * If the last value is 0.0, the last layer will be losslessly compressed
229 public void setRatioLayers(float[] layers) {
230 this.ratioLayers = layers;
233 /** Return the PSNR Layers, i.e. the target PSNR for each quality layer.
234 * If the last value is -1, last layer is lossless compressed.
236 public float[] getPsnrLayers() {
241 * sets the quality layers.
242 * At least one level.
243 * Each level is expressed as a target PSNR (float).
244 * If the last value is -1, the last layer will be losslessly compressed
246 public void setPsnrLayers(float[] layers) {
247 this.psnrLayers = layers;
250 /** Set the number of resolutions that must be created */
251 public void setNbResolutions(int nbResolutions) {
252 this.nbResolutions = nbResolutions;
255 public int getWidth() {
259 /** Width of the image, in pixels */
260 public void setWidth(int width) {
264 /** Return the compressed index file.
267 public byte[] getCompressedIndex() {
268 return compressedIndex;
271 public void setCompressedIndex(byte[] index) {
272 compressedIndex = index;
275 public byte[] getCompressedStream() {
276 return compressedStream;
279 public void reset() {
286 compressedStream = null;
287 compressedIndex = null;
293 public short[] getImage16() {
297 public int[] getImage24() {
301 public byte[] getImage8() {
305 /** Sets the size of the tiles. We assume square tiles */
306 public void setTileSize(int tileSize) {
307 this.tileSize = tileSize;
310 /** Contains all the encoding arguments other than the input/output file, compression ratio, tile size */
311 public void setEncoderArguments(String[] argumentsForTheEncoder) {
312 encoder_arguments = argumentsForTheEncoder;
315 public void logMessage(String message) {
316 for (IJavaJ2KEncoderLogger logger:loggers)
317 logger.logEncoderMessage(message);
320 public void logError(String error) {
321 for (IJavaJ2KEncoderLogger logger:loggers)
322 logger.logEncoderError(error);
325 public long getCompressedStreamLength() {
326 return compressedStreamLength;
329 private String arrayToString(String[] array) {
332 StringBuffer sb = new StringBuffer();
333 for (int i=0; i<array.length; i++)
334 sb.append(array[i]).append(" ");
335 sb.delete(sb.length()-1, sb.length());
336 return sb.toString();