4 * Copyright (c) 1999-2007 Telemis SA. All Rights Reserved
\r
6 * Author: Patrick Piscaglia, Telemis s.a.
\r
8 package org.openJpeg;
\r
10 import java.io.File;
\r
11 import java.util.Vector;
\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
17 public class OpenJPEGJavaEncoder {
\r
19 public interface IJavaJ2KEncoderLogger {
\r
20 public void logEncoderMessage(String message);
\r
21 public void logEncoderError(String message);
\r
24 private static boolean isInitialized = false;
\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
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
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
62 private Vector<IJavaJ2KEncoderLogger> loggers = new Vector();
\r
64 public OpenJPEGJavaEncoder(String openJPEGlibraryFullPathAndName, IJavaJ2KEncoderLogger messagesAndErrorsLogger) throws ExceptionInInitializerError
\r
66 this(openJPEGlibraryFullPathAndName);
\r
67 loggers.addElement(messagesAndErrorsLogger);
\r
70 public OpenJPEGJavaEncoder(String openJPEGlibraryFullPathAndName) throws ExceptionInInitializerError
\r
72 if (!isInitialized) {
\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
84 public void addLogger(IJavaJ2KEncoderLogger messagesAndErrorsLogger) {
\r
85 loggers.addElement(messagesAndErrorsLogger);
\r
88 public void removeLogger(IJavaJ2KEncoderLogger messagesAndErrorsLogger) {
\r
89 loggers.removeElement(messagesAndErrorsLogger);
\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
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
106 // - number of resolutions "-n 5" : 2
\r
107 // - size of tile "-t 512,512" : 2
\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
114 else if (ratioLayers != null && ratioLayers.length>0 && ratioLayers[0]!=0.0)
\r
116 String[] arguments = new String[nbArgs];
\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
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
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
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
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
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
150 * Fills the compressedStream byte[] and the compressedIndex byte[]
\r
151 * @return the codestream length.
\r
153 private native long internalEncodeImageToJ2K(String[] parameters);
\r
155 /** Image depth in bpp */
\r
156 public int getDepth() {
\r
160 /** Image depth in bpp */
\r
161 public void setDepth(int depth) {
\r
162 this.depth = depth;
\r
165 /** Image height in pixels */
\r
166 public int getHeight() {
\r
170 /** Image height in pixels */
\r
171 public void setHeight(int height) {
\r
172 this.height = height;
\r
175 /** This method must be called in depth in [9,16].
\r
176 * @param an array of shorts, containing width*height values
\r
178 public void setImage16(short[] image16) {
\r
179 this.image16 = image16;
\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
185 public void setImage24(int[] image24) {
\r
186 this.image24 = image24;
\r
189 /** This method must be called in depth in [1,8].
\r
190 * @param an array of bytes, containing width*height values
\r
192 public void setImage8(byte[] image8) {
\r
193 this.image8 = image8;
\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
199 public float[] getRatioLayers() {
\r
200 return ratioLayers;
\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
209 public void setRatioLayers(float[] layers) {
\r
210 this.ratioLayers = layers;
\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
216 public float[] getPsnrLayers() {
\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
226 public void setPsnrLayers(float[] layers) {
\r
227 this.psnrLayers = layers;
\r
230 /** Set the number of resolutions that must be created */
\r
231 public void setNbResolutions(int nbResolutions) {
\r
232 this.nbResolutions = nbResolutions;
\r
235 public int getWidth() {
\r
239 /** Width of the image, in pixels */
\r
240 public void setWidth(int width) {
\r
241 this.width = width;
\r
244 /** Return the compressed index file.
\r
247 public byte[] getCompressedIndex() {
\r
248 return compressedIndex;
\r
251 public void setCompressedIndex(byte[] index) {
\r
252 compressedIndex = index;
\r
255 public byte[] getCompressedStream() {
\r
256 return compressedStream;
\r
259 public void reset() {
\r
260 nbResolutions = -1;
\r
261 ratioLayers = null;
\r
266 compressedStream = null;
\r
267 compressedIndex = null;
\r
273 public short[] getImage16() {
\r
277 public int[] getImage24() {
\r
281 public byte[] getImage8() {
\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
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
295 public void logMessage(String message) {
\r
296 for (IJavaJ2KEncoderLogger logger:loggers)
\r
297 logger.logEncoderMessage(message);
\r
300 public void logError(String error) {
\r
301 for (IJavaJ2KEncoderLogger logger:loggers)
\r
302 logger.logEncoderError(error);
\r
305 public long getCompressedStreamLength() {
\r
306 return compressedStreamLength;
\r
309 private String arrayToString(String[] array) {
\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