Fix line endings (CRLF -> LF)
[openjpeg.git] / wrapping / java / openjp2 / java-sources / org / openJpeg / OpenJPEGJavaEncoder.java
1 /*
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.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
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.
15  *
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.
27  */  
28 package org.openJpeg;
29
30 import java.io.File;
31 import java.util.Vector;
32
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.
36  */
37 public class OpenJPEGJavaEncoder {
38
39         public interface IJavaJ2KEncoderLogger {
40                 public void logEncoderMessage(String message);
41                 public void logEncoderError(String message);
42         }
43         
44     private static boolean isInitialized = false;
45     
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;
55         
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
61          * </UL>*/
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 =============
81     
82     private Vector<IJavaJ2KEncoderLogger> loggers = new Vector();
83
84     public OpenJPEGJavaEncoder(String openJPEGlibraryFullPathAndName, IJavaJ2KEncoderLogger messagesAndErrorsLogger) throws ExceptionInInitializerError
85     {
86         this(openJPEGlibraryFullPathAndName);
87         loggers.addElement(messagesAndErrorsLogger);
88     }
89
90     public OpenJPEGJavaEncoder(String openJPEGlibraryFullPathAndName) throws ExceptionInInitializerError
91     {
92         if (!isInitialized) {
93                 try {
94                         String absolutePath = (new File(openJPEGlibraryFullPathAndName)).getCanonicalPath();
95                         System.load(absolutePath);
96                         isInitialized = true;
97                 } catch (Throwable t) {
98                         t.printStackTrace();
99                         throw new ExceptionInInitializerError("OpenJPEG Java Encoder: probably impossible to find the C library");
100                 }
101         }
102     }
103     
104     public void addLogger(IJavaJ2KEncoderLogger messagesAndErrorsLogger) {
105         loggers.addElement(messagesAndErrorsLogger);
106     }
107     
108     public void removeLogger(IJavaJ2KEncoderLogger messagesAndErrorsLogger) {
109         loggers.removeElement(messagesAndErrorsLogger);
110     }
111     
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.
118      */
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];
124                 }
125                 // Arguments = 
126                 // - number of resolutions "-n 5" : 2
127                 // - size of tile "-t 512,512" : 2
128                 // 
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"
133                         nbArgs += 2;
134                 else if (ratioLayers != null && ratioLayers.length>0 && ratioLayers[0]!=0.0)
135                         nbArgs += 2;
136                 String[] arguments = new String[nbArgs];
137                 int offset = 0;
138                 arguments[offset] = "-n"; arguments[offset+1] = "" + nbResolutions; offset += 2;
139                 if (tileSize!= -1) {
140                         arguments[offset++] = "-t"; 
141                         arguments[offset++] = "" + tileSize + "," + tileSize;
142                 }
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";
146                         String s = "";
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";
153                         String s = "";
154                         for (int i=0; i<ratioLayers.length; i++)
155                                 s += ratioLayers[i] + ",";
156                         arguments[offset++] = s.substring(0, s.length()-1);
157                 }
158                 if (encoder_arguments != null) {
159                         for (int i=0; i<encoder_arguments.length; i++) {
160                                 arguments[i+offset] = encoder_arguments[i];
161                         }
162                 }
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");
167     }
168     
169     /** 
170      * Fills the compressedStream byte[] and the compressedIndex byte[]
171      * @return the codestream length.
172      */
173     private native long internalEncodeImageToJ2K(String[] parameters);
174
175     /** Image depth in bpp */
176         public int getDepth() {
177                 return depth;
178         }
179
180     /** Image depth in bpp */
181         public void setDepth(int depth) {
182                 this.depth = depth;
183         }
184
185         /** Image height in pixels  */
186         public int getHeight() {
187                 return height;
188         }
189
190         /** Image height in pixels  */
191         public void setHeight(int height) {
192                 this.height = height;
193         }
194
195         /** This method must be called in depth in [9,16].
196          * @param an array of shorts, containing width*height values
197          */
198         public void setImage16(short[] image16) {
199                 this.image16 = image16;
200         }
201
202         /** This method must be called in depth in [17,24] for RGB images.
203          * @param an array of int, containing width*height values
204          */
205         public void setImage24(int[] image24) {
206                 this.image24 = image24;
207         }
208
209         /** This method must be called in depth in [1,8].
210          * @param an array of bytes, containing width*height values
211          */
212         public void setImage8(byte[] image8) {
213                 this.image8 = image8;
214         }
215
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.
218          */
219         public float[] getRatioLayers() {
220                 return ratioLayers;
221         }
222
223         /**
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
228          */
229         public void setRatioLayers(float[] layers) {
230                 this.ratioLayers = layers;
231         }
232
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.
235          */
236         public float[] getPsnrLayers() {
237                 return psnrLayers;
238         }
239
240         /**
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
245          */
246         public void setPsnrLayers(float[] layers) {
247                 this.psnrLayers = layers;
248         }
249
250         /** Set the number of resolutions that must be created */
251         public void setNbResolutions(int nbResolutions) {
252                 this.nbResolutions = nbResolutions;
253         }
254
255         public int getWidth() {
256                 return width;
257         }
258
259         /** Width of the image, in pixels */
260         public void setWidth(int width) {
261                 this.width = width;
262         }
263
264         /** Return the compressed index file.
265          * Syntax: TODO PP:
266          */
267         public byte[] getCompressedIndex() {
268                 return compressedIndex;
269         }
270         
271         public void setCompressedIndex(byte[] index) {
272                 compressedIndex = index;
273         }
274
275         public byte[] getCompressedStream() {
276                 return compressedStream;
277         }
278
279         public void reset() {
280                 nbResolutions = -1;
281                 ratioLayers = null;
282                 psnrLayers = null;
283                 image8 = null;
284                 image16 = null;
285                 image24 = null;
286                 compressedStream = null;
287             compressedIndex = null;
288             width = -1;
289             height = -1;
290             depth = -1;
291         }
292
293         public short[] getImage16() {
294                 return image16;
295         }
296
297         public int[] getImage24() {
298                 return image24;
299         }
300
301         public byte[] getImage8() {
302                 return image8;
303         }
304         
305         /** Sets the size of the tiles. We assume square tiles */
306         public void setTileSize(int tileSize) {
307                 this.tileSize = tileSize;
308         }
309         
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;
313         }
314
315         public void logMessage(String message) {
316                 for (IJavaJ2KEncoderLogger logger:loggers)
317                         logger.logEncoderMessage(message);
318         }
319         
320         public void logError(String error) {
321                 for (IJavaJ2KEncoderLogger logger:loggers)
322                         logger.logEncoderError(error);
323         }
324
325         public long getCompressedStreamLength() {
326                 return compressedStreamLength;
327         }
328         
329         private String arrayToString(String[] array) {
330                 if (array == null)
331                         return "NULL";
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();
337         }
338 }