Search code examples
javascriptimageopenlayersopenseadragonzoomify

How to display high resolution image in browser using openlayers


I am trying to display a high resolution image in browser using openlayers 5. I found an example on how to use zoomify to create image tiles and render it using openlayers map. But I am unable to use it for my own image. I am completely new to this. The question I ask may be very trivial. Please bear my ignorance.

Example code - This is the example from openlayers website. I am trying to do the same with this image. high resolution image I tried replacing the zoomifyUrl and iipUrl with my image url but it didn't work.

import Map from 'ol/Map.js';
import View from 'ol/View.js';
import TileLayer from 'ol/layer/Tile.js';
import Zoomify from 'ol/source/Zoomify.js';

var imgWidth = 799;
var imgHeight = 586;

var zoomifyUrl = 'https://live.staticflickr.com/8173/7993440342_5d9c68faec_c.jpg';
var iipUrl = 'https://live.staticflickr.com/8173/7993440342_5d9c68faec_c.jpg' + '&JTL={z},{tileIndex}';

var layer = new TileLayer({
  source: new Zoomify({
    url: zoomifyUrl,
    size: [imgWidth, imgHeight],
    crossOrigin: 'anonymous'
  })
});

var extent = [0, -imgHeight, imgWidth, 0];

var map = new Map({
  layers: [layer],
  target: 'map',
  view: new View({
    // adjust zoom levels to those provided by the source
    resolutions: layer.getSource().getTileGrid().getResolutions(),
    // constrain the center: center cannot be set outside this extent
    extent: extent
  })
});
map.getView().fit(extent);

var control = document.getElementById('zoomifyProtocol');
control.addEventListener('change', function(event) {
  var value = event.currentTarget.value;
  if (value === 'iip') {
    layer.setSource(new Zoomify({
      url: iipUrl,
      size: [imgWidth, imgHeight],
      crossOrigin: 'anonymous'
    }));
  } else if (value === 'zoomify') {
    layer.setSource(new Zoomify({
      url: zoomifyUrl,
      size: [imgWidth, imgHeight],
      crossOrigin: 'anonymous'
    }));
  }

});

I want to achieve something like the demo in openseadragon website. After making the above code change, I get a a grid with a portion of image repeated. final image


Solution

  • To use Zoomify you need a server which supports the image served as tiles. The url you are using is a static image on flickr which OpenLayers would need to process as ImageStatic. Here is the code using the highest resolution image from flickr

      var extent = [0, 0, 10000, 7328];
      var resolutions = [64, 32, 16, 8, 4, 2, 1];
    
      var layer = new ol.layer.Image({
        source: new ol.source.ImageStatic({
          url: 'https://live.staticflickr.com/8173/7993440342_6a1f281898_o_d.jpg',
          imageExtent: extent
        })
      });
    
      var map = new ol.Map({
        layers: [layer],
        target: 'map',
        view: new ol.View({
          // adjust zoom levels to those provided by the source
          resolutions: resolutions,
          // constrain the center: center cannot be set outside this extent
          extent: extent
        })
      });
      map.getView().fit(extent);
    html, body, .map {
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100%;
    }
    <link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
    <script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
    <div id="map" class="map"></div>

    Alternatively you could link to the url being used by OpenSeadragon. Sttrangely the zoom levels run from 8 to 14, with one tile at level 8 and a 55 x 41 grid at level 14 where the tiles in the rightmost column are 206px wide and in the bottom row 41px high. Zoomify can be used but a custom tile url function is needed to add 8 to the zoom level expected by OpenLayers.

      var imgWidth = 54*256 + 206;
      var imgHeight = 40*256 + 41;
    
      var zoomifyUrl = 'https://openseadragon.github.io/example-images/duomo/duomo_files/{z}/{x}_{y}.jpg';
    
      var layer = new ol.layer.Tile({
        source: new ol.source.Zoomify({
          url: zoomifyUrl,
          size: [imgWidth, imgHeight],
          crossOrigin: 'anonymous'
        })
      });
    
      layer.getSource().setTileUrlFunction(function(tileCoord) {
        return zoomifyUrl.replace(
          '{z}', tileCoord[0]+8
        ).replace(
          '{x}', tileCoord[1]
        ).replace(
          '{y}', -(tileCoord[2]+1)
        );
      });
    
      var extent = [0, -imgHeight, imgWidth, 0];
    
      var map = new ol.Map({
        layers: [layer],
        target: 'map',
        view: new ol.View({
          // adjust zoom levels to those provided by the source
          resolutions: layer.getSource().getTileGrid().getResolutions(),
          // constrain the center: center cannot be set outside this extent
          extent: extent
        })
      });
      map.getView().fit(extent);
    html, body, .map {
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100%;
    }
    <link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
    <script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
    <div id="map" class="map"></div>

    Looking at the result from that attempt it is apparent some tiles are only 255px instead of the standard 256 which is causing white lines to appear on the output. I added a custom tile load function to stretch 255px widths or heights to 256 (but it must not stretch tiles at the right and bottom edges which can be less than 255px).

      var imgWidth = 54*256 + 206;
      var imgHeight = 40*256 + 41;
    
      var zoomifyUrl = 'https://openseadragon.github.io/example-images/duomo/duomo_files/{z}/{x}_{y}.jpg';
    
      var layer = new ol.layer.Tile({
        source: new ol.source.Zoomify({
          url: zoomifyUrl,
          size: [imgWidth, imgHeight],
          crossOrigin: 'anonymous'
        })
      });
    
      layer.getSource().setTileUrlFunction(function(tileCoord) {
        return zoomifyUrl.replace(
          '{z}', tileCoord[0]+8
        ).replace(
          '{x}', tileCoord[1]
        ).replace(
          '{y}', -(tileCoord[2]+1)
        );
      });
    
      var tileSize = ol.size.toSize(layer.getSource().getTileGrid().getTileSize(0));
    
      layer.getSource().setTileLoadFunction(function(imageTile, src) {
        var img = document.createElement('img');
        img.onload = function() {
          var width = img.naturalWidth >= tileSize[0]-1 ? tileSize[0] : img.naturalWidth;
          var height = img.naturalHeight >= tileSize[1]-1 ? tileSize[1] : img.naturalHeight;
          var canvas = document.createElement('canvas');
          canvas.width = width;
          canvas.height = height;
          var ctx = canvas.getContext('2d');
          ctx.drawImage(img, 0, 0, width, height);
          imageTile.getImage().src = canvas.toDataURL();
        };
        img.crossOrigin = 'anonymous';
        img.src = src;
      });
    
      var extent = [0, -imgHeight, imgWidth, 0];
    
      var map = new ol.Map({
        layers: [layer],
        target: 'map',
        view: new ol.View({
          // adjust zoom levels to those provided by the source
          resolutions: layer.getSource().getTileGrid().getResolutions(),
          // constrain the center: center cannot be set outside this extent
          extent: extent
        })
      });
      map.getView().fit(extent);
    html, body, .map {
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100%;
    }
    <link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
    <script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
    <div id="map" class="map"></div>

    Using a dzi XML parser:

    var map = new ol.Map({
        target: 'map',
        logo: false
    });
    
    var layer = dzi.loadUrl(
        'https://openseadragon.github.io/example-images/duomo/duomo.dzi',
        { attributions: 'Image &copy 2012, <a href="https://www.flickr.com/photos/projectese/" target="_blank">Dario Morelli</a>' }
    );
    
    layer.on('change:source', function(evt) {
        map.setView(
          new ol.View({
            resolutions: layer.getSource().getTileGrid().getResolutions(),
            extent: layer.getExtent()
          })
        );
        map.getView().fit(layer.getExtent(), { size: map.getSize() });
    });
    
    map.addLayer(layer);
    html, body, .map {
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100%;
    }
    <link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
    <script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
    <script>
    
    dzi = function() {
    
    function loadUrl ( url,
                       opt_options  // attributions (defaults to undefined), crossOrigin (defaults to 'anonymous')
    ) {
    
        var options = opt_options || {};
        var crossOrigin = options.crossOrigin === undefined ? 'anonymous' : options.crossOrigin;
    
        var layer = new ol.layer.Tile();
    
        var last = url.lastIndexOf('.');
        var path = url.slice(0, last);
    
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.onload = function() {
    
            var parser = new DOMParser();
            var xmlDoc = parser.parseFromString(xhr.responseText,'text/xml');
    
            var elements = xmlDoc.getElementsByTagName('Image');
            var tileSize = Number(elements[0].getAttribute('TileSize'));
            var format = elements[0].getAttribute('Format');
            var width = Number(elements[0].getElementsByTagName('Size')[0].getAttribute('Width'));
            var height = Number(elements[0].getElementsByTagName('Size')[0].getAttribute('Height'));
            var url = path + '_files/{z}/{x}_{y}.' + format;
    
            var source = new ol.source.Zoomify({
                attributions: options.attributions,
                url: url,
                size: [width, height],
                tileSize: tileSize,
                crossOrigin: crossOrigin
            });
    
            var offset = Math.ceil(Math.log(tileSize)/Math.LN2);
    
            source.setTileUrlFunction(function(tileCoord) {
                return url.replace(
                    '{z}', tileCoord[0] + offset
                ).replace(
                    '{x}', tileCoord[1]
                ).replace(
                    '{y}', -(tileCoord[2]+1)
                );
            });
    
            layer.setExtent([0, -height, width, 0]);
            layer.setSource(source);
    
        }
        xhr.send();
        return layer;
    
    }
    
    return {
       "loadUrl" : loadUrl
    }
    
    } ();
    
    </script>
    <div id="map" class="map"></div>