Skip to content

Commit

Permalink
Merge pull request #4 from dbmdz/next-monkeys-preparations
Browse files Browse the repository at this point in the history
Next monkeys preparations
  • Loading branch information
stefan-it authored Nov 29, 2018
2 parents 490e256 + e04b10b commit 1efaa89
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 79 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ target
*.iml
core
hs_err_*.log
/nb-configuration.xml
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,7 @@ so you merely need to make sure that the backing native libraries are compatible
Both [OpenJPEG](https://packages.debian.org/stretch/libopenjp2-7) and [TurboJPEG](https://packages.debian.org/stretch/libturbojpeg0)
are available for the majority of commonly used platforms.

## FAQ

- Q: I get a `Failed to read JPEG info.` `IllegalArgumentException` when using the TwelveMonkeys ImageIO libraries in version 3.4.1 when reading a TIFF with JPEG compressed data.
A: Stick to Twelvemonkeys version 3.3.2 for now. We're currently investigating the issue.
4 changes: 0 additions & 4 deletions imageio-openjpeg/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${version.slf4j}</version>
</dependency>

<dependency>
Expand All @@ -36,19 +35,16 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${version.junit-jupiter}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${version.junit-jupiter}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${version.slf4j}</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
4 changes: 0 additions & 4 deletions imageio-turbojpeg/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${version.slf4j}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
Expand All @@ -41,19 +40,16 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${version.junit-jupiter}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${version.junit-jupiter}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${version.slf4j}</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
import de.digitalcollections.turbojpeg.lib.libturbojpeg;
import de.digitalcollections.turbojpeg.lib.structs.tjscalingfactor;
import de.digitalcollections.turbojpeg.lib.structs.tjtransform;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import jnr.ffi.LibraryLoader;
import jnr.ffi.Pointer;
import jnr.ffi.Runtime;
Expand All @@ -17,15 +23,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.nio.Buffer;
import java.nio.ByteBuffer;

/** Java bindings for libturbojpeg via JFFI **/
public class TurboJpeg {

private static final Logger LOG = LoggerFactory.getLogger(TurboJpeg.class);
public libturbojpeg lib;
public Runtime runtime;
Expand All @@ -35,7 +35,13 @@ public TurboJpeg() {
runtime = Runtime.getRuntime(lib);
}

/** Return information about the JPEG image in the input buffer **/
/**
* Return information about the JPEG image in the input buffer
*
* @param jpegData jpeg image data
* @return information about the jpeg image
* @throws de.digitalcollections.turbojpeg.TurboJpegException if decompressing header with library fails
*/
public Info getInfo(byte[] jpegData) throws TurboJpegException {
Pointer codec = null;
try {
Expand All @@ -45,7 +51,7 @@ public Info getInfo(byte[] jpegData) throws TurboJpegException {
IntByReference height = new IntByReference();
IntByReference jpegSubsamp = new IntByReference();
int rv = lib.tjDecompressHeader2(
codec, ByteBuffer.wrap(jpegData), jpegData.length, width, height, jpegSubsamp);
codec, ByteBuffer.wrap(jpegData), jpegData.length, width, height, jpegSubsamp);
if (rv != 0) {
throw new TurboJpegException(lib.tjGetErrorStr());
}
Expand All @@ -62,7 +68,9 @@ public Info getInfo(byte[] jpegData) throws TurboJpegException {
}
return new Info(width.getValue(), height.getValue(), jpegSubsamp.getValue(), factors);
} finally {
if (codec != null && codec.address() != 0) lib.tjDestroy(codec);
if (codec != null && codec.address() != 0) {
lib.tjDestroy(codec);
}
}
}

Expand All @@ -72,7 +80,7 @@ public Info getInfo(byte[] jpegData) throws TurboJpegException {
* @param info Information about the JPEG image in the buffer
* @param size Target decompressed dimensions, must be among the available sizes (see {@link Info#getAvailableSizes()})
* @return The decoded image
* @throws TurboJpegException
* @throws TurboJpegException if decompression with library fails
*/
public BufferedImage decode(byte[] jpegData, Info info, Dimension size) throws TurboJpegException {
Pointer codec = null;
Expand All @@ -83,7 +91,7 @@ public BufferedImage decode(byte[] jpegData, Info info, Dimension size) throws T
if (size != null) {
if (!info.getAvailableSizes().contains(size)) {
throw new IllegalArgumentException(String.format(
"Invalid size, must be one of %s", info.getAvailableSizes()));
"Invalid size, must be one of %s", info.getAvailableSizes()));
} else {
width = size.width;
height = size.height;
Expand All @@ -99,21 +107,30 @@ public BufferedImage decode(byte[] jpegData, Info info, Dimension size) throws T
BufferedImage img = new BufferedImage(width, height, imgType);
// Wrap the underlying data buffer of the image with a ByteBuffer so we can pass it over the ABI
ByteBuffer outBuf = ByteBuffer.wrap(((DataBufferByte) img.getRaster().getDataBuffer()).getData())
.order(runtime.byteOrder());
.order(runtime.byteOrder());
int rv = lib.tjDecompress2(
codec, ByteBuffer.wrap(jpegData), jpegData.length, outBuf,
width, isGray ? width : width * 3, height, isGray ? TJPF.TJPF_GRAY : TJPF.TJPF_BGR, 0);
codec, ByteBuffer.wrap(jpegData), jpegData.length, outBuf,
width, isGray ? width : width * 3, height, isGray ? TJPF.TJPF_GRAY : TJPF.TJPF_BGR, 0);
if (rv != 0) {
LOG.error("Could not decompress JPEG (dimensions: {}x{}, gray: {})", width, height, isGray);
throw new TurboJpegException(lib.tjGetErrorStr());
}
return img;
} finally {
if (codec != null && codec.address() != 0) lib.tjDestroy(codec);
if (codec != null && codec.address() != 0) {
lib.tjDestroy(codec);
}
}
}

/** Encode an image to JPEG **/
/**
* Encode an image to JPEG
*
* @param img image as rectangle of pixels
* @param quality compression quality
* @return jpeg image
* @throws de.digitalcollections.turbojpeg.TurboJpegException if compression with library fails
*/
public ByteBuffer encode(Raster img, int quality) throws TurboJpegException {
Pointer codec = null;
Pointer bufPtr = null;
Expand Down Expand Up @@ -143,22 +160,26 @@ public ByteBuffer encode(Raster img, int quality) throws TurboJpegException {

// Wrap source image data buffer with ByteBuffer to pass it over the ABI
ByteBuffer inBuf = ByteBuffer.wrap(((DataBufferByte) img.getDataBuffer()).getData())
.order(runtime.byteOrder());
.order(runtime.byteOrder());
int rv = lib.tjCompress2(
codec, inBuf, img.getWidth(), 0, img.getHeight(), pixelFmt,
new PointerByReference(bufPtr), lenPtr, sampling, quality, 0);
codec, inBuf, img.getWidth(), 0, img.getHeight(), pixelFmt,
new PointerByReference(bufPtr), lenPtr, sampling, quality, 0);
if (rv != 0) {
LOG.error("Could not compress image (dimensions: {}x{}, format: {}, sampling: {}, quality: {}",
img.getWidth(), img.getHeight(), pixelFmt, sampling, quality);
img.getWidth(), img.getHeight(), pixelFmt, sampling, quality);
throw new TurboJpegException(lib.tjGetErrorStr());
}
ByteBuffer outBuf = ByteBuffer.allocate(lenPtr.getValue().intValue()).order(runtime.byteOrder());
bufPtr.get(0, outBuf.array(), 0, lenPtr.getValue().intValue());
outBuf.rewind();
return outBuf;
} finally {
if (codec != null && codec.address() != 0) lib.tjDestroy(codec);
if (bufPtr != null && bufPtr.address() != 0) lib.tjFree(bufPtr);
if (codec != null && codec.address() != 0) {
lib.tjDestroy(codec);
}
if (bufPtr != null && bufPtr.address() != 0) {
lib.tjFree(bufPtr);
}
}
}

Expand All @@ -169,7 +190,7 @@ public ByteBuffer encode(Raster img, int quality) throws TurboJpegException {
* @param region Source region to crop out of JPEG
* @param rotation Degrees to rotate the JPEG, must be 90, 180 or 270
* @return The transformed JPEG data
* @throws TurboJpegException
* @throws TurboJpegException if image transformation fails
*/
public ByteBuffer transform(byte[] jpegData, Info info, Rectangle region, int rotation) throws TurboJpegException {
Pointer codec = null;
Expand All @@ -185,7 +206,7 @@ public ByteBuffer transform(byte[] jpegData, Info info, Rectangle region, int ro
Dimension mcuSize = info.getMCUSize();
if (region.width % mcuSize.width != 0 || region.height % mcuSize.height != 0) {
throw new IllegalArgumentException(String.format(
"Invalid cropping region, width must be divisible by %d, height by %d", mcuSize.width, mcuSize.height));
"Invalid cropping region, width must be divisible by %d, height by %d", mcuSize.width, mcuSize.height));
}
width = region.width;
height = region.height;
Expand Down Expand Up @@ -240,20 +261,24 @@ public ByteBuffer transform(byte[] jpegData, Info info, Rectangle region, int ro
NativeLongByReference lenRef = new NativeLongByReference(bufSize);
Buffer inBuf = ByteBuffer.wrap(jpegData).order(runtime.byteOrder());
int rv = lib.tjTransform(
codec, inBuf, jpegData.length, 1, new PointerByReference(bufPtr),
lenRef, transform, 0);
codec, inBuf, jpegData.length, 1, new PointerByReference(bufPtr),
lenRef, transform, 0);
if (rv != 0) {
LOG.error("Could not compress image (crop: {},{},{},{}, rotate: {})",
transform.r.x, transform.r.y, transform.r.w, transform.r.h, rotation);
transform.r.x, transform.r.y, transform.r.w, transform.r.h, rotation);
throw new TurboJpegException(lib.tjGetErrorStr());
}
ByteBuffer outBuf = ByteBuffer.allocate(lenRef.getValue().intValue()).order(runtime.byteOrder());
bufPtr.get(0, outBuf.array(), 0, lenRef.getValue().intValue());
outBuf.rewind();
return outBuf;
} finally {
if (codec != null && codec.address() != 0) lib.tjDestroy(codec);
if (bufPtr != null && bufPtr.address() != 0) lib.tjFree(bufPtr);
if (codec != null && codec.address() != 0) {
lib.tjDestroy(codec);
}
if (bufPtr != null && bufPtr.address() != 0) {
lib.tjFree(bufPtr);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package de.digitalcollections.turbojpeg.imageio;

import com.google.common.collect.ImmutableSet;
import javax.imageio.ImageReadParam;
import javax.imageio.plugins.jpeg.JPEGImageReadParam;

public class TurboJpegImageReadParam extends JPEGImageReadParam {

public class TurboJpegImageReadParam extends ImageReadParam {
private int rotationDegree;

public int getRotationDegree() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@
import de.digitalcollections.turbojpeg.Info;
import de.digitalcollections.turbojpeg.TurboJpeg;
import de.digitalcollections.turbojpeg.TurboJpegException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.stream.Stream;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.awt.image.BufferedImage.*;

public class TurboJpegImageReader extends ImageReader {

private final static Logger LOGGER = LoggerFactory.getLogger(TurboJpegImageReader.class);

private final TurboJpeg lib;
Expand All @@ -45,8 +45,10 @@ public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetada
jpegData = bufferFromStream((ImageInputStream) input);
info = lib.getInfo(jpegData.array());
} catch (IOException e) {
LOGGER.error(e.getMessage());
throw new IllegalArgumentException("Failed to read input.");
} catch (TurboJpegException e) {
LOGGER.error(e.getMessage());
throw new IllegalArgumentException("Failed to read JPEG info.");
}
} else {
Expand Down Expand Up @@ -95,8 +97,8 @@ public int getHeight(int imageIndex) throws IOException {
@Override
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
return Stream.of(TYPE_3BYTE_BGR, TYPE_4BYTE_ABGR, TYPE_4BYTE_ABGR_PRE, TYPE_BYTE_GRAY)
.map(ImageTypeSpecifier::createFromBufferedImageType)
.iterator();
.map(ImageTypeSpecifier::createFromBufferedImageType)
.iterator();
}

/** Since TurboJPEG can only crop to values divisible by the MCU size, we may need to
Expand Down Expand Up @@ -139,9 +141,9 @@ private Rectangle adjustRegion(Dimension mcuSize, Rectangle region, int rotation
region.height = w;
}
Rectangle extraCrop = new Rectangle(
0, 0,
region.width == 0 ? originalWidth - region.x : region.width,
region.height == 0 ? originalHeight - region.y : region.height);
0, 0,
region.width == 0 ? originalWidth - region.x : region.width,
region.height == 0 ? originalHeight - region.y : region.height);
if (region.x % mcuSize.width != 0) {
extraCrop.x = region.x % mcuSize.width;
region.x -= extraCrop.x;
Expand All @@ -159,11 +161,11 @@ private Rectangle adjustRegion(Dimension mcuSize, Rectangle region, int rotation
modified = true;
}
if (region.width % mcuSize.width != 0) {
region.width = (int) (mcuSize.width*(Math.ceil(region.getWidth() / mcuSize.width)));
region.width = (int) (mcuSize.width * (Math.ceil(region.getWidth() / mcuSize.width)));
modified = true;
}
if (region.height % mcuSize.height != 0) {
region.height = (int) (mcuSize.height*(Math.ceil(region.getHeight() / mcuSize.height)));
region.height = (int) (mcuSize.height * (Math.ceil(region.getHeight() / mcuSize.height)));
modified = true;
}
if (modified) {
Expand Down Expand Up @@ -240,7 +242,7 @@ public BufferedImage read(int imageIndex, ImageReadParam param) throws IOExcepti
}
Info transformedInfo = lib.getInfo(data.array());
BufferedImage img = lib.decode(
data.array(), transformedInfo, transformedInfo.getAvailableSizes().get(imageIndex));
data.array(), transformedInfo, transformedInfo.getAvailableSizes().get(imageIndex));
if (extraCrop != null) {
adjustExtraCrop(imageIndex, transformedInfo, extraCrop);
img = img.getSubimage(extraCrop.x, extraCrop.y, extraCrop.width, extraCrop.height);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package de.digitalcollections.turbojpeg.imageio;

import javax.imageio.ImageWriteParam;
import java.util.Locale;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;

public class TurboJpegImageWriteParam extends ImageWriteParam {
public class TurboJpegImageWriteParam extends JPEGImageWriteParam {

public TurboJpegImageWriteParam(Locale locale) {
super(locale);
}

@Override
public boolean canWriteCompressed() {
Expand Down
Loading

0 comments on commit 1efaa89

Please sign in to comment.