Skip to content

Commit

Permalink
Merge branch 'release/5.0' into develop
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v1/InformationResource.java
#	src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v2/InformationResource.java
#	src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v3/InformationResource.java
  • Loading branch information
Alex Dolski committed Mar 25, 2022
2 parents 9cf7cd4 + 19a2bb3 commit efed8c2
Show file tree
Hide file tree
Showing 14 changed files with 296 additions and 72 deletions.
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@

## 5.0.6

* IIIF information endpoints always return JSON in HTTP 4xx responses.
* TurboJpegProcessor is able to generate non-JPEG derivative images, which
fixes an HTTP 415 error that would occur when trying to do that.
* Fixed a crop-offset bug that could occur when using PdfBoxProcessor to
generate JPEGs with libjpeg-turbo active.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import edu.illinois.library.cantaloupe.operation.Transpose;
import edu.illinois.library.cantaloupe.operation.overlay.Overlay;
import edu.illinois.library.cantaloupe.operation.redaction.Redaction;
import edu.illinois.library.cantaloupe.processor.codec.ImageWriter;
import edu.illinois.library.cantaloupe.processor.codec.ImageWriterFactory;
import edu.illinois.library.cantaloupe.processor.codec.jpeg.JPEGMetadataReader;
import edu.illinois.library.cantaloupe.processor.codec.jpeg.TurboJPEGImageReader;
import edu.illinois.library.cantaloupe.processor.codec.jpeg.TurboJPEGImageWriter;
Expand Down Expand Up @@ -53,9 +55,6 @@ public class TurboJpegProcessor extends AbstractProcessor
private static final Logger LOGGER =
LoggerFactory.getLogger(TurboJpegProcessor.class);

private static final Set<Format> SUPPORTED_OUTPUT_FORMATS =
Set.of(Format.get("jpg"));

private static boolean isClassInitialized;

private static final boolean USE_FAST_DECODE_DCT = false;
Expand Down Expand Up @@ -101,7 +100,7 @@ public void close() {

@Override
public Set<Format> getAvailableOutputFormats() {
return SUPPORTED_OUTPUT_FORMATS;
return ImageWriterFactory.supportedFormats();
}

@Override
Expand Down Expand Up @@ -158,6 +157,17 @@ public boolean supportsSourceFormat(Format format) {
public void process(final OperationList opList,
final Info info,
final OutputStream outputStream) throws FormatException, ProcessorException {
if (Format.get("jpg").equals(opList.getOutputFormat())) {
processUsingTurboJPEGWriter(opList, info, outputStream);
} else {
processUsingImageIOWriter(opList, info, outputStream);
}
}

private void processUsingTurboJPEGWriter(
final OperationList opList,
final Info info,
final OutputStream outputStream) throws FormatException, ProcessorException {
final Dimension fullSize = info.getSize();
final ReductionFactor reductionFactor = new ReductionFactor();
final ScaleConstraint scaleConstraint = opList.getScaleConstraint();
Expand Down Expand Up @@ -253,6 +263,97 @@ public void process(final OperationList opList,
}
}

private void processUsingImageIOWriter(
final OperationList opList,
final Info info,
final OutputStream outputStream) throws FormatException, ProcessorException {
final Dimension fullSize = info.getSize();
final ReductionFactor reductionFactor = new ReductionFactor();
final ScaleConstraint scaleConstraint = opList.getScaleConstraint();

try {
imageReader.setUseFastDCT(USE_FAST_DECODE_DCT);

final Rectangle roiWithinSafeRegion = new Rectangle();
BufferedImage image =
imageReader.readAsBufferedImage(roiWithinSafeRegion);

Orientation orientation = Orientation.ROTATE_0;
final Metadata metadata = info.getMetadata();
if (metadata != null) {
orientation = metadata.getOrientation();
}

// Apply the crop operation, if present, and retain a reference
// to it for subsequent operations to refer to.
Crop crop = new CropByPercent();
for (Operation op : opList) {
if (op instanceof Crop) {
crop = (Crop) op;
if (crop.hasEffect(fullSize, opList)) {
// The TurboJPEG writer cannot deal with a
// BufferedImage that has been "virtually cropped" by
// BufferedImage.getSubimage(). We must tell this
// method to copy the underlying raster.
image = Java2DUtil.crop(image, crop, reductionFactor,
scaleConstraint, true);
}
}
}

// All operations have already been corrected for the orientation,
// but the image itself has not yet been corrected.
if (!Orientation.ROTATE_0.equals(orientation)) {
image = Java2DUtil.rotate(image, orientation);
}

final Set<Redaction> redactions = opList.stream()
.filter(op -> op instanceof Redaction)
.filter(op -> op.hasEffect(fullSize, opList))
.map(op -> (Redaction) op)
.collect(Collectors.toSet());
Java2DUtil.applyRedactions(image, fullSize, crop,
new double[] { 1.0, 1.0 }, reductionFactor,
scaleConstraint, redactions);

for (Operation op : opList) {
if (!op.hasEffect(fullSize, opList)) {
continue;
}
if (op instanceof Scale) {
final Scale scale = (Scale) op;
final boolean isLinear = scale.isLinear() &&
!scale.isUp(fullSize, scaleConstraint);
if (isLinear) {
image = Java2DUtil.convertColorToLinearRGB(image);
}
image = Java2DUtil.scale(image, scale,
scaleConstraint, reductionFactor, isLinear);
if (isLinear) {
image = Java2DUtil.convertColorToSRGB(image);
}
} else if (op instanceof Transpose) {
image = Java2DUtil.transpose(image, (Transpose) op);
} else if (op instanceof Rotate) {
image = Java2DUtil.rotate(image, (Rotate) op);
} else if (op instanceof ColorTransform) {
image = Java2DUtil.transformColor(image, (ColorTransform) op);
} else if (op instanceof Sharpen) {
image = Java2DUtil.sharpen(image, (Sharpen) op);
} else if (op instanceof Overlay) {
Java2DUtil.applyOverlay(image, (Overlay) op);
}
}
Encode encode = (Encode) opList.getFirst(Encode.class);
ImageWriter writer = new ImageWriterFactory().newImageWriter(encode);
writer.write(image, outputStream);
} catch (SourceFormatException e) {
throw e;
} catch (IOException e) {
throw new ProcessorException(e);
}
}

@Override
public Info readInfo() throws IOException {
return Info.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ public interface Callback {
* <p>Performs authorization using an {@link
* edu.illinois.library.cantaloupe.auth.Authorizer}.</p>
*
* <p>Any exceptions will bubble up through {@link #handle()}. This
* feature can be used to support e.g. a {@link ResourceException} with
* a custom status depending on the authorization result.</p>
*
* <p>This is the first callback to get called.</p>
*
* @return Authorization result.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package edu.illinois.library.cantaloupe.resource.iiif.v1;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import edu.illinois.library.cantaloupe.http.Method;
import edu.illinois.library.cantaloupe.http.Status;
import edu.illinois.library.cantaloupe.image.Format;
import edu.illinois.library.cantaloupe.image.Info;
import edu.illinois.library.cantaloupe.resource.JacksonRepresentation;
Expand Down Expand Up @@ -52,16 +55,7 @@ public void doGET() throws Exception {
class CustomCallback implements InformationRequestHandler.Callback {
@Override
public boolean authorize() throws Exception {
try {
// The logic here is somewhat convoluted. See the method
// documentation for more information.
return InformationResource.this.preAuthorize();
} catch (ResourceException e) {
if (e.getStatus().getCode() > 400) {
throw e;
}
}
return false;
return InformationResource.this.preAuthorize();
}

@Override
Expand All @@ -85,18 +79,25 @@ public void knowAvailableOutputFormats(Set<Format> formats) {
.withRequestContext(getRequestContext())
.withCallback(new CustomCallback())
.build()) {
Info info = handler.handle();

Information iiifInfo = new InformationFactory().newImageInfo(
getImageURI(),
availableOutputFormats,
info,
getPageIndex(),
getMetaIdentifier().getScaleConstraint());

addHeaders(info, iiifInfo);
new JacksonRepresentation(iiifInfo)
.write(getResponse().getOutputStream());
try {
Info info = handler.handle();
Information iiifInfo = new InformationFactory().newImageInfo(
getImageURI(),
availableOutputFormats,
info,
getPageIndex(),
getMetaIdentifier().getScaleConstraint());
addHeaders(info, iiifInfo);
new JacksonRepresentation(iiifInfo)
.write(getResponse().getOutputStream());
} catch (ResourceException e) {
if (e.getStatus().getCode() < 500) {
newHTTP4xxRepresentation(e.getStatus(), e.getMessage())
.write(getResponse().getOutputStream());
} else {
throw e;
}
}
}
}

Expand Down Expand Up @@ -136,4 +137,14 @@ private String getNegotiatedMediaType() {
return mediaType + ";charset=UTF-8";
}

private JacksonRepresentation newHTTP4xxRepresentation(Status status,
String message) {
final Map<String, Object> map = new LinkedHashMap<>(); // preserves key order
map.put("@context", "http://library.stanford.edu/iiif/image-api/1.1/context.json");
map.put("@id", getImageURI());
map.put("status", status.getCode());
map.put("message", message);
return new JacksonRepresentation(map);
}

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package edu.illinois.library.cantaloupe.resource.iiif.v2;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import edu.illinois.library.cantaloupe.http.Method;
import edu.illinois.library.cantaloupe.http.Status;
import edu.illinois.library.cantaloupe.image.Format;
import edu.illinois.library.cantaloupe.image.Info;
import edu.illinois.library.cantaloupe.processor.codec.ImageWriterFactory;
Expand All @@ -16,6 +19,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.script.ScriptException;

/**
* Handles information requests.
*
Expand Down Expand Up @@ -57,16 +62,7 @@ public void doGET() throws Exception {
class CustomCallback implements InformationRequestHandler.Callback {
@Override
public boolean authorize() throws Exception {
try {
// The logic here is somewhat convoluted. See the method
// documentation for more information.
return InformationResource.this.preAuthorize();
} catch (ResourceException e) {
if (e.getStatus().getCode() > 400) {
throw e;
}
}
return false;
return InformationResource.this.preAuthorize();
}

@Override
Expand All @@ -90,10 +86,19 @@ public void knowAvailableOutputFormats(Set<Format> formats) {
.withRequestContext(getRequestContext())
.withCallback(new CustomCallback())
.build()) {
Info info = handler.handle();
addHeaders(info);
newRepresentation(info, availableOutputFormats)
.write(getResponse().getOutputStream());
try {
Info info = handler.handle();
addHeaders(info);
newRepresentation(info, availableOutputFormats)
.write(getResponse().getOutputStream());
} catch (ResourceException e) {
if (e.getStatus().getCode() < 500) {
newHTTP4xxRepresentation(e.getStatus(), e.getMessage())
.write(getResponse().getOutputStream());
} else {
throw e;
}
}
}
}

Expand Down Expand Up @@ -144,4 +149,17 @@ private JacksonRepresentation newRepresentation(Info info,
return new JacksonRepresentation(iiifInfo);
}

private JacksonRepresentation newHTTP4xxRepresentation(
Status status,
String message) throws ScriptException {
final Map<String,Object> map = new LinkedHashMap<>(); // preserves key order
map.put("@context", "http://iiif.io/api/image/2/context.json");
map.put("@id", getImageURI());
map.put("protocol", "http://iiif.io/api/image");
map.put("status", status.getCode());
map.put("message", message);
map.putAll(getDelegateProxy().getExtraIIIF2InformationResponseKeys());
return new JacksonRepresentation(map);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ private void validateScale(Dimension virtualSize,
if (scale.isWidthUp(virtualSize, constraint) ||
scale.isHeightUp(virtualSize, constraint)) {
throw new ScaleRestrictedException("Requests for scales in " +
"excess of 100% must prefix the scale path component " +
"excess of 100% must prefix the size path component " +
"with a ^ character.",
Status.BAD_REQUEST);
}
Expand Down
Loading

0 comments on commit efed8c2

Please sign in to comment.