UNPKG

mirador

Version:

An open-source, web-based 'multi-up' viewer that supports zoom-pan-rotate functionality, ability to display/compare simple images, and images with annotations.

319 lines (268 loc) 12.5 kB
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } import { Utils } from 'manifesto.js'; import MiradorManifest from './MiradorManifest'; import MiradorCanvas from './MiradorCanvas'; import asArray from './asArray'; /** */ function isLevel0ImageProfile(service) { var profile = service.getProfile(); // work around a bug in manifesto with normalized urls that strip # values. if (profile.endsWith('#level1') || profile.endsWith('#level2')) return false; // support IIIF v3-style profiles if (profile === 'level0') return true; return Utils.isLevel0ImageProfile(profile); } /** */ function isLevel2ImageProfile(service) { var profile = service.getProfile(); // work around a bug in manifesto with normalized urls that strip # values. if (profile.endsWith('#level0') || profile.endsWith('#level1')) return false; // support IIIF v3-style profiles if (profile === 'level2') return true; return Utils.isLevel2ImageProfile(profile); } /** */ function iiifv3ImageServiceType(service) { var type = service.getProperty('type') || []; return asArray(type).some(function (v) { return v.startsWith('ImageService'); }); } /** */ function iiifImageService(resource) { var service = resource && resource.getServices().find(function (s) { return iiifv3ImageServiceType(s) || Utils.isImageProfile(s.getProfile()); }); if (!service) return undefined; return service; } /** */ var ThumbnailFactory = /*#__PURE__*/function () { /** */ function ThumbnailFactory(resource) { var iiifOpts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; _classCallCheck(this, ThumbnailFactory); this.resource = resource; this.iiifOpts = iiifOpts; } /** */ _createClass(ThumbnailFactory, [{ key: "iiifThumbnailUrl", value: /** * Determines the appropriate thumbnail to use to represent an Image Resource. * @param {Object} resource The Image Resource from which to derive a thumbnail * @return {Object} The thumbnail URL and any spatial dimensions that can be determined */ function iiifThumbnailUrl(resource) { var size; var width; var height; var minDimension = 120; var maxHeight = minDimension; var maxWidth = minDimension; var _this$iiifOpts = this.iiifOpts, requestedMaxHeight = _this$iiifOpts.maxHeight, requestedMaxWidth = _this$iiifOpts.maxWidth; if (requestedMaxHeight) maxHeight = Math.max(requestedMaxHeight, minDimension); if (requestedMaxWidth) maxWidth = Math.max(requestedMaxWidth, minDimension); var service = iiifImageService(resource); if (!service) return ThumbnailFactory.staticImageUrl(resource); var aspectRatio = resource.getWidth() && resource.getHeight() && resource.getWidth() / resource.getHeight(); var target = requestedMaxWidth && requestedMaxHeight ? requestedMaxWidth * requestedMaxHeight : maxHeight * maxWidth; var closestSize = ThumbnailFactory.selectBestImageSize(service, target); if (closestSize) { // Embedded service advertises an appropriate size width = closestSize.width; height = closestSize.height; size = "".concat(width, ",").concat(height); } else if (isLevel0ImageProfile(service)) { /** Bail if the best available size is the full size.. maybe we'll get lucky with the @id */ if (!service.getProperty('height') && !service.getProperty('width')) { return ThumbnailFactory.staticImageUrl(resource); } } else if (requestedMaxHeight && requestedMaxWidth) { // IIIF level 2, no problem. if (isLevel2ImageProfile(service)) { size = "!".concat(maxWidth, ",").concat(maxHeight); width = maxWidth; height = maxHeight; if (aspectRatio && aspectRatio > 1) height = Math.round(maxWidth / aspectRatio); if (aspectRatio && aspectRatio < 1) width = Math.round(maxHeight * aspectRatio); } else if (maxWidth / maxHeight < aspectRatio) { size = "".concat(maxWidth, ","); width = maxWidth; if (aspectRatio) height = Math.round(maxWidth / aspectRatio); } else { size = ",".concat(maxHeight); height = maxHeight; if (aspectRatio) width = Math.round(maxHeight * aspectRatio); } } else if (requestedMaxHeight && !requestedMaxWidth) { size = ",".concat(maxHeight); height = maxHeight; if (aspectRatio) width = Math.round(maxHeight * aspectRatio); } else if (!requestedMaxHeight && requestedMaxWidth) { size = "".concat(maxWidth, ","); width = maxWidth; if (aspectRatio) height = Math.round(maxWidth / aspectRatio); } else { size = ",".concat(minDimension); height = minDimension; if (aspectRatio) width = Math.round(height * aspectRatio); } var region = 'full'; var quality = Utils.getImageQuality(service.getProfile()); var id = service.id.replace(/\/+$/, ''); var format = this.getFormat(service); return { height: height, url: [id, region, size, 0, "".concat(quality, ".").concat(format)].join('/'), width: width }; } /** * Figure out what format thumbnail to use by looking at the preferred formats * on offer, and selecting a format shared in common with the application's * preferred format list. * * Fall back to jpg, which is required to work for all IIIF services. */ }, { key: "getFormat", value: function getFormat(service) { var _this$iiifOpts$prefer = this.iiifOpts.preferredFormats, preferredFormats = _this$iiifOpts$prefer === void 0 ? [] : _this$iiifOpts$prefer; var servicePreferredFormats = service.getProperty('preferredFormats'); if (!servicePreferredFormats) return 'jpg'; var filteredFormats = servicePreferredFormats.filter(function (value) { return preferredFormats.includes(value); }); // this is a format found in common between the preferred formats of the service // and the application if (filteredFormats[0]) return filteredFormats[0]; // IIIF Image API guarantees jpg support; if it wasn't provided by the service // but the application is fine with it, we might as well try it. if (!servicePreferredFormats.includes('jpg') && preferredFormats.includes('jpg')) { return 'jpg'; } // there were no formats in common, and the application didn't want jpg... so // just trust that the IIIF service is advertising something useful? if (servicePreferredFormats[0]) return servicePreferredFormats[0]; // JPG support is guaranteed by the spec, so it's a good worst-case fallback return 'jpg'; } /** * Determines the content resource from which to derive a thumbnail to represent a given resource. * This method is recursive. * @param {Object} resource A IIIF resource to derive a thumbnail from * @return {Object|undefined} The Image Resource to derive a thumbnail from, or undefined * if no appropriate resource exists */ }, { key: "getSourceContentResource", value: function getSourceContentResource(resource) { var thumbnail = resource.getThumbnail(); // Any resource type may have a thumbnail if (thumbnail) { if (typeof thumbnail.__jsonld === 'string') return thumbnail.__jsonld; // Prefer an image's ImageService over its image's thumbnail // Note that Collection, Manifest, and Canvas don't have `getType()` if (!resource.isCollection() && !resource.isManifest() && !resource.isCanvas()) { if (resource.getType() === 'image' && iiifImageService(resource) && !iiifImageService(thumbnail)) { return resource; } } return thumbnail; } if (resource.isCollection()) { var firstManifest = resource.getManifests()[0]; if (firstManifest) return this.getSourceContentResource(firstManifest); return undefined; } if (resource.isManifest()) { var miradorManifest = new MiradorManifest(resource); var canvas = miradorManifest.startCanvas || miradorManifest.canvasAt(0); if (canvas) return this.getSourceContentResource(canvas); return undefined; } if (resource.isCanvas()) { var image = ThumbnailFactory.getPreferredImage(resource); if (image) return this.getSourceContentResource(image); return undefined; } if (resource.getType() === 'image') { return resource; } return undefined; } /** * Gets a thumbnail representing the resource. * @return {Object|undefined} A thumbnail representing the resource, or undefined if none could * be determined */ }, { key: "get", value: function get() { if (!this.resource) return undefined; // Determine which content resource we should use to derive a thumbnail var sourceContentResource = this.getSourceContentResource(this.resource); if (!sourceContentResource) return undefined; // Special treatment for external resources if (typeof sourceContentResource === 'string') return { url: sourceContentResource }; return this.iiifThumbnailUrl(sourceContentResource); } }], [{ key: "staticImageUrl", value: function staticImageUrl(resource) { return { height: resource.getProperty('height'), url: resource.id, width: resource.getProperty('width') }; } /** * Selects the image resource that is representative of the given canvas. * @param {Object} canvas A Manifesto Canvas * @return {Object} A Manifesto Image Resource */ }, { key: "getPreferredImage", value: function getPreferredImage(canvas) { var miradorCanvas = new MiradorCanvas(canvas); return miradorCanvas.iiifImageResources[0] || miradorCanvas.imageResource; } /** * Chooses the best available image size based on a target area (w x h) value. * @param {Object} service A IIIF Image API service that has a `sizes` array * @param {Number} targetArea The target area value to compare potential sizes against * @return {Object|undefined} The best size, or undefined if none are acceptable */ }, { key: "selectBestImageSize", value: function selectBestImageSize(service, targetArea) { var sizes = asArray(service.getProperty('sizes')); var closestSize = { "default": true, height: service.getProperty('height') || Number.MAX_SAFE_INTEGER, width: service.getProperty('width') || Number.MAX_SAFE_INTEGER }; /** Compare the total image area to our target */ var imageFitness = function imageFitness(test) { return test.width * test.height - targetArea; }; /** Look for the size that's just bigger than we prefer... */ closestSize = sizes.reduce(function (best, test) { var score = imageFitness(test); if (score < 0) return best; return Math.abs(score) < Math.abs(imageFitness(best)) ? test : best; }, closestSize); /** .... but not "too" big; we'd rather scale up an image than download too much */ if (closestSize.width * closestSize.height > targetArea * 6) { closestSize = sizes.reduce(function (best, test) { return Math.abs(imageFitness(test)) < Math.abs(imageFitness(best)) ? test : best; }, closestSize); } if (closestSize["default"]) return undefined; return closestSize; } }]); return ThumbnailFactory; }(); /** */ function getBestThumbnail(resource, iiifOpts) { return new ThumbnailFactory(resource, iiifOpts).get(); } export { getBestThumbnail as default, ThumbnailFactory };