First import of JAVAOpenJPEG, a Java wrapper of OpenJPEG, developed by Patrick Piscag...
[openjpeg.git] / JavaOpenJPEG / java sources / org / openJpeg / OpenJPEGJavaEncoder.java
1 /* \r
2  * $Id: $ \r
3  * \r
4  * Copyright (c) 1999-2007 Telemis SA. All Rights Reserved \r
5  * \r
6  * Author: Patrick Piscaglia, Telemis s.a.\r
7  */ \r
8 package org.openJpeg;\r
9 \r
10 import java.io.File;\r
11 import java.util.Vector;\r
12 \r
13 /** This class encodes one image into the J2K format, \r
14  * using the OpenJPEG.org library.\r
15  * To be able to log messages, the called must register a IJavaJ2KEncoderLogger object.\r
16  */\r
17 public class OpenJPEGJavaEncoder {\r
18 \r
19         public interface IJavaJ2KEncoderLogger {\r
20                 public void logEncoderMessage(String message);\r
21                 public void logEncoderError(String message);\r
22         }\r
23         \r
24     private static boolean isInitialized = false;\r
25     \r
26         // ===== Compression parameters =============>\r
27         // These value may be changed for each image\r
28     private String[] encoder_arguments = null;\r
29         /** number of resolutions decompositions */\r
30         private int nbResolutions = -1;\r
31         /** the quality layers, expressed as compression rate */\r
32         private float[] ratioLayers = null;\r
33         /** the quality layers, expressed as PSNR values. This variable, if defined, has priority over the ratioLayers variable */\r
34         private float[] psnrLayers = null;\r
35         \r
36         /** Contains the 8 bpp version of the image. May NOT be filled together with image16 or image24.<P>\r
37          * We store the 8 or 16 bpp version of the original image while the encoder uses a 32 bpp version, because <UL>\r
38          * <LI> the storage capacity required is smaller\r
39          * <LI> the transfer Java --> C will be faster\r
40          * <LI> the conversion byte/short ==> int will be done faster by the C\r
41          * </UL>*/\r
42         private byte[] image8 = null;\r
43         /** Contains the 16 bpp version of the image. May NOT be filled together with image8 or image24*/\r
44         private short[] image16 = null;\r
45         /** Contains the 24 bpp version of the image. May NOT be filled together with image8 or image16 */\r
46         private int[] image24 = null;\r
47         /** Holds the result of the compression, i.e. the J2K compressed bytecode */\r
48     private byte compressedStream[] = null;\r
49     /** Holds the compressed stream length, which may be smaller than compressedStream.length if this byte[] is pre-allocated */\r
50     private long compressedStreamLength = -1;\r
51     /** Holds the compressed version of the index file, returned by the encoder */\r
52     private byte compressedIndex[] = null;\r
53     /** Width and Height of the image */\r
54     private int width = -1;\r
55     private int height = -1;\r
56     private int depth = -1;\r
57     /** Tile size. We suppose the same size for the horizontal and vertical tiles.\r
58      * If size == -1 ==> no tiling */\r
59     private int tileSize = -1;\r
60     // <===== Compression parameters =============\r
61     \r
62     private Vector<IJavaJ2KEncoderLogger> loggers = new Vector();\r
63 \r
64     public OpenJPEGJavaEncoder(String openJPEGlibraryFullPathAndName, IJavaJ2KEncoderLogger messagesAndErrorsLogger) throws ExceptionInInitializerError\r
65     {\r
66         this(openJPEGlibraryFullPathAndName);\r
67         loggers.addElement(messagesAndErrorsLogger);\r
68     }\r
69 \r
70     public OpenJPEGJavaEncoder(String openJPEGlibraryFullPathAndName) throws ExceptionInInitializerError\r
71     {\r
72         if (!isInitialized) {\r
73                 try {\r
74                         String absolutePath = (new File(openJPEGlibraryFullPathAndName)).getCanonicalPath();\r
75                         System.load(absolutePath);\r
76                         isInitialized = true;\r
77                 } catch (Throwable t) {\r
78                         t.printStackTrace();\r
79                         throw new ExceptionInInitializerError("OpenJPEG Java Encoder: probably impossible to find the C library");\r
80                 }\r
81         }\r
82     }\r
83     \r
84     public void addLogger(IJavaJ2KEncoderLogger messagesAndErrorsLogger) {\r
85         loggers.addElement(messagesAndErrorsLogger);\r
86     }\r
87     \r
88     public void removeLogger(IJavaJ2KEncoderLogger messagesAndErrorsLogger) {\r
89         loggers.removeElement(messagesAndErrorsLogger);\r
90     }\r
91     \r
92     /** This method compresses the given image.<P>\r
93      * It returns the compressed J2K codestream into the compressedStream byte[].<P>\r
94      * It also returns the compression index as a compressed form, into the compressedIndex byte[].<P>\r
95      * One of the image8, image16 or image24 arrays must be correctly initialized and filled.<P>\r
96      * The width, height and depth variables must be correctly filled.<P>\r
97      * The nbResolutions, nbLayers and if needed the float[] psnrLayers or ratioLayers must also be filled before calling this method.\r
98      */\r
99     public void encodeImageToJ2K() {\r
100                 // Need to allocate / reallocate the compressed stream buffer ? (size = max possible size = original image size)\r
101                 if (compressedStream== null || (compressedStream.length != width*height*depth/8)) {\r
102                         logMessage("OpenJPEGJavaEncoder.encodeImageToJ2K: (re-)allocating " + (width*height*depth/8) + " bytes for the compressedStream");\r
103                         compressedStream = new byte[width*height*depth/8];\r
104                 }\r
105                 // Arguments = \r
106                 // - number of resolutions "-n 5" : 2\r
107                 // - size of tile "-t 512,512" : 2\r
108                 // \r
109                 // Image width, height, depth and pixels are directly fetched by C from the Java class\r
110                 int nbArgs = 2 + (tileSize == -1 ? 0 : 2) + (encoder_arguments != null ? encoder_arguments.length : 0);\r
111                 if (psnrLayers != null && psnrLayers.length>0 && psnrLayers[0] != 0)\r
112                         // If psnrLayers is defined and doesn't just express "lossless"\r
113                         nbArgs += 2;\r
114                 else if (ratioLayers != null && ratioLayers.length>0 && ratioLayers[0]!=0.0)\r
115                         nbArgs += 2;\r
116                 String[] arguments = new String[nbArgs];\r
117                 int offset = 0;\r
118                 arguments[offset] = "-n"; arguments[offset+1] = "" + nbResolutions; offset += 2;\r
119                 if (tileSize!= -1) {\r
120                         arguments[offset++] = "-t"; \r
121                         arguments[offset++] = "" + tileSize + "," + tileSize;\r
122                 }\r
123                 // If PSNR layers are defined, use them to encode the images\r
124                 if (psnrLayers != null && psnrLayers.length>0 && psnrLayers[0]!=-1) {\r
125                         arguments[offset++] = "-q";\r
126                         String s = "";\r
127                         for (int i=0; i<psnrLayers.length; i++)\r
128                                 s += psnrLayers[i] + ",";\r
129                         arguments[offset++] = s.substring(0, s.length()-1);\r
130                 } else if (ratioLayers != null && ratioLayers.length>0 && ratioLayers[0]!=0.0) {\r
131                         // Specify quality ratioLayers, as compression ratios\r
132                         arguments[offset++] = "-r";\r
133                         String s = "";\r
134                         for (int i=0; i<ratioLayers.length; i++)\r
135                                 s += ratioLayers[i] + ",";\r
136                         arguments[offset++] = s.substring(0, s.length()-1);\r
137                 }\r
138                 if (encoder_arguments != null) {\r
139                         for (int i=0; i<encoder_arguments.length; i++) {\r
140                                 arguments[i+offset] = encoder_arguments[i];\r
141                         }\r
142                 }\r
143                 logMessage("Encoder additional arguments = " + arrayToString(arguments));\r
144                 long startTime = (new java.util.Date()).getTime();\r
145                 compressedStreamLength = internalEncodeImageToJ2K(arguments);\r
146                 logMessage("compression time = " + ((new java.util.Date()).getTime() - startTime) + " msec");\r
147     }\r
148     \r
149     /** \r
150      * Fills the compressedStream byte[] and the compressedIndex byte[]\r
151      * @return the codestream length.\r
152      */\r
153     private native long internalEncodeImageToJ2K(String[] parameters);\r
154 \r
155     /** Image depth in bpp */\r
156         public int getDepth() {\r
157                 return depth;\r
158         }\r
159 \r
160     /** Image depth in bpp */\r
161         public void setDepth(int depth) {\r
162                 this.depth = depth;\r
163         }\r
164 \r
165         /** Image height in pixels  */\r
166         public int getHeight() {\r
167                 return height;\r
168         }\r
169 \r
170         /** Image height in pixels  */\r
171         public void setHeight(int height) {\r
172                 this.height = height;\r
173         }\r
174 \r
175         /** This method must be called in depth in [9,16].\r
176          * @param an array of shorts, containing width*height values\r
177          */\r
178         public void setImage16(short[] image16) {\r
179                 this.image16 = image16;\r
180         }\r
181 \r
182         /** This method must be called in depth in [17,24] for RGB images.\r
183          * @param an array of int, containing width*height values\r
184          */\r
185         public void setImage24(int[] image24) {\r
186                 this.image24 = image24;\r
187         }\r
188 \r
189         /** This method must be called in depth in [1,8].\r
190          * @param an array of bytes, containing width*height values\r
191          */\r
192         public void setImage8(byte[] image8) {\r
193                 this.image8 = image8;\r
194         }\r
195 \r
196         /** Return the ratioLayers, i.e. the compression ratio for each quality layer.\r
197          * If the last value is 0.0, last layer is lossless compressed.\r
198          */\r
199         public float[] getRatioLayers() {\r
200                 return ratioLayers;\r
201         }\r
202 \r
203         /**\r
204          * sets the quality layers.\r
205          * At least one level.\r
206          * Each level is expressed as a compression ratio (float).\r
207          * If the last value is 0.0, the last layer will be losslessly compressed\r
208          */\r
209         public void setRatioLayers(float[] layers) {\r
210                 this.ratioLayers = layers;\r
211         }\r
212 \r
213         /** Return the PSNR Layers, i.e. the target PSNR for each quality layer.\r
214          * If the last value is -1, last layer is lossless compressed.\r
215          */\r
216         public float[] getPsnrLayers() {\r
217                 return psnrLayers;\r
218         }\r
219 \r
220         /**\r
221          * sets the quality layers.\r
222          * At least one level.\r
223          * Each level is expressed as a target PSNR (float).\r
224          * If the last value is -1, the last layer will be losslessly compressed\r
225          */\r
226         public void setPsnrLayers(float[] layers) {\r
227                 this.psnrLayers = layers;\r
228         }\r
229 \r
230         /** Set the number of resolutions that must be created */\r
231         public void setNbResolutions(int nbResolutions) {\r
232                 this.nbResolutions = nbResolutions;\r
233         }\r
234 \r
235         public int getWidth() {\r
236                 return width;\r
237         }\r
238 \r
239         /** Width of the image, in pixels */\r
240         public void setWidth(int width) {\r
241                 this.width = width;\r
242         }\r
243 \r
244         /** Return the compressed index file.\r
245          * Syntax: TODO PP:\r
246          */\r
247         public byte[] getCompressedIndex() {\r
248                 return compressedIndex;\r
249         }\r
250         \r
251         public void setCompressedIndex(byte[] index) {\r
252                 compressedIndex = index;\r
253         }\r
254 \r
255         public byte[] getCompressedStream() {\r
256                 return compressedStream;\r
257         }\r
258 \r
259         public void reset() {\r
260                 nbResolutions = -1;\r
261                 ratioLayers = null;\r
262                 psnrLayers = null;\r
263                 image8 = null;\r
264                 image16 = null;\r
265                 image24 = null;\r
266                 compressedStream = null;\r
267             compressedIndex = null;\r
268             width = -1;\r
269             height = -1;\r
270             depth = -1;\r
271         }\r
272 \r
273         public short[] getImage16() {\r
274                 return image16;\r
275         }\r
276 \r
277         public int[] getImage24() {\r
278                 return image24;\r
279         }\r
280 \r
281         public byte[] getImage8() {\r
282                 return image8;\r
283         }\r
284         \r
285         /** Sets the size of the tiles. We assume square tiles */\r
286         public void setTileSize(int tileSize) {\r
287                 this.tileSize = tileSize;\r
288         }\r
289         \r
290         /** Contains all the encoding arguments other than the input/output file, compression ratio, tile size */\r
291         public void setEncoderArguments(String[] argumentsForTheEncoder) {\r
292                 encoder_arguments = argumentsForTheEncoder;\r
293         }\r
294 \r
295         public void logMessage(String message) {\r
296                 for (IJavaJ2KEncoderLogger logger:loggers)\r
297                         logger.logEncoderMessage(message);\r
298         }\r
299         \r
300         public void logError(String error) {\r
301                 for (IJavaJ2KEncoderLogger logger:loggers)\r
302                         logger.logEncoderError(error);\r
303         }\r
304 \r
305         public long getCompressedStreamLength() {\r
306                 return compressedStreamLength;\r
307         }\r
308         \r
309         private String arrayToString(String[] array) {\r
310                 if (array == null)\r
311                         return "NULL";\r
312                 StringBuffer sb = new StringBuffer();\r
313                 for (int i=0; i<array.length; i++)\r
314                         sb.append(array[i]).append(" ");\r
315                 sb.delete(sb.length()-1, sb.length());\r
316                 return sb.toString();\r
317         }\r
318 }\r