humangeo / leaflet-tilefilter

Change the appearance of Leaflet map tiles on the fly using a variety of canvas or CSS3 image filters. It's like Instagram for Leaflet map tiles.
MIT License
97 stars 19 forks source link

ctx.drawImage is not a function #7

Closed fnick851 closed 5 years ago

fnick851 commented 5 years ago
    var map = L.map('map').setView([51.505, -0.09], 15);

    var tilelayer = new L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
        filter: function (image, ctx) {
            new L.CanvasFilter({
                channelFilter: function (imageData) {
                    return new L.ChannelFilters.Sepia().render(imageData);
                }
            }).render(this, image, ctx);
        }
    });

    map.addLayer(tilelayer); 

The above code gives:

Uncaught TypeError: ctx.drawImage is not a function at i.render (leaflet-tilefilter.js:381) at i.filter (london_map.html:33) at i._tileOnLoad (leaflet-tilefilter.js:1160) at HTMLImageElement.s (leaflet.js:5)

in console.

So far I can only get the filter to work with CSS filter written in the simplified style:

var tilelayer = new L.tileLayer('https://wmts20.geo.admin.ch/1.0.0/ch.swisstopo.vec25-gebaeude/default/20090401/3857/{z}/{x}/{y}.png', { minZoom: 13, maxZoom: 15, cssFilter: L.ImageFilters.GenerateCSSFilter(['contrast(160%)', 'saturate(0%)', 'brightness(94%)']), detectRetina: true });

But I really need the Canvas filter.

I am using leaflet 1.5.1 and leaflet-tilefilter 1.0dev

sfairgrieve commented 5 years ago

I think the main cause of the error is that the filter function passes in element, image, ctx rather than image, ctx, so the ctx variable is actually the image in the above code. Also, ChannelFilter doesn't have a render method and gets called by a CanvasChannelFilter instance. So, in order for the above code to work, you would need to create a new CanvasChannelFilter instance passing in the channel filter you want to use. I agree that it's confusing, and there are probably ways to simplify it. In the meantime, I would recommend using this helper call, which lets you pass in an array of channel filters that you want to use:

L.ImageFilters.GenerateCanvasFilter([new L.ChannelFilters.Sepia()])

Since it simplifies some of the logic. You can rewrite your code to the following:

    var map = L.map('map').setView([51.505, -0.09], 15);

    var tilelayer = new L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
        filter: L.ImageFilters.GenerateCanvasFilter([new L.ChannelFilters.Sepia()])
    });

    map.addLayer(tilelayer);

Also, I would highly recommend the use of the L.TileLayer.CanvasTMS class over the regular TileLayer in this case, since it has improvements to help with using canvas filters in a semi-performant manner. I would advise that you use the canvas filters only when nothing else in the CSS filter world is available. Their performance is not amazing.

fnick851 commented 5 years ago

Thanks for walking me through this and the recommendations!