1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.iview2.iiif;
20
21 import java.awt.Graphics2D;
22 import java.awt.image.BufferedImage;
23 import java.io.IOException;
24 import java.nio.file.FileSystem;
25 import java.nio.file.FileSystemNotFoundException;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.util.Arrays;
29 import java.util.Locale;
30 import java.util.Map;
31 import java.util.Optional;
32 import java.util.stream.Collectors;
33
34 import javax.imageio.ImageIO;
35 import javax.imageio.ImageReader;
36
37 import org.apache.logging.log4j.LogManager;
38 import org.apache.logging.log4j.Logger;
39 import org.mycore.access.MCRAccessException;
40 import org.mycore.access.MCRAccessManager;
41 import org.mycore.common.config.MCRConfiguration2;
42 import org.mycore.common.config.MCRConfigurationException;
43 import org.mycore.iiif.image.MCRIIIFImageUtil;
44 import org.mycore.iiif.image.impl.MCRIIIFImageImpl;
45 import org.mycore.iiif.image.impl.MCRIIIFImageNotFoundException;
46 import org.mycore.iiif.image.impl.MCRIIIFImageProvidingException;
47 import org.mycore.iiif.image.impl.MCRIIIFUnsupportedFormatException;
48 import org.mycore.iiif.image.model.MCRIIIFFeatures;
49 import org.mycore.iiif.image.model.MCRIIIFImageInformation;
50 import org.mycore.iiif.image.model.MCRIIIFImageProfile;
51 import org.mycore.iiif.image.model.MCRIIIFImageQuality;
52 import org.mycore.iiif.image.model.MCRIIIFImageSourceRegion;
53 import org.mycore.iiif.image.model.MCRIIIFImageTargetRotation;
54 import org.mycore.iiif.image.model.MCRIIIFImageTargetSize;
55 import org.mycore.iiif.image.model.MCRIIIFImageTileInformation;
56 import org.mycore.iiif.model.MCRIIIFBase;
57 import org.mycore.imagetiler.MCRTiledPictureProps;
58 import org.mycore.iview2.backend.MCRDefaultTileFileProvider;
59 import org.mycore.iview2.backend.MCRTileFileProvider;
60 import org.mycore.iview2.backend.MCRTileInfo;
61 import org.mycore.iview2.services.MCRIView2Tools;
62
63 public class MCRIVIEWIIIFImageImpl extends MCRIIIFImageImpl {
64
65 public static final String DEFAULT_PROTOCOL = "http://iiif.io/api/image";
66
67 public static final double LOG_HALF = Math.log(1.0 / 2.0);
68
69 public static final java.util.List<String> SUPPORTED_FORMATS = Arrays.asList(ImageIO.getReaderFileSuffixes());
70
71 public static final String MAX_BYTES_PROPERTY = "MaxImageBytes";
72
73 private static final String TILE_FILE_PROVIDER_PROPERTY = "TileFileProvider";
74
75 private static final String IDENTIFIER_SEPARATOR_PROPERTY = "IdentifierSeparator";
76
77 private static final Logger LOGGER = LogManager.getLogger(MCRIVIEWIIIFImageImpl.class);
78
79 private final java.util.List<String> transparentFormats;
80
81 protected final MCRTileFileProvider tileFileProvider;
82
83 public MCRIVIEWIIIFImageImpl(String implName) {
84 super(implName);
85 Map<String, String> properties = getProperties();
86 String tileFileProviderClassName = properties.get(TILE_FILE_PROVIDER_PROPERTY);
87 if (tileFileProviderClassName == null) {
88 tileFileProvider = new MCRDefaultTileFileProvider();
89 } else {
90 Optional<MCRTileFileProvider> optTFP = MCRConfiguration2
91 .getInstanceOf(getConfigPrefix() + TILE_FILE_PROVIDER_PROPERTY);
92 if (optTFP.isPresent()) {
93 tileFileProvider = optTFP.get();
94 } else {
95 throw new MCRConfigurationException(
96 "Configurated class (" + TILE_FILE_PROVIDER_PROPERTY + ") not found: "
97 + tileFileProviderClassName);
98 }
99 }
100
101 transparentFormats = Arrays.asList(properties.get("TransparentFormats").split(","));
102 }
103
104 private String buildURL(String identifier) {
105 return MCRIIIFImageUtil.getIIIFURL(this) + MCRIIIFImageUtil.encodeImageIdentifier(identifier);
106 }
107
108 @Override
109 public BufferedImage provide(String identifier,
110 MCRIIIFImageSourceRegion region,
111 MCRIIIFImageTargetSize targetSize,
112 MCRIIIFImageTargetRotation rotation,
113 MCRIIIFImageQuality imageQuality,
114 String format) throws MCRIIIFImageNotFoundException, MCRIIIFImageProvidingException,
115 MCRIIIFUnsupportedFormatException, MCRAccessException {
116
117 long resultingSize = (long) targetSize.getHeight() * targetSize.getWidth()
118 * (imageQuality.equals(MCRIIIFImageQuality.color) ? 3 : 1);
119
120 long maxImageSize = Optional.ofNullable(getProperties().get(MAX_BYTES_PROPERTY)).map(Long::parseLong)
121 .orElseThrow(() -> MCRConfiguration2.createConfigurationException(getConfigPrefix() + MAX_BYTES_PROPERTY));
122 if (resultingSize > maxImageSize) {
123 throw new MCRIIIFImageProvidingException("Maximal image size is " + (maxImageSize / 1024 / 1024) + "MB. ["
124 + resultingSize + "/" + maxImageSize + "]");
125 }
126
127 if (!SUPPORTED_FORMATS.contains(format.toLowerCase(Locale.ENGLISH))) {
128 throw new MCRIIIFUnsupportedFormatException(format);
129 }
130
131 MCRTileInfo tileInfo = createTileInfo(identifier);
132 Optional<Path> oTileFile = tileFileProvider.getTileFile(tileInfo);
133 if (oTileFile.isEmpty()) {
134 throw new MCRIIIFImageNotFoundException(identifier);
135 }
136 checkTileFile(identifier, tileInfo, oTileFile.get());
137 MCRTiledPictureProps tiledPictureProps = getTiledPictureProps(oTileFile.get());
138
139 int sourceWidth = region.getX2() - region.getX1();
140 int sourceHeight = region.getY2() - region.getY1();
141
142 double targetWidth = targetSize.getWidth();
143 double targetHeight = targetSize.getHeight();
144
145 double rotatationRadians = Math.toRadians(rotation.getDegrees());
146 double sinRotation = Math.sin(rotatationRadians);
147 double cosRotation = Math.cos(rotatationRadians);
148
149 final int height = (int) (Math.abs(targetWidth * sinRotation) + Math.abs(targetHeight * cosRotation));
150 final int width = (int) (Math.abs(targetWidth * cosRotation) + Math.abs(targetHeight * sinRotation));
151
152 BufferedImage targetImage;
153 switch (imageQuality) {
154 case bitonal:
155 targetImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
156 break;
157 case gray:
158 targetImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
159 break;
160 case color:
161 default:
162 if (transparentFormats.contains(format)) {
163 targetImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
164 } else {
165 targetImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
166 }
167 }
168
169
170 double largestScaling = Math.max(targetWidth / sourceWidth, targetHeight / sourceHeight);
171
172
173 int sourceZoomLevel = (int) Math.min(
174 Math.max(0, Math.ceil(tiledPictureProps.getZoomlevel() - Math.log(largestScaling) / LOG_HALF)),
175 tiledPictureProps.getZoomlevel());
176
177
178 double zoomLevelScale = Math.min(1.0, Math.pow(0.5, tiledPictureProps.getZoomlevel() - sourceZoomLevel));
179
180
181 double drawScaleX = (targetWidth / (sourceWidth * zoomLevelScale)),
182 drawScaleY = (targetHeight / (sourceHeight * zoomLevelScale));
183
184
185 double x1 = region.getX1() * zoomLevelScale,
186 x2 = region.getX2() * zoomLevelScale,
187 y1 = region.getY1() * zoomLevelScale,
188 y2 = region.getY2() * zoomLevelScale;
189
190
191 int x1Tile = (int) Math.floor(x1 / 256),
192 y1Tile = (int) Math.floor(y1 / 256),
193 x2Tile = (int) Math.ceil(x2 / 256),
194 y2Tile = (int) Math.ceil(y2 / 256);
195
196 try (FileSystem zipFileSystem = MCRIView2Tools.getFileSystem(oTileFile.get())) {
197 Path rootPath = zipFileSystem.getPath("/");
198
199 Graphics2D graphics = targetImage.createGraphics();
200 if (rotation.isMirrored()) {
201 graphics.scale(-1, 1);
202 graphics.translate(-width, 0);
203 }
204
205 int xt = (int) ((targetWidth - 1) / 2), yt = (int) ((targetHeight - 1) / 2);
206 graphics.translate((width - targetWidth) / 2, (height - targetHeight) / 2);
207 graphics.rotate(rotatationRadians, xt, yt);
208
209 graphics.scale(drawScaleX, drawScaleY);
210 graphics.translate(-x1, -y1);
211
212 graphics.scale(zoomLevelScale, zoomLevelScale);
213 graphics.setClip(region.getX1(), region.getY1(), sourceWidth, sourceHeight);
214 graphics.scale(1 / zoomLevelScale, 1 / zoomLevelScale);
215
216 LOGGER.info(String.format(Locale.ROOT, "Using zoom-level: %d and scales %s/%s!", sourceZoomLevel,
217 drawScaleX, drawScaleY));
218
219 for (int x = x1Tile; x < x2Tile; x++) {
220 for (int y = y1Tile; y < y2Tile; y++) {
221 ImageReader imageReader = MCRIView2Tools.getTileImageReader();
222 BufferedImage tile = MCRIView2Tools.readTile(rootPath, imageReader, sourceZoomLevel, x, y);
223 graphics.drawImage(tile, x * 256, y * 256, null);
224 }
225 }
226
227 } catch (IOException e) {
228 throw new MCRIIIFImageProvidingException("Error while reading tiles!", e);
229 }
230
231 return targetImage;
232 }
233
234 public MCRIIIFImageInformation getInformation(String identifier)
235 throws MCRIIIFImageNotFoundException, MCRIIIFImageProvidingException, MCRAccessException {
236 try {
237 MCRTileInfo tileInfo = createTileInfo(identifier);
238 Optional<Path> oTiledFile = tileFileProvider.getTileFile(tileInfo);
239 if (oTiledFile.isEmpty()) {
240 throw new MCRIIIFImageNotFoundException(identifier);
241 }
242 final Path tileFilePath = oTiledFile.get();
243 checkTileFile(identifier, tileInfo, tileFilePath);
244 MCRTiledPictureProps tiledPictureProps = getTiledPictureProps(tileFilePath);
245
246 MCRIIIFImageInformation imageInformation = new MCRIIIFImageInformation(MCRIIIFBase.API_IMAGE_2,
247 buildURL(identifier), DEFAULT_PROTOCOL, tiledPictureProps.getWidth(), tiledPictureProps.getHeight(),
248 Files.getLastModifiedTime(tileFilePath).toMillis());
249
250 MCRIIIFImageTileInformation tileInformation = new MCRIIIFImageTileInformation(256, 256);
251 for (int i = 0; i < tiledPictureProps.getZoomlevel(); i++) {
252 tileInformation.scaleFactors.add((int) Math.pow(2, i));
253 }
254
255 imageInformation.tiles.add(tileInformation);
256
257 return imageInformation;
258 } catch (FileSystemNotFoundException | IOException e) {
259 LOGGER.error("Could not find Iview ZIP for {}", identifier, e);
260 throw new MCRIIIFImageNotFoundException(identifier);
261 }
262 }
263
264 @Override
265 public MCRIIIFImageProfile getProfile() {
266 MCRIIIFImageProfile mcriiifImageProfile = new MCRIIIFImageProfile();
267
268 mcriiifImageProfile.formats = SUPPORTED_FORMATS.stream().filter(s -> !s.isEmpty())
269 .collect(Collectors.toSet());
270
271 mcriiifImageProfile.qualities.add("color");
272 mcriiifImageProfile.qualities.add("bitonal");
273 mcriiifImageProfile.qualities.add("gray");
274
275 mcriiifImageProfile.supports.add(MCRIIIFFeatures.mirroring);
276 mcriiifImageProfile.supports.add(MCRIIIFFeatures.regionByPct);
277 mcriiifImageProfile.supports.add(MCRIIIFFeatures.regionByPx);
278 mcriiifImageProfile.supports.add(MCRIIIFFeatures.rotationArbitrary);
279 mcriiifImageProfile.supports.add(MCRIIIFFeatures.rotationBy90s);
280 mcriiifImageProfile.supports.add(MCRIIIFFeatures.sizeAboveFull);
281 mcriiifImageProfile.supports.add(MCRIIIFFeatures.sizeByWhListed);
282 mcriiifImageProfile.supports.add(MCRIIIFFeatures.sizeByForcedWh);
283 mcriiifImageProfile.supports.add(MCRIIIFFeatures.sizeByH);
284 mcriiifImageProfile.supports.add(MCRIIIFFeatures.sizeByPct);
285 mcriiifImageProfile.supports.add(MCRIIIFFeatures.sizeByW);
286 mcriiifImageProfile.supports.add(MCRIIIFFeatures.sizeByWh);
287
288 return mcriiifImageProfile;
289 }
290
291 private MCRTiledPictureProps getTiledPictureProps(Path tiledFile) throws MCRIIIFImageProvidingException {
292 MCRTiledPictureProps tiledPictureProps = null;
293 try (FileSystem fileSystem = MCRIView2Tools.getFileSystem(tiledFile)) {
294 tiledPictureProps = MCRTiledPictureProps.getInstanceFromDirectory(fileSystem.getPath("/"));
295 } catch (IOException e) {
296 throw new MCRIIIFImageProvidingException("Could not provide image information!", e);
297 }
298 return tiledPictureProps;
299 }
300
301 protected MCRTileInfo createTileInfo(String identifier) throws MCRIIIFImageNotFoundException {
302 MCRTileInfo tileInfo = null;
303 String id = identifier.contains(":/") ? identifier.replaceFirst(":/", "/") : identifier;
304 String separator = getProperties().getOrDefault(IDENTIFIER_SEPARATOR_PROPERTY, "/");
305 String[] splittedIdentifier = id.split(separator, 2);
306 switch (splittedIdentifier.length) {
307 case 1:
308 tileInfo = new MCRTileInfo(null, identifier, null);
309 break;
310 case 2:
311 tileInfo = new MCRTileInfo(splittedIdentifier[0], splittedIdentifier[1], null);
312 break;
313 default:
314 throw new MCRIIIFImageNotFoundException(identifier);
315 }
316 return tileInfo;
317 }
318
319 private void checkTileFile(String identifier, MCRTileInfo tileInfo, Path tileFilePath)
320 throws MCRAccessException, MCRIIIFImageNotFoundException {
321 if (!Files.exists(tileFilePath)) {
322 throw new MCRIIIFImageNotFoundException(identifier);
323 }
324 if (tileInfo.getDerivate() != null
325 && !checkPermission(identifier, tileInfo)) {
326 throw MCRAccessException.missingPermission(
327 "View the file " + tileInfo.getImagePath() + " in " + tileInfo.getDerivate(), tileInfo.getDerivate(),
328 MCRAccessManager.PERMISSION_VIEW);
329 }
330 }
331
332 protected boolean checkPermission(String identifier, MCRTileInfo tileInfo) {
333 return MCRAccessManager.checkPermission(tileInfo.getDerivate(), MCRAccessManager.PERMISSION_VIEW) ||
334 MCRAccessManager.checkPermission(tileInfo.getDerivate(), MCRAccessManager.PERMISSION_READ);
335 }
336 }