Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting rid of thin border at edge with transparency when antialiasing with Python Image Library

I have a high-resolution image that I want to use as a tiled map overlay using Google Maps API v3.

I used MapTiler to break it into appropriate tiles at the desired zoom levels. That worked well except that there was a thin grey-black border on the tiles that were the edges of the original image.

Following the suggestion of the second post at http://groups.google.com/group/maptiler/browse_thread/thread/70a4c5610538332a/42fefedb4a0bc6d2, I tried using gdal2tiles.py instead, passing it the -r antialias option but the thin border persisted.

If I open the actual generated image tiles, the thin border does indeed appear to be part of the generated tile, but it is not part of the original image.

I suspect what is happening is that the portions of the Google Maps tile for which I have no data are being treated as black pixels when the program generates the tile image files and the result is the grey border.

Here's what I believe to be the relevant code from gdal2tiles.py:

                    # Scaling by PIL (Python Imaging Library) - improved Lanczos
                    array = numpy.zeros((querysize, querysize, tilebands), numpy.uint8)
                    for i in range(tilebands):
                            array[:,:,i] = gdalarray.BandReadAsArray(dsquery.GetRasterBand(i+1), 0, 0, querysize, querysize)
                    im = Image.fromarray(array, 'RGBA') # Always four bands
                    im1 = im.resize((tilesize,tilesize), Image.ANTIALIAS)
                    if os.path.exists(tilefilename):
                            im0 = Image.open(tilefilename)
                            im1 = Image.composite(im1, im0, im1) 
                    im1.save(tilefilename,self.tiledriver)

Any idea how to make it so that border isn't there, short of opening the relevant generated tile image files in an image editor and setting the relevant pixels to transparent?

I suspect the answer involves finding some way to represent transparent pixels such that antialiasing ignores them for sampling purposes.

Update: It might not win any awards for elegance or performance, but I'm most of the way there, I think. Of course, the final few yards are also the toughest.

If I change ANTIALIAS to BICUBIC and then process the alpha channel such that any semi-transparent pixels are rendered completely transparent, I get rid of most of the borders on most of the tiles. Some light-colored borderlines persist, however. I'm not sure what to do about that. It's also worth noting that I guess this strategy might not work so well if there were transparent or semi-transparent pixels in the image that were not outside the edges of the actual image area.

Here's the code with these modifications:

                # Scaling by PIL (Python Imaging Library) - improved Lanczos
                array = numpy.zeros((querysize, querysize, tilebands), numpy.uint8)
                for i in range(tilebands):
                        array[:,:,i] = gdalarray.BandReadAsArray(dsquery.GetRasterBand(i+1), 0, 0, querysize, querysize)
                im = Image.fromarray(array, 'RGBA') # Always four bands
                im1 = im.resize((tilesize,tilesize), Image.BICUBIC)
                if os.path.exists(tilefilename):
                        im0 = Image.open(tilefilename)
                        im1 = Image.composite(im1, im0, im1)
                im1AsArray = numpy.array(im1)
                alpha = im1AsArray[:,:,3]
                semiTransparentIndices = alpha < 255
                alpha[semiTransparentIndices] = 0
                im1AsArray[:,:,3] = alpha
                im1 = Image.fromarray(im1AsArray, 'RGBA')
                im1.save(tilefilename,self.tiledriver)
like image 447
Trott Avatar asked May 13 '11 01:05

Trott


1 Answers

The answer is to change the resampling to BILINEAR (and not BICUBIC, which is what I tried in the Update posted to the question), and then make sure to change any semitransparent pixels to completely transparent pixels.

As I said in the Update, the code modifications I made here might not win any awards for elegance or performance, but it works. Here's what the original posted snippet from gdal2tiles.py needs to be changed to:

            # Scaling by PIL (Python Imaging Library) - improved Lanczos
            array = numpy.zeros((querysize, querysize, tilebands), numpy.uint8)
            for i in range(tilebands):
                    array[:,:,i] = gdalarray.BandReadAsArray(dsquery.GetRasterBand(i+1), 0, 0, querysize, querysize)
            im = Image.fromarray(array, 'RGBA') # Always four bands
            im1 = im.resize((tilesize,tilesize), Image.BILINEAR)
            if os.path.exists(tilefilename):
                    im0 = Image.open(tilefilename)
                    im1 = Image.composite(im1, im0, im1)
            im1AsArray = numpy.array(im1)
            alpha = im1AsArray[:,:,3]
            semiTransparentIndices = alpha < 255
            alpha[semiTransparentIndices] = 0
            im1AsArray[:,:,3] = alpha
            im1 = Image.fromarray(im1AsArray, 'RGBA')
            im1.save(tilefilename,self.tiledriver)

Note also that the above code will only execute if you pass gdal2tiles.py the -r antialias flag. Yes, that's right: We changed the -r antialias code so that it doesn't antialias. But if you're having the problem I was having and just want a solution, there it is.

like image 91
Trott Avatar answered Oct 11 '22 21:10

Trott