Skip to content
Snippets Groups Projects
Commit 486bc672 authored by Chris Beer's avatar Chris Beer
Browse files

Respect the IIIF service preferredFormats list when picking thumbnails

Also, allow the Mirador application to filter the offered list (for browser compatibility, etc)

Replaces #3393
parent 0f6b3cf6
Branches
No related tags found
No related merge requests found
...@@ -248,6 +248,66 @@ describe('getThumbnail', () => { ...@@ -248,6 +248,66 @@ describe('getThumbnail', () => {
}); });
}); });
describe('picking the best format', () => {
const url = 'http://example.com';
it('defaults to jpg', () => {
const myCanvas = {
...canvas.__jsonld,
thumbnail: {
height: 100,
id: 'arbitrary-url',
service: [{
id: url,
profile: 'level2',
type: 'ImageService3',
}],
width: 100,
},
};
expect(createSubject(myCanvas, 'Canvas'))
.toMatchObject({ url: `${url}/full/,120/0/default.jpg` });
});
it('uses the preferred format of the service', () => {
const myCanvas = {
...canvas.__jsonld,
thumbnail: {
height: 100,
id: 'arbitrary-url',
service: [{
id: url,
preferredFormats: ['webp'],
profile: 'level2',
type: 'ImageService3',
}],
width: 100,
},
};
expect(createSubject(myCanvas, 'Canvas'))
.toMatchObject({ url: `${url}/full/,120/0/default.webp` });
});
it('can be filtered by application preferred formats', () => {
const myCanvas = {
...canvas.__jsonld,
thumbnail: {
height: 100,
id: 'arbitrary-url',
service: [{
id: url,
preferredFormats: ['webp', 'png'],
profile: 'level2',
type: 'ImageService3',
}],
width: 100,
},
};
expect(createSubject(myCanvas, 'Canvas', { preferredFormats: ['png', 'jpg'] }))
.toMatchObject({ url: `${url}/full/,120/0/default.png` });
});
});
describe('selectBestImageSize', () => { describe('selectBestImageSize', () => {
const targetWidth = 120; const targetWidth = 120;
const targetHeight = 120; const targetHeight = 120;
......
...@@ -115,12 +115,12 @@ export class IIIFThumbnail extends Component { ...@@ -115,12 +115,12 @@ export class IIIFThumbnail extends Component {
/** */ /** */
image() { image() {
const { const {
thumbnail, resource, maxHeight, maxWidth, thumbnail, resource, maxHeight, maxWidth, thumbnailsConfig,
} = this.props; } = this.props;
if (thumbnail) return thumbnail; if (thumbnail) return thumbnail;
const image = getThumbnail(resource, { maxHeight, maxWidth }); const image = getThumbnail(resource, { ...thumbnailsConfig, maxHeight, maxWidth });
if (image && image.url) return image; if (image && image.url) return image;
...@@ -189,6 +189,7 @@ IIIFThumbnail.propTypes = { ...@@ -189,6 +189,7 @@ IIIFThumbnail.propTypes = {
url: PropTypes.string.isRequired, url: PropTypes.string.isRequired,
width: PropTypes.number, width: PropTypes.number,
}), }),
thumbnailsConfig: PropTypes.object, // eslint-disable-line react/forbid-prop-types
variant: PropTypes.oneOf(['inside', 'outside']), variant: PropTypes.oneOf(['inside', 'outside']),
}; };
...@@ -203,5 +204,6 @@ IIIFThumbnail.defaultProps = { ...@@ -203,5 +204,6 @@ IIIFThumbnail.defaultProps = {
maxWidth: null, maxWidth: null,
style: {}, style: {},
thumbnail: null, thumbnail: null,
thumbnailsConfig: {},
variant: null, variant: null,
}; };
...@@ -298,6 +298,9 @@ export default { ...@@ -298,6 +298,9 @@ export default {
// ../lib/MiradorViewer.js `windowAction` // ../lib/MiradorViewer.js `windowAction`
*/ */
], ],
thumbnails: {
preferredFormats: ['jpg', 'png', 'webp', 'tif'],
},
thumbnailNavigation: { thumbnailNavigation: {
defaultPosition: 'off', // Which position for the thumbnail navigation to be be displayed. Other possible values are "far-bottom" or "far-right" defaultPosition: 'off', // Which position for the thumbnail navigation to be be displayed. Other possible values are "far-bottom" or "far-right"
displaySettings: true, // Display the settings for this in WindowTopMenu displaySettings: true, // Display the settings for this in WindowTopMenu
......
import { compose } from 'redux'; import { compose } from 'redux';
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withPlugins } from '../extend/withPlugins'; import { withPlugins } from '../extend/withPlugins';
import {
getConfig,
} from '../state/selectors';
import { IIIFThumbnail } from '../components/IIIFThumbnail'; import { IIIFThumbnail } from '../components/IIIFThumbnail';
/**
* mapStateToProps - to hook up connect
* @private
*/
const mapStateToProps = (state) => ({
thumbnailsConfig: getConfig(state).thumbnails,
});
/** /**
* Styles for withStyles HOC * Styles for withStyles HOC
*/ */
...@@ -50,6 +62,7 @@ const styles = theme => ({ ...@@ -50,6 +62,7 @@ const styles = theme => ({
const enhance = compose( const enhance = compose(
withStyles(styles), withStyles(styles),
withTranslation(), withTranslation(),
connect(mapStateToProps),
withPlugins('IIIFThumbnail'), withPlugins('IIIFThumbnail'),
); );
......
...@@ -197,7 +197,7 @@ class ThumbnailFactory { ...@@ -197,7 +197,7 @@ class ThumbnailFactory {
const region = 'full'; const region = 'full';
const quality = Utils.getImageQuality(service.getProfile()); const quality = Utils.getImageQuality(service.getProfile());
const id = service.id.replace(/\/+$/, ''); const id = service.id.replace(/\/+$/, '');
const format = 'jpg'; const format = this.getFormat(service);
return { return {
height, height,
url: [id, region, size, 0, `${quality}.${format}`].join('/'), url: [id, region, size, 0, `${quality}.${format}`].join('/'),
...@@ -205,6 +205,41 @@ class ThumbnailFactory { ...@@ -205,6 +205,41 @@ class ThumbnailFactory {
}; };
} }
/**
* 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.
*/
getFormat(service) {
const { preferredFormats = [] } = this.iiifOpts;
const servicePreferredFormats = service.getProperty('preferredFormats');
if (!servicePreferredFormats) return 'jpg';
const filteredFormats = servicePreferredFormats.filter(
value => 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. * Determines the content resource from which to derive a thumbnail to represent a given resource.
* This method is recursive. * This method is recursive.
......
...@@ -234,10 +234,13 @@ export const getRights = createSelector( ...@@ -234,10 +234,13 @@ export const getRights = createSelector(
*/ */
export function getManifestThumbnail(state, props) { export function getManifestThumbnail(state, props) {
const manifest = getManifestoInstance(state, props); const manifest = getManifestoInstance(state, props);
const { thumbnails = {} } = getConfig(state);
if (!manifest) return undefined; if (!manifest) return undefined;
const thumbnail = getThumbnail(manifest, { maxHeight: 80, maxWidth: 120 }); const thumbnail = getThumbnail(manifest, {
maxHeight: 80, maxWidth: 120, preferredFormats: thumbnails.preferredFormats,
});
return thumbnail && thumbnail.url; return thumbnail && thumbnail.url;
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment