diff --git a/pom.xml b/pom.xml
index bbfaf54..310045c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,6 +37,13 @@
jblosc
1.0.1
+
+
+ org.openmicroscopy
+ ome-codecs
+ 0.3.1
+
+
@@ -211,4 +218,4 @@
-
\ No newline at end of file
+
diff --git a/src/main/java/com/bc/zarr/Compressor.java b/src/main/java/com/bc/zarr/Compressor.java
index 71fdcd2..1e88fd8 100644
--- a/src/main/java/com/bc/zarr/Compressor.java
+++ b/src/main/java/com/bc/zarr/Compressor.java
@@ -50,4 +50,41 @@ void passThrough(InputStream is, OutputStream os) throws IOException {
read = is.read(bytes);
}
}
+
+ public boolean booleanValue(Object v, boolean defaultValue) {
+ if (v == null) {
+ return defaultValue;
+ }
+ if (v instanceof Boolean) {
+ return (Boolean) v;
+ }
+ return Boolean.parseBoolean(v.toString());
+ }
+
+ public int intValue(Object v, int defaultValue) {
+ int value = defaultValue;
+ if (v != null) {
+ if (v instanceof String) {
+ value = Integer.parseInt((String) v);
+ }
+ else if (v instanceof Number) {
+ value = ((Number) v).intValue();
+ }
+ }
+ return value;
+ }
+
+ public double doubleValue(Object v, double defaultValue) {
+ double value = defaultValue;
+ if (v != null) {
+ if (v instanceof String) {
+ value = Double.parseDouble((String) v);
+ }
+ else if (v instanceof Number) {
+ value = ((Number) v).doubleValue();
+ }
+ }
+ return value;
+ }
+
}
diff --git a/src/main/java/com/bc/zarr/CompressorFactory.java b/src/main/java/com/bc/zarr/CompressorFactory.java
index e9c3c76..1e99a8d 100644
--- a/src/main/java/com/bc/zarr/CompressorFactory.java
+++ b/src/main/java/com/bc/zarr/CompressorFactory.java
@@ -31,6 +31,7 @@
import org.blosc.IBloscDll;
import org.blosc.JBlosc;
+import java.awt.image.WritableRaster;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
@@ -47,6 +48,16 @@
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
+import loci.common.services.DependencyException;
+import loci.common.services.ServiceException;
+import loci.common.services.ServiceFactory;
+import ome.codecs.CodecException;
+import ome.codecs.JPEG2000Codec;
+import ome.codecs.JPEG2000CodecOptions;
+import ome.codecs.gui.AWTImageTools;
+import ome.codecs.services.JAIIIOService;
+import ome.codecs.services.JAIIIOServiceImpl;
+
public class CompressorFactory {
public final static Compressor nullCompressor = new NullCompressor();
@@ -120,6 +131,9 @@ public static Compressor create(String id, Map properties) {
if ("blosc".equals(id)) {
return new BloscCompressor(properties);
}
+ if ("j2k".equals(id)) {
+ return new J2KCompressor(properties);
+ }
throw new IllegalArgumentException("Compressor id:'" + id + "' not supported.");
}
@@ -356,5 +370,110 @@ private BufferSizes cbufferSizes(ByteBuffer cbuffer) {
return bs;
}
}
+
+ static class J2KCompressor extends Compressor {
+ public static final String littleEndianKey = "littleEndian";
+ public static final String interleavedKey = "interleaved";
+ public static final String losslessKey = "lossless";
+ public static final String widthKey = "imageWidth";
+ public static final String heightKey = "imageHeight";
+ public static final String bitsPerSampleKey = "bitsPerSample";
+ public static final String channelsKey = "channels";
+ public static final String qualityKey = "quality";
+
+ // TODO: any way to copy these options from array metadata?
+ private boolean littleEndian;
+ private boolean interleaved;
+ private int width;
+ private int height;
+ private int bitsPerSample;
+ private int channels;
+ // specify lossless or quality, not both
+ private boolean lossless;
+ private double quality;
+
+ private J2KCompressor(Map map) {
+ littleEndian = booleanValue(map.get(littleEndianKey), false);
+ interleaved = booleanValue(map.get(interleavedKey), false);
+ width = intValue(map.get(widthKey), -1);
+ height = intValue(map.get(heightKey), -1);
+ bitsPerSample = intValue(map.get(bitsPerSampleKey), 8);
+ channels = intValue(map.get(channelsKey), 1);
+
+ // if neither quality nor lossless is defined, do lossless compression
+ quality = doubleValue(map.get(qualityKey), -1);
+ lossless = booleanValue(map.get(losslessKey), quality < 0);
+ }
+
+ @Override
+ public String toString() {
+ return "compressor=" + getId();
+ }
+
+ @Override
+ public String getId() {
+ return "j2k";
+ }
+
+ @Override
+ public void compress(InputStream is, OutputStream os) throws IOException {
+ try (ByteArrayOutputStream tmpOut = new ByteArrayOutputStream()) {
+ passThrough(is, tmpOut);
+ byte[] buffer = tmpOut.toByteArray();
+ JPEG2000Codec codec = new JPEG2000Codec();
+ JPEG2000CodecOptions options = getCodecOptions();
+ byte[] compressed = codec.compress(buffer, options);
+ os.write(compressed);
+ }
+ catch (CodecException e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ public void uncompress(InputStream is, OutputStream os) throws IOException {
+ try {
+ JAIIIOService service = getService();
+ WritableRaster raster = (WritableRaster) service.readRaster(is, getCodecOptions());
+ byte[][] raw = AWTImageTools.getPixelBytes(raster, littleEndian);
+ for (byte[] channel : raw) {
+ os.write(channel);
+ }
+ }
+ catch (ServiceException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private JPEG2000CodecOptions getCodecOptions() {
+ JPEG2000CodecOptions options = new JPEG2000CodecOptions();
+ options.interleaved = interleaved;
+ options.littleEndian = littleEndian;
+ options.width = width;
+ options.height = height;
+ options.channels = channels;
+ options.bitsPerSample = bitsPerSample;
+ if (quality >= 0) {
+ options.quality = quality;
+ }
+ options.lossless = lossless;
+ options.numDecompositionLevels = 1;
+
+ return JPEG2000CodecOptions.getDefaultOptions(options);
+ }
+
+ private JAIIIOService getService() throws IOException {
+ try {
+ ServiceFactory factory = new ServiceFactory();
+ return factory.getInstance(JAIIIOService.class);
+ }
+ catch (DependencyException de) {
+ throw new IOException(JAIIIOServiceImpl.NO_J2K_MSG, de);
+ }
+ }
+
+ }
+
+
}
diff --git a/src/test/java/com/bc/zarr/CompressorTest.java b/src/test/java/com/bc/zarr/CompressorTest.java
index 4324664..e52affb 100644
--- a/src/test/java/com/bc/zarr/CompressorTest.java
+++ b/src/test/java/com/bc/zarr/CompressorTest.java
@@ -28,6 +28,7 @@
import static org.hamcrest.Matchers.*;
import static org.hamcrest.MatcherAssert.*;
+import static org.junit.Assert.assertNotNull;
import com.bc.zarr.chunk.ZarrInputStreamAdapter;
@@ -155,6 +156,51 @@ public void writeRead_BloscCompressor() throws IOException {
assertThat(input, is(equalTo(uncompressed)));
}
+ @Test
+ public void writeRead_J2KCompressor() throws IOException {
+ final Map j2kProperties = new LinkedHashMap<>();
+ j2kProperties.put(CompressorFactory.J2KCompressor.widthKey, 11);
+ j2kProperties.put(CompressorFactory.J2KCompressor.heightKey, 5);
+ j2kProperties.put(CompressorFactory.J2KCompressor.bitsPerSampleKey, 16);
+ j2kProperties.put(CompressorFactory.J2KCompressor.channelsKey, 1);
+ j2kProperties.put(CompressorFactory.J2KCompressor.interleavedKey, false);
+ j2kProperties.put(CompressorFactory.J2KCompressor.littleEndianKey, false);
+ j2kProperties.put(CompressorFactory.J2KCompressor.losslessKey, true);
+
+ final Compressor compressor = CompressorFactory.create("j2k", j2kProperties);
+ final ByteOrder byteOrder = ByteOrder.BIG_ENDIAN;
+ final short[] input = {
+ 100, 22, 100, 22, 22, 22, 100, 100, 100, 22, 100,
+ 100, 22, 100, 22, 22, 22, 100, 100, 100, 22, 100,
+ 100, 22, 100, 22, 22, 22, 100, 100, 100, 22, 100,
+ 100, 22, 100, 22, 22, 22, 100, 100, 100, 22, 100,
+ 100, 22, 100, 22, 22, 22, 100, 100, 100, 22, 100
+ };
+ final MemoryCacheImageOutputStream iis = new MemoryCacheImageOutputStream(new ByteArrayOutputStream());
+ iis.setByteOrder(byteOrder);
+ iis.writeShorts(input, 0, input.length);
+ iis.seek(0);
+
+ ByteArrayOutputStream os;
+ ByteArrayInputStream is;
+
+ //write
+ os = new ByteArrayOutputStream();
+ compressor.compress(new ZarrInputStreamAdapter(iis), os);
+ final byte[] compressed = os.toByteArray();
+
+ //read
+ is = new ByteArrayInputStream(compressed);
+ os = new ByteArrayOutputStream();
+ compressor.uncompress(is, os);
+ final ByteArrayInputStream bais = new ByteArrayInputStream(os.toByteArray());
+ final MemoryCacheImageInputStream resultIis = new MemoryCacheImageInputStream(bais);
+ resultIis.setByteOrder(byteOrder);
+ final short[] uncompressed = new short[input.length];
+ resultIis.readFully(uncompressed, 0, uncompressed.length);
+ assertThat(input, is(equalTo(uncompressed)));
+ }
+
@Test
public void read_BloscCompressor_DefaultAvailable() throws IOException {
final Compressor compressor = CompressorFactory.create("blosc");