├── .gitignore
├── res
└── balloon.jp2
├── src
└── main
│ ├── resources
│ └── .gitignore
│ ├── c
│ ├── nl_kb_jp2_JP2Reader.h
│ └── nl_kb_jp2_JP2Reader.c
│ └── java
│ └── nl
│ └── kb
│ └── jp2
│ ├── JPEG2000Image.java
│ └── JP2Reader.java
├── Makefile
├── README.md
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.idea
3 | target
4 |
--------------------------------------------------------------------------------
/res/balloon.jp2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KBNLresearch/jp2view/master/res/balloon.jp2
--------------------------------------------------------------------------------
/src/main/resources/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
5 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: clean compile maven
2 |
3 | clean:
4 | rm -f src/main/resources/libkbjp2.so
5 |
6 | compile:
7 | mkdir -p src/main/resources
8 | gcc -shared -o src/main/resources/libkbjp2.so -I$(JAVA_HOME)/include/ -I$(JAVA_HOME)/include/linux/ -I${OPJ_INC} src/main/c/nl_kb_jp2_JP2Reader.c -fPIC -lopenjp2
9 |
10 | maven:
11 | mvn clean install package assembly:single
12 |
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | jp2view
2 | =======
3 |
4 | first version, tested on [openjpeg2.0](http://code.google.com/p/openjpeg/downloads/detail?name=openjpeg-2.0.0.tar.gz&can=2&q=) with openjdk1.7
5 |
6 | Build and install openjpeg
7 | -----
8 |
9 | 1. cd /your/path/to/openjpeg2.0
10 |
11 | 2. sudo apt get install cmake make
12 |
13 | 3. cmake .
14 |
15 | 4. make
16 |
17 | 5. sudo make install
18 |
19 | 6. sudo ldconfig (alternatively set LD_LIBRARY_PATH to location of libopenjp2.so)
20 |
21 | Build this project
22 | ----
23 |
24 | (prerequisites: make and maven2+)
25 |
26 | 1. cd /your/path/to/jp2view
27 |
28 | 2. export OPJ_INC=/your/includepath/openjpeg2.0 (i.e.: /usr/local/include/openjpeg-2.0/)
29 |
30 | 3. export JAVA_HOME=/your/jdkpath (i.e.: /usr/lib/jvm/java-1.7.0-openjdk-amd64)
31 |
32 | 4. make
33 |
34 | 5. cd target
35 |
36 | 6. java -jar jp2-0.1.0-jar-with-dependencies.jar nl.kb.JP2Reader ../res/balloon.jp2 4
37 |
38 | If all went well, there are now 5 jpeg files in your target directory:
39 | - test.jpg file (full res balloon)
40 | - test_region.jpg
41 | - test_region_scaled_up.jpg
42 | - test_region_scaled_down.jpg
43 | - test_full_scaled.jpg
44 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | nl.kb
8 | jp2
9 | 0.1.0
10 | jar
11 |
12 |
13 |
14 |
15 | commons-io
16 | commons-io
17 | 2.7
18 |
19 |
20 |
21 |
22 |
23 |
24 | maven-assembly-plugin
25 |
26 |
27 |
28 | nl.kb.jp2.JP2Reader
29 |
30 |
31 |
32 | jar-with-dependencies
33 |
34 |
35 |
36 |
37 | org.apache.maven.plugins
38 | maven-compiler-plugin
39 | 2.5.1
40 |
41 | true
42 | true
43 | 1.7
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/main/c/nl_kb_jp2_JP2Reader.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013, Koninklijke Bibliotheek - Nationale bibliotheek van Nederland
3 | *
4 | * All rights reserved.
5 | *
6 | * Redistribution and use in source and binary forms, with or without modification,
7 | * are permitted provided that the following conditions are met:
8 | *
9 | * * Redistributions of source code must retain the above copyright notice, this
10 | * list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above copyright notice,
12 | * this list of conditions and the following disclaimer in the documentation
13 | * and/or other materials provided with the distribution.
14 | * * Neither the name of the Koninklijke Bibliotheek nor the names of its contributors
15 | * may be used to endorse or promote products derived from this software without
16 | * specific prior written permission.
17 | *
18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 | *
30 | */
31 |
32 | #include
33 |
34 | #ifndef _Included_nl_kb_jp2_JP2Reader
35 | #define _Included_nl_kb_jp2_JP2Reader
36 | #ifdef __cplusplus
37 | extern "C" {
38 | #endif
39 |
40 | /*
41 | * Class: nl_kb_jp2_JP2Reader
42 | * Method: getJp2Specs
43 | * Signature: (Ljava/lang/String;)[I
44 | */
45 | JNIEXPORT jintArray JNICALL Java_nl_kb_jp2_JP2Reader_getJp2Specs
46 | (JNIEnv *, jclass, jstring);
47 |
48 | /*
49 | * Class: nl_kb_jp2_JP2Reader
50 | * Method: getJp2Specs
51 | * Signature: (Ljava/lang/String;)[I
52 | */
53 | JNIEXPORT jintArray JNICALL JNICALL Java_nl_kb_jp2_JP2Reader_getTile
54 | (JNIEnv *, jclass, jstring, jint, jint, jobjectArray);
55 |
56 |
57 | #ifdef __cplusplus
58 | }
59 | #endif
60 | #endif
61 |
--------------------------------------------------------------------------------
/src/main/java/nl/kb/jp2/JPEG2000Image.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013, Koninklijke Bibliotheek - Nationale bibliotheek van Nederland
3 | *
4 | * All rights reserved.
5 | *
6 | * Redistribution and use in source and binary forms, with or without modification,
7 | * are permitted provided that the following conditions are met:
8 | *
9 | * * Redistributions of source code must retain the above copyright notice, this
10 | * list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above copyright notice,
12 | * this list of conditions and the following disclaimer in the documentation
13 | * and/or other materials provided with the distribution.
14 | * * Neither the name of the Koninklijke Bibliotheek nor the names of its contributors
15 | * may be used to endorse or promote products derived from this software without
16 | * specific prior written permission.
17 | *
18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 | *
30 | */
31 |
32 | package nl.kb.jp2;
33 |
34 | import java.util.ArrayList;
35 | import java.util.List;
36 |
37 | public class JPEG2000Image {
38 |
39 | public static final int HEADER_FAIL = 0;
40 | public static final int HEADER_SUCCESS = 1;
41 | private int status;
42 | private int width;
43 | private int height;
44 | private int tilesX;
45 | private int tilesY;
46 | private int tileW;
47 | private int tileH;
48 | private int maxReduction;
49 | private int numCompositions;
50 | private String filename;
51 |
52 | public JPEG2000Image(String filename, final int[] arySpex) {
53 | this.filename = filename;
54 | status = arySpex[0];
55 | width = arySpex[1];
56 | height = arySpex[2];
57 | tilesX = arySpex[3];
58 | tilesY = arySpex[4];
59 | tileW = arySpex[5];
60 | tileH = arySpex[6];
61 | maxReduction = arySpex[7];
62 | numCompositions = arySpex[8];
63 | }
64 |
65 | public boolean headerLoaded() {
66 | return status == HEADER_SUCCESS;
67 | }
68 |
69 | public int getBestReductionFactorForScale(double scale) {
70 | int redux = maxReduction - 1;
71 | double scaleAtRedux = reduce(1.0, redux);
72 | while(scale > scaleAtRedux && redux > 0) {
73 | scaleAtRedux *= 2.0;
74 | redux--;
75 | }
76 |
77 | return redux;
78 | }
79 |
80 | public int getTilesX() {
81 | return tilesX;
82 | }
83 |
84 | public int getTilesY() {
85 | return tilesY;
86 | }
87 |
88 | public int getNumCompositions() {
89 | return numCompositions;
90 | }
91 |
92 | public int getMaxReduction() {
93 | return maxReduction - 1;
94 | }
95 |
96 | public static double reduce(double num, int reduction) {
97 | for(int i = 0; i < reduction; ++i) {
98 | num /= 2;
99 | }
100 | return num;
101 | }
102 |
103 | public static int reduce(int num, int reduction) {
104 | for(int i = 0; i < reduction; ++i) {
105 | num = (int) Math.ceil(((double) num) / 2.0d);
106 | }
107 | return num;
108 | }
109 |
110 | public int getWidth(int reduction) {
111 | return reduce(width, reduction);
112 | }
113 |
114 | public int getHeight(int reduction) {
115 | return reduce(height, reduction);
116 | }
117 |
118 | public int getTileW(int reduction) {
119 | return reduce(tileW, reduction);
120 | }
121 |
122 | public int getTileH(int reduction) {
123 | return reduce(tileH, reduction);
124 | }
125 |
126 | public String getFilename() {
127 | return filename;
128 | }
129 |
130 | private List filterTilesDim(int start, int finish, int tiles, int tsiz, int reduction) {
131 | int siz = reduce(tsiz, reduction);
132 | List indices = new ArrayList();
133 | for(int i = 0; i < tiles; ++i) {
134 | int cur1 = i * siz;
135 | int cur2 = (i+1) * siz;
136 | if( (start >= cur1 && start <= cur2) ||
137 | (finish >= cur1 && finish <= cur2) ||
138 | (start <= cur1 && finish >= cur2)) {
139 | indices.add(i);
140 | }
141 | }
142 | return indices;
143 | }
144 |
145 | public List filterTilesX(int x, int w, int reduction) {
146 | return filterTilesDim(x, x + w, tilesX, tileW, reduction);
147 | }
148 |
149 | public List filterTilesY(int y, int h, int reduction) {
150 | return filterTilesDim(y, y + h, tilesY, tileH, reduction);
151 | }
152 |
153 |
154 | @Override
155 | public String toString() {
156 | return "JPEG2000Image{" +
157 | "status=" + (status == HEADER_FAIL ? "HEADER_FAIL" : "HEADER_SUCCESS") +
158 | ", width=" + width +
159 | ", height=" + height +
160 | ", tilesX=" + tilesX +
161 | ", tilesY=" + tilesY +
162 | ", tileW=" + tileW +
163 | ", tileH=" + tileH +
164 | ", maxReduction=" + maxReduction +
165 | ", numCompositions=" + numCompositions +
166 | '}';
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/src/main/c/nl_kb_jp2_JP2Reader.c:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013, Koninklijke Bibliotheek - Nationale bibliotheek van Nederland
3 | *
4 | * All rights reserved.
5 | *
6 | * Redistribution and use in source and binary forms, with or without modification,
7 | * are permitted provided that the following conditions are met:
8 | *
9 | * * Redistributions of source code must retain the above copyright notice, this
10 | * list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above copyright notice,
12 | * this list of conditions and the following disclaimer in the documentation
13 | * and/or other materials provided with the distribution.
14 | * * Neither the name of the Koninklijke Bibliotheek nor the names of its contributors
15 | * may be used to endorse or promote products derived from this software without
16 | * specific prior written permission.
17 | *
18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 | *
30 | */
31 | #include
32 | #include
33 | #include "nl_kb_jp2_JP2Reader.h"
34 |
35 | static void error_callback(const char *msg, void *client_data) {(void)client_data; fprintf(stdout, "[ERROR] %s\r\n", msg);}
36 | static void warning_callback(const char *msg, void *client_data) { (void)client_data; fprintf(stdout, "[WARNING] %s\r\n", msg);}
37 | static void info_callback(const char *msg, void *client_data) {(void)client_data; fprintf(stdout, "[INFO] %s\r\n", msg);}
38 |
39 | struct opj_res {
40 | int status;
41 | opj_stream_t *l_stream;
42 | opj_codec_t *l_codec;
43 | opj_image_t *image;
44 | FILE * open_file;
45 | };
46 |
47 | struct opj_res opj_init(const char *fname, opj_dparameters_t *parameters) {
48 |
49 | struct opj_res resources;
50 | resources.status = 0;
51 | resources.image = NULL;
52 | FILE *fptr = fopen(fname, "rb");
53 | resources.open_file = fptr;
54 | resources.l_stream = opj_stream_create_default_file_stream(fptr,1);
55 | resources.l_codec = opj_create_decompress(OPJ_CODEC_JP2);
56 | if(!resources.l_stream) { resources.status = 1; }
57 | if(!opj_setup_decoder(resources.l_codec, parameters)) {
58 | opj_stream_destroy(resources.l_stream);
59 | opj_destroy_codec(resources.l_codec);
60 | resources.status = 2;
61 | }
62 |
63 | if(!opj_read_header(resources.l_stream, resources.l_codec, &(resources.image))) {
64 | opj_stream_destroy(resources.l_stream);
65 | opj_destroy_codec(resources.l_codec);
66 | opj_image_destroy(resources.image);
67 | resources.status = 3;
68 | }
69 |
70 | /* opj_set_info_handler(resources.l_codec, info_callback,00);
71 | opj_set_warning_handler(resources.l_codec, warning_callback,00);
72 | opj_set_error_handler(resources.l_codec, error_callback,00);*/
73 | return resources;
74 | }
75 |
76 | void opj_cleanup(struct opj_res *resources) {
77 | if(resources->l_stream) { opj_stream_destroy(resources->l_stream); }
78 | if(resources->l_codec) { opj_destroy_codec(resources->l_codec); }
79 | if(resources->image) { opj_image_destroy(resources->image); }
80 | if(resources->open_file) { fclose(resources->open_file); }
81 | }
82 |
83 |
84 |
85 | #define JP2_RFC3745_MAGIC "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a"
86 | #define JP2_MAGIC "\x0d\x0a\x87\x0a"
87 | static int is_jp2(FILE *fptr) {
88 | unsigned char buf[12];
89 | unsigned int l_nb_read;
90 |
91 | l_nb_read = fread(buf, 1, 12, fptr);
92 | fseek(fptr, 0, SEEK_SET);
93 |
94 | int retval = memcmp(buf, JP2_RFC3745_MAGIC, 12) == 0 || memcmp(buf, JP2_MAGIC, 4) == 0;
95 | fclose(fptr);
96 | return retval;
97 | }
98 |
99 | #define READ_FAILURE 0
100 | #define READ_SUCCESS 1
101 |
102 |
103 | JNIEXPORT jintArray JNICALL Java_nl_kb_jp2_JP2Reader_getTile
104 | (JNIEnv *env, jclass cls, jstring fname, jint tile_index, jint reduction_factor, jobjectArray pixels) {
105 | const char *filename = (*env)->GetStringUTFChars(env, fname, 0);
106 | jclass intArrayClass = (*env)->FindClass(env, "[I");
107 | jintArray ary = (*env)->NewIntArray(env, 3);
108 |
109 | int data[3];
110 | int i = 0;
111 | for(i = 0; i < 3; ++i) { data[i] = 0; }
112 | data[0] = READ_FAILURE;
113 |
114 | FILE *fptr = fopen(filename, "rb");
115 | if(fptr != NULL && is_jp2(fptr)) {
116 | opj_dparameters_t parameters;
117 | opj_set_default_decoder_parameters(¶meters);
118 | parameters.cp_reduce = reduction_factor;
119 | parameters.cp_layer = 100;
120 | struct opj_res resources = opj_init(filename, ¶meters);
121 | if(resources.status == 0 && opj_get_decoded_tile(resources.l_codec, resources.l_stream, resources.image, tile_index)) {
122 | int numpix = resources.image->comps[0].w * resources.image->comps[0].h;
123 | int comp;
124 | for(comp = 0; comp < resources.image->numcomps; ++comp) {
125 | jintArray data = (*env)->NewIntArray(env, numpix);
126 | (*env)->SetIntArrayRegion(env, data, (jsize) 0, (jsize) numpix, (jint*) resources.image->comps[comp].data);
127 | (*env)->SetObjectArrayElement(env, pixels, (jsize) comp, data);
128 | }
129 | data[0] = READ_SUCCESS;
130 | data[1] = resources.image->comps[0].w;
131 | data[2] = resources.image->comps[0].h;
132 | }
133 | opj_cleanup(&resources);
134 | }
135 |
136 | (*env)->SetIntArrayRegion(env, ary, 0, 3, data);
137 | return ary;
138 | }
139 |
140 |
141 |
142 | #define FIELD_LEN 9
143 | JNIEXPORT jintArray JNICALL Java_nl_kb_jp2_JP2Reader_getJp2Specs
144 | (JNIEnv *env, jclass cls, jstring fname) {
145 |
146 | const char *filename = (*env)->GetStringUTFChars(env, fname, 0);
147 | jintArray ary = (*env)->NewIntArray(env, FIELD_LEN);
148 |
149 | int data[FIELD_LEN];
150 | int i = 0;
151 | for(i = 0; i < FIELD_LEN; ++i) { data[i] = 0; }
152 | data[0] = READ_FAILURE;
153 |
154 | FILE *fptr = fopen(filename, "rb");
155 | if(fptr != NULL && is_jp2(fptr)) {
156 | opj_dparameters_t parameters;
157 | opj_set_default_decoder_parameters(¶meters);
158 | struct opj_res resources = opj_init(filename, ¶meters);
159 |
160 | opj_codestream_info_v2_t* info = opj_get_cstr_info(resources.l_codec);
161 | if(resources.status == 0) {
162 | data[0] = READ_SUCCESS;
163 | data[1] = resources.image->x1;
164 | data[2] = resources.image->y1;
165 | data[3] = info->tw;
166 | data[4] = info->th;
167 | data[5] = info->tdx;
168 | data[6] = info->tdy;
169 | data[7] = info->m_default_tile_info.tccp_info[0].numresolutions;
170 | data[8] = resources.image->numcomps;
171 | }
172 | opj_destroy_cstr_info(&info);
173 | opj_cleanup(&resources);
174 | } else {
175 | error_callback("Cannot read file:", NULL);
176 | error_callback(filename, NULL);
177 | }
178 | (*env)->SetIntArrayRegion(env, ary, 0, FIELD_LEN, data);
179 | return ary;
180 | }
181 |
--------------------------------------------------------------------------------
/src/main/java/nl/kb/jp2/JP2Reader.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013, Koninklijke Bibliotheek - Nationale bibliotheek van Nederland
3 | *
4 | * All rights reserved.
5 | *
6 | * Redistribution and use in source and binary forms, with or without modification,
7 | * are permitted provided that the following conditions are met:
8 | *
9 | * * Redistributions of source code must retain the above copyright notice, this
10 | * list of conditions and the following disclaimer.
11 | * * Redistributions in binary form must reproduce the above copyright notice,
12 | * this list of conditions and the following disclaimer in the documentation
13 | * and/or other materials provided with the distribution.
14 | * * Neither the name of the Koninklijke Bibliotheek nor the names of its contributors
15 | * may be used to endorse or promote products derived from this software without
16 | * specific prior written permission.
17 | *
18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 | *
30 | */
31 |
32 | package nl.kb.jp2;
33 |
34 | import org.apache.commons.io.IOUtils;
35 |
36 | import javax.imageio.ImageIO;
37 | import java.awt.*;
38 | import java.awt.image.BufferedImage;
39 | import java.io.File;
40 | import java.io.FileOutputStream;
41 | import java.io.IOException;
42 | import java.io.InputStream;
43 | import java.util.ArrayList;
44 | import java.util.Date;
45 | import java.util.List;
46 |
47 |
48 | public class JP2Reader {
49 | private static final String LIBRARY_NAME = "libkbjp2.so";
50 | private static final int MAX_THREADS_PER_JOB = 8;
51 |
52 |
53 | static {
54 | InputStream is = JP2Reader.class.getResourceAsStream("/" + LIBRARY_NAME);
55 | try {
56 | File temp = File.createTempFile(LIBRARY_NAME, "");
57 | FileOutputStream fos = new FileOutputStream(temp);
58 | fos.write(IOUtils.toByteArray(is));
59 | fos.close();
60 | is.close();
61 | System.load(temp.getAbsolutePath());
62 | } catch(IOException e) {
63 | throw new RuntimeException(e);
64 | }
65 | }
66 |
67 | public native int[] getJp2Specs(String filename);
68 | private native int[] getTile(String filename, int tileIndex, int reduction, int[][] pixels);
69 |
70 |
71 | private class TileToBufferJob implements Runnable {
72 | private BufferedImage img;
73 | private JPEG2000Image image;
74 | private int reduction;
75 | private int tileIndex;
76 | private int imageX;
77 | private int imageY;
78 |
79 | private int subX1 = -1;
80 | private int subY1 = -1;
81 | private int subX = -1;
82 | private int subY = -1;
83 | private int realX = 0;
84 | private int realY = 0;
85 |
86 | public TileToBufferJob(JPEG2000Image image, BufferedImage img, int reduction, int tileIndex, int imageX, int imageY,
87 | int subX, int subY, int subX1, int subY1, int realX, int realY) {
88 | this(image, img, reduction, tileIndex, imageX, imageY);
89 | this.subX = subX;
90 | this.subY = subY;
91 | this.subX1 = subX1;
92 | this.subY1 = subY1;
93 | this.realX = realX;
94 | this.realY = realY;
95 | }
96 |
97 | public TileToBufferJob(JPEG2000Image image, BufferedImage img, int reduction, int tileIndex, int imageX, int imageY) {
98 | this.image = image;
99 | this.img = img;
100 | this.reduction = reduction;
101 | this.tileIndex = tileIndex;
102 | this.imageX = imageX;
103 | this.imageY = imageY;
104 | }
105 |
106 | public void run() {
107 | int[][] tileRBG = new int[image.getNumCompositions()][];
108 | int[] tileSpecs = getTile(image.getFilename(), tileIndex, reduction, tileRBG);
109 | int startX = subX > 0 ? subX : 0;
110 | int startY = subY > 0 ? subY : 0;
111 | int endX = subX1 > 0 ? (subX1 > tileSpecs[1] ? tileSpecs[1] : subX1) : tileSpecs[1];
112 | int endY = subY1 > 0 ? (subY1 > tileSpecs[2] ? tileSpecs[2] : subY1) : tileSpecs[2];
113 | int outLeft = imageX - realX < 0 ? 0 : imageX - realX;
114 | int outTop = imageY - realY < 0 ? 0 : imageY - realY;
115 |
116 | for(int y = startY; y < endY; ++y) {
117 | for(int x = startX; x < endX; ++x) {
118 | int[] rgb = new int[3];
119 | int i = y * tileSpecs[1] + x;
120 | if(image.getNumCompositions() >= 3) {
121 | rgb[0] = tileRBG[0][i];
122 | rgb[1] = tileRBG[1][i];
123 | rgb[2] = tileRBG[2][i];
124 | } else {
125 | /** we do not use the alpha channel at the kbnl; maybe grayscale though **/
126 | rgb[0] = tileRBG[0][i];
127 | rgb[1] = tileRBG[0][i];
128 | rgb[2] = tileRBG[0][i];
129 | }
130 | try {
131 | img.getRaster().setPixel(outLeft + x - startX, outTop + y - startY, rgb);
132 | } catch(ArrayIndexOutOfBoundsException e) {
133 | System.err.print("x");
134 | }
135 | }
136 | }
137 | }
138 | }
139 |
140 |
141 | /**
142 | * Get the full JP2 image at requested at the requested reduction level.
143 | * @param image the image object representing the image
144 | * @param reduction the requested reduction level (will be cut off at max reduction level)
145 | * @return buffered image object containing the full image
146 | */
147 | public BufferedImage getFullImage(JPEG2000Image image, int reduction) {
148 | if(reduction < 0) { reduction = 0; }
149 | if(reduction > image.getMaxReduction()) { reduction = image.getMaxReduction(); }
150 | return getRegion(image, reduction, 0, 0, image.getWidth(reduction), image.getHeight(reduction));
151 | }
152 |
153 | /**
154 | * Get the full JP2 image at requested scale.
155 | * @param image the image object representing the image
156 | * @param scale the requested rescale factor
157 | * @return buffered image object containing the full image
158 | */
159 | public BufferedImage getFullImage(JPEG2000Image image, double scale) {
160 | return getRegion(image, scale, 0, 0, (int) Math.ceil(image.getWidth(0) * scale), (int) Math.ceil(image.getHeight(0) * scale));
161 | }
162 |
163 | /**
164 | * Get a region of a JP2 image at the requested scale.
165 | * @param image the image object representing the image
166 | * @param scale the requested rescale factor
167 | * @param x the x-position on the image after resize to scale
168 | * @param y the y-position on the image after resize to scale
169 | * @param w the width after resize
170 | * @param h the height after resize
171 | * @return buffered image object containing the selected region
172 | */
173 | public BufferedImage getRegion(JPEG2000Image image, double scale, int x, int y, int w, int h) {
174 | int reduction = image.getBestReductionFactorForScale(scale);
175 | double jp2scale = JPEG2000Image.reduce(1.0, reduction);
176 | double factor = jp2scale / scale;
177 | int x1 = (int) Math.ceil(factor * (double)x);
178 | int y1 = (int) Math.ceil(factor * (double)y);
179 | int w1 = (int) Math.ceil(factor * (double)w);
180 | int h1 = (int) Math.ceil(factor * (double)h);
181 | BufferedImage img = getRegion(image, reduction, x1, y1, w1, h1);
182 | BufferedImage out = new BufferedImage(w, h, img.getType());
183 | Graphics2D g = out.createGraphics();
184 | g.drawImage(img, 0, 0, w, h, null);
185 | g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
186 | g.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
187 | g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
188 |
189 | return out;
190 | }
191 |
192 |
193 | /**
194 | * Get a region of a JP2 image at a supported resolution reduction level.
195 | * @param image the image object representing the image (TODO: check for successful load)
196 | * @param reduction the requested reduction level (will be cut off at max reduction level)
197 | * @param x the x-position on the image after resize to reduction level
198 | * @param y the y-position on the image after resize to reduction level
199 | * @param w the width after resize
200 | * @param h the height after resize
201 | * @return buffered image object containing the selected region
202 | */
203 | public BufferedImage getRegion(JPEG2000Image image, int reduction, int x, int y, int w, int h) {
204 | if(reduction < 0) { reduction = 0; }
205 | else if(reduction > image.getMaxReduction()) { reduction = image.getMaxReduction(); }
206 | if(x > image.getWidth(reduction)) { x = image.getWidth(reduction); }
207 | if(y > image.getHeight(reduction)) { y = image.getHeight(reduction); }
208 | if(x + w > image.getWidth(reduction)) { w = image.getWidth(reduction) - x; }
209 | if(y + h > image.getHeight(reduction)) { h = image.getHeight(reduction) - y; }
210 |
211 | if(w <= 0|| h <= 0) {
212 | return new BufferedImage(1,1, BufferedImage.TYPE_INT_RGB);
213 | }
214 |
215 | BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
216 |
217 | List openThreads = new ArrayList();
218 | for(int tileX : image.filterTilesX(x, w, reduction)) {
219 | int imageX = tileX * image.getTileW(reduction);
220 | for(int tileY : image.filterTilesY(y, h, reduction)) {
221 | int imageY = tileY * image.getTileH(reduction);
222 | int tileIndex = (image.getTilesX() * tileY) + tileX;
223 | int subX = (imageX > x ? 0 : x - imageX);
224 | int subY = (imageY > y ? 0 : y - imageY);
225 | int subX1 = (imageX + image.getTileW(reduction) > x + w ?
226 | (x + w) - imageX :
227 | image.getTileW(reduction));
228 | int subY1 = (imageY + image.getTileH(reduction) > y + h ?
229 | (y + h) - imageY :
230 | image.getTileH(reduction));
231 |
232 | TileToBufferJob job = new TileToBufferJob(image, img, reduction, tileIndex, imageX, imageY,
233 | subX, subY, subX1, subY1, x, y);
234 | Thread t = new Thread(job);
235 | t.start();
236 | openThreads.add(t);
237 | if(openThreads.size() >= MAX_THREADS_PER_JOB) {
238 | try {
239 | openThreads.remove(0).join();
240 | } catch(InterruptedException e) { }
241 | }
242 | }
243 | }
244 | for(Thread t : openThreads) {
245 | try {
246 | t.join();
247 | } catch (InterruptedException e) { }
248 | }
249 |
250 | return img;
251 | }
252 |
253 | /**
254 | * TODO: move to unit tests, parse the command line
255 | * @param args
256 | */
257 | public static void main(String args[]) {
258 | String filename = args[1];
259 | long start = new Date().getTime();
260 | JP2Reader reader = new JP2Reader();
261 |
262 | JPEG2000Image image = new JPEG2000Image(filename, reader.getJp2Specs(filename));
263 | System.out.println("Read header ms: " + ((new Date().getTime()) - start));
264 | if(image.headerLoaded()) {
265 | System.out.println(image);
266 |
267 | start = new Date().getTime();
268 | BufferedImage outImg = reader.getFullImage(image, 0);
269 | System.out.println("Decompile full image no reduction ms: " + ((new Date().getTime()) - start));
270 | try {
271 | ImageIO.write(outImg, "jpg", new File("test.jpg"));
272 | } catch(IOException e) {
273 | e.printStackTrace();
274 | }
275 |
276 | start = new Date().getTime();
277 | BufferedImage outImg1 = reader.getRegion(image, 1, 250, 135, 180, 400);
278 | System.out.println("Get region ms: " + ((new Date().getTime()) - start));
279 | try {
280 | ImageIO.write(outImg1, "jpg", new File("test_region.jpg"));
281 | } catch(IOException e) {
282 | e.printStackTrace();
283 | }
284 |
285 | start = new Date().getTime();
286 | BufferedImage outImg2 = reader.getRegion(image, 0.5d, 250, 135, 180, 400);
287 | System.out.println("Get scaled DOWN region ms: " + ((new Date().getTime()) - start));
288 | try {
289 | ImageIO.write(outImg2, "jpg", new File("test_region_scaled.jpg"));
290 | } catch(IOException e) {
291 | e.printStackTrace();
292 | }
293 |
294 | start = new Date().getTime();
295 | BufferedImage outImg4 = reader.getRegion(image, 1.5d, 0, 0, 1500, 800);
296 | System.out.println("Get scaled UP region ms: " + ((new Date().getTime()) - start));
297 | try {
298 | ImageIO.write(outImg4, "jpg", new File("test_region_scaled_up.jpg"));
299 | } catch(IOException e) {
300 | e.printStackTrace();
301 | }
302 |
303 | start = new Date().getTime();
304 | BufferedImage outImg3 = reader.getFullImage(image, 0.01d);
305 | System.out.println("Get scaled full image (s=0.01) ms: " + ((new Date().getTime()) - start));
306 | try {
307 | ImageIO.write(outImg3, "jpg", new File("test_full_scaled.jpg"));
308 | } catch(IOException e) {
309 | e.printStackTrace();
310 | }
311 |
312 | } else {
313 | System.err.println("failed to load file: " + filename);
314 | }
315 | }
316 | }
317 |
--------------------------------------------------------------------------------