Open iangilman opened 9 years ago
The factory will produce properly configured IIIF resource objects that have the information they need from the IIIF Metadata that is passed to them. There is a lot of checking that needs to go on to determine whether the resource is static or dynamic, is an alternate of another image, what its clip mask should be (if any), and what its local coordinates should be in the canvas space. The relevant specification sections are 6.4 Image resources and 7.1 Segments, and 7.4 Choice of Alternate Resources. You will notice language such as "MAY", "SHOULD", and "MUST", which will affect the implementation.
Ultimately, we want some uniform configuration for a resourceObject
. Presumably these properties will be:
@id
, which "MUST" dereferenceable.)There are 3 possible ways the image/resource may be represented structurally, based on their @type
. Note that both oa:SpecificResource
s or dcterms:Image
resource types may appear inside of oa:Choice
options, and that eventually one does always "drill down" to a vanilla dcterms:Image
.
dcterms:Image
The simplest version is just that we have an image
with a static resource
(no service with the correct @context
). We dereference the image and assume that it has the same aspect ratio and size as the canvas in the on
field (because there is no hash identifier in the on
field).
{
"@context":"http://iiif.io/api/presentation/2/context.json",
"@id":"http://www.example.org/iiif/book1/annotation/p0001-image",
"@type":"oa:Annotation",
"motivation":"sc:painting",
"resource": {
"@id":"http://www.example.org/iiif/book1/res/page1.jpg",
"@type":"dctypes:Image",
"format":"image/jpeg",
"service": {
"@context": "http://iiif.io/api/image/2/context.json",
"@id":"http://www.example.org/images/book1-page1",
"profile":"http://iiif.io/api/image/2/profiles/level2.json",
},
},
"on":"http://www.example.org/iiif/book1/canvas/p1"
}
If the @id
image has a hash identifier in it, then we need to forcibly crop it (so pass in a clipMask).
{
"@context":"http://iiif.io/api/presentation/2/context.json",
"@id":"http://www.example.org/iiif/book1/annotation/anno1",
"@type":"oa:Annotation",
"motivation":"sc:painting",
"resource":{
"@id":"http://www.example.org/iiif/book1/res/page1.jpg#xywh=40,50,1200,1800",
"@type":"dctypes:Image",
"format":"image/jpeg"
},
"on":"http://www.example.org/iiif/book1/canvas/p1"
}
Note that the on
field has no hash identifier, so we assume our tilesource will have the same size and aspect ratio of the canvas.
If the "on" field has a hash identifier (`"on":"http://www.example.org/iiif/book1/canvas/p1#xywh=100,124,300,400"), then the destination x, y, width, and height (in "canvas units") is given. No other data must be consulted to determine the eventual x, y, width, and height of an image resource (including alternate/choice images).
oa:Choice
{
"@context":"http://iiif.io/api/presentation/2/context.json",
"@id":"http://www.example.org/iiif/book1/annotation/anno1",
"@type":"oa:Annotation",
"motivation":"sc:painting",
"resource":{
"@type":"oa:Choice",
"default":{
"@id":"http://www.example.org/iiif/book1/res/page1.jpg",
"@type":"dctypes:Image",
"label":"Color"
},
"item": [
{
"@id":"http://www.example.org/iiif/book1/res/page1-blackandwhite.jpg",
"@type":"dctypes:Image",
"label":"Black and White"
}
]
},
"on":"http://www.example.org/iiif/book1/canvas/p1"
}
oa:SpecificResource
{
"@context":"http://iiif.io/api/presentation/2/context.json",
"@id":"http://www.example.org/iiif/book1/annotation/anno1",
"@type":"oa:Annotation",
"motivation":"sc:painting",
"resource":{
"@id" : "http://www.example.org/iiif/book1-page1/40,50,1200,1800/full/0/default.jpg",
"@type":"oa:SpecificResource",
"full": {
"@id":"http://www.example.org/iiif/book1-page1/full/full/0/default.jpg",
"@type":"dctypes:Image",
"service": {
"@context": "http://iiif.io/api/image/2/context.json",
"@id": "http://www.example.org/iiif/book1-page1",
"profile":"http://iiif.io/api/image/2/level2.json"
}
},
"selector": {
"@context": "http://iiif.io/api/annex/openannotation/context.json",
"@type": "iiif:ImageApiSelector",
"region": "40,50,1200,1800"
}
},
"on":"http://www.example.org/iiif/book1/canvas/p1#xywh=50,50,320,240"
}
This is the easy part. No matter what, the x
, y
, width
, and height
of the resource are given by the on
property of the image
metadata. If there is no hash identifier, then x=0, y=0, width=canvasWidth, height=canvasHeight, and the aspect ratio is assumed to be the same as the canvas. If there is a hash identifier, such as "on":"http://www.example.org/iiif/book1/canvas/p1#xywh=50,50,320,240"
, then the position and size properties are given by the properties there, in canvas units. So, for instance, x=50, y=50, width=320, height=240, where these data are in "canvas units".
So if the canvas of which these items are a part is 1000X2000 (0.5 aspect ratio), then the x and y will be 5% and 2.5% of the total canvas width and height, while the width and height of the resource when it gets placed in the OSD coordinate space will be 32% and and 24% of the canvas width and height in the OSD space.
Also, if this hash identifier exists, we know this is a "detail" image, though there is no real distinction that will make itself into an interface (There is no privileged image that is considered a "main" image; a canvas may be depicted by 3 or 4 "detail" images, for instance).
A resource can have an @type
of 'oa:choice
, oa:SpecificResource
, and dctypes:Image
. We need to check for them in this order, since oa:SpecificResource
resources can appear inside of a choice as well as dctypes:Image
resources. And ultimately, the next lowest level inside an oa:SpecificResource
will also be a dctypes:Image
resource. It makes no difference whether the image
metadata implies this resource will supply a "detail" image, there may still be a choice of many alternate representations of this same detail image.
As described in 7.3, an alternate resource will change the structure of the resource metadata, so we will need to check for this different structure, and create a new resourceObject for each image choice found. Inside resource
, we should check whether it has a default
field. If so, then the contents of default
is the "default" resource for that group. Other resources are in the item
array, each of whose elements has normal resource
metadata that can undergo further processing like any resource. If the resource has no default
field, it will be either a "main/plain/detail" image.
If the image is in the item
field, it would be good to store the @id
of the default
somewhere on each of the alternates, so we can refer back to it. This might be useful for rendering a dropdown or tree with the alternate images grouped underneath their default. Many image choice resources may have several dozens possible alternative images, so we need to know which one is the default
, and only render that until others are requested.
The "resource" has all the information necessary to construct the resourceObject
. The resource may or may not be a "choice" resource, so before we can check for its static or dynamic status, we need to see if the "resource" property has a "default" field, (as above) and get the contents of that. (See 7.3). The following therefore only applies to dctypes:Image
resources.
If the resource is dynamic, it "SHOULD" have a "service" property defined. However, it is not enough to just check if the service is defined, because it may be embedded, as described in the annex. It also may not be an image service, but one of a variety of valid services. So we must also check both that the @context
is correct (http://iiif.io/api/image/2/context.json) to see if it's the right kind of service, and some required property, like width
(a required property of the info.json
), to see if it is embedded. If no width is defined, then dereference the @id
to retrieve the info.json
. If it is embedded, then the info.json is pre-defined, and may be passed in directly.
Since the image may not have a service defined even though one is available, there is some checking we can do on the url to determine if it is a iiif endpoint and then prune it to use it as the base url to retrieve the info.json
. However, this may not be advisable in all cases, so let's not bother with that here. The manifest publisher may not want the endpoint to be used in that manner if they haven't published it as such.
Given all of this, the logic should be:
@context
exists. If it doesn't, treat it as a static image. width
to see if it is embedded. If it is not, store the url. If it is embedded, store the embedded info.json.The clip region for a resource must be retrieved in different ways depending on the @type
and whether the resource is static or dynamic. Again, here, oa:Choice
resources must be broken into multiple resources and decomposed until the dctypes:Image
resource is retrieved.
If the resource is of type oa:SpecificResource
, then the clip mask will be given by the selector.region
field. You will also have to confirm that the @type
of the OA selector
is iiif:ImageApiSelector
. The service url will need to be retrieved from the full.service[@id]
.
If the resource is of type dcterms:Image
, there are two possibilities. Either there is no hash identifier, in which case the clip mask will be 0,0,canvasWidth, canvasHeight, or there will be a hash identifier. If there is a hash identifier, the image will be static, and the clip mask will be given by that hash identifier. In that case, the static image will need to be processed by osd to prevent the extraneous parts from being shown within the target region (on
field hash identifier).
In descriptive properties, the canvas might have a thumbnail, and "should" if there are multiple resources. If the canvas does not have a thumbnail, then construct one from the first image resource.
To do this, simply use the @id
of the first image resource (assuming there is one). Otherwise, use some kind of "blank canvas" placeholder, such as an arty, thin-lined svg "X". There may be instances where the given @id
image is not as appropriate as a larger or different resolution thumbnail. In this case, if the resource provides a service, and there is a compelling reason to use this service rather than the @id
we can construct a iiif url for the desired thumbnail size from the service. Otherwise, just use the @id
, which is required by the spec ("MUST").
Note that it is of course possible to construct a thumbnail from all the resources, rendering a somewhat accurate depiction of it including detail images, for instance, using a canvas element to generate a dataurl for the image, but that would be rather intensive.
@nein09 will need this for #97.