Ошибки прозрачности в gdal2tiles

В своей работе над r2weather.ru я нахожу множество багов. Иногда они случаются по собственной глупости, а иногда появляются в самых неожиданных местах. Причём, ошибки связаны не только с программированием и различными алгоритмами, но и со странным поведением внешних систем.

Из недавнего, я обнаружил на результирующей карте совершенно странные полупрозрачные полоски толщиной один-два пиксела.

Выглядит это, прямо скажем, не очень. Особенно на большой карте мира.

Исследование

Вообще, я специально конвертирую jpeg-файл, принимаемый со спутника в формат .png. Во-первых, это формат без потерь. То есть, каждый пиксел, который я вижу в результирующем jpeg, выглядит именно так, как он был передан со спутника. Это очень сильно улучшает изображение, так как нет двойного перекодирования в jpeg. Во-вторых, png поддерживает альфа-канал. Если часть изображения не была принята, то в альфа-канал можно поместить полностью прозрачные пикселы. Они будут нести дополнительную информацию и их можно будет красиво “не показать” на карте.

Если ещё раз глянуть на получившийся тайл, то видно, что полупрозраные пикселы находятся по краям изображения. Если бы я не принял часть пакетов со спутника, то у меня была бы полоса шириной 8 пикселов. А это значит, что эти пикселы добавляются где-то во время обработки.

Чтобы их найти, достаточно проверить изображение на каждом шаге обработки.

GeoTIFF получается с очень чёткими краями.

Ага! Попался. gdal2tiles создаёт тайлы с полупрозрачными пикселами по краям. И это очень странно:

  1. В официальной документации об этом нигде не сказано
  2. Полупрозрачность добавляется не во всех слоях! В примере выше тайл с 5го уровня, а для уровня 6 никакой прозрачности не добавляется.

Исправляется эта ошибка достаточно просто. Во-первых, при отрисовке нужно брать изображения чуть-чуть внахлёст. Тогда будет плавный переход между двумя изображениями. А во-вторых можно игнорировать все пикселы в которых выставлена полупрозрачность.

for (int i = 0; i < sourceImage.getWidth(); i++) {
	for (int j = 0; j < sourceImage.getHeight(); j++) {
		int sourceRgb = sourceImage.getRGB(i, j);
		// do not override dest with no data
		// do not copy any partially tranparent pixels
		// gdal2tiles.py create them on the edges for some reason
		if ((sourceRgb >>> 24) != 0xFF) {
			continue;
		}
		destImage.setRGB(i, j, sourceRgb);
	}
}

Результат

Такое небольшое изменение дало на удивление хорошие результаты.

На изображении выше наложены два пролёта подряд на Северной Америкой. Несмотря, на то, что границы каждого пролёта немного размыты, изображение склеено вполне неплохо.