MapProxy 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- MapProxy-2.1.0.dist-info/AUTHORS.txt +33 -0
- MapProxy-2.1.0.dist-info/COPYING.txt +60 -0
- MapProxy-2.1.0.dist-info/LICENSE.txt +202 -0
- MapProxy-2.1.0.dist-info/METADATA +165 -0
- MapProxy-2.1.0.dist-info/RECORD +459 -0
- MapProxy-2.1.0.dist-info/WHEEL +5 -0
- MapProxy-2.1.0.dist-info/entry_points.txt +4 -0
- MapProxy-2.1.0.dist-info/top_level.txt +1 -0
- mapproxy/__init__.py +0 -0
- mapproxy/cache/__init__.py +36 -0
- mapproxy/cache/azureblob.py +145 -0
- mapproxy/cache/base.py +120 -0
- mapproxy/cache/compact.py +665 -0
- mapproxy/cache/couchdb.py +301 -0
- mapproxy/cache/dummy.py +36 -0
- mapproxy/cache/file.py +200 -0
- mapproxy/cache/geopackage.py +647 -0
- mapproxy/cache/legend.py +87 -0
- mapproxy/cache/mbtiles.py +411 -0
- mapproxy/cache/meta.py +80 -0
- mapproxy/cache/path.py +261 -0
- mapproxy/cache/redis.py +152 -0
- mapproxy/cache/renderd.py +100 -0
- mapproxy/cache/riak.py +206 -0
- mapproxy/cache/s3.py +209 -0
- mapproxy/cache/tile.py +736 -0
- mapproxy/client/__init__.py +0 -0
- mapproxy/client/arcgis.py +82 -0
- mapproxy/client/cgi.py +141 -0
- mapproxy/client/http.py +295 -0
- mapproxy/client/log.py +33 -0
- mapproxy/client/tile.py +158 -0
- mapproxy/client/wms.py +255 -0
- mapproxy/compat/__init__.py +0 -0
- mapproxy/compat/image.py +86 -0
- mapproxy/config/__init__.py +22 -0
- mapproxy/config/config-schema.json +813 -0
- mapproxy/config/config.py +213 -0
- mapproxy/config/coverage.py +108 -0
- mapproxy/config/defaults.py +102 -0
- mapproxy/config/loader.py +2399 -0
- mapproxy/config/spec.py +657 -0
- mapproxy/config/validator.py +242 -0
- mapproxy/config_template/__init__.py +0 -0
- mapproxy/config_template/base_config/config.wsgi +10 -0
- mapproxy/config_template/base_config/full_example.yaml +598 -0
- mapproxy/config_template/base_config/full_seed_example.yaml +79 -0
- mapproxy/config_template/base_config/log.ini +35 -0
- mapproxy/config_template/base_config/mapproxy.yaml +60 -0
- mapproxy/config_template/base_config/seed.yaml +27 -0
- mapproxy/exception.py +149 -0
- mapproxy/featureinfo.py +251 -0
- mapproxy/grid.py +1199 -0
- mapproxy/image/__init__.py +549 -0
- mapproxy/image/fonts/DejaVuSans.ttf +0 -0
- mapproxy/image/fonts/DejaVuSansMono.ttf +0 -0
- mapproxy/image/fonts/LICENSE +99 -0
- mapproxy/image/fonts/__init__.py +0 -0
- mapproxy/image/mask.py +79 -0
- mapproxy/image/merge.py +323 -0
- mapproxy/image/message.py +357 -0
- mapproxy/image/opts.py +185 -0
- mapproxy/image/tile.py +171 -0
- mapproxy/image/transform.py +350 -0
- mapproxy/layer.py +489 -0
- mapproxy/multiapp.py +230 -0
- mapproxy/proj.py +309 -0
- mapproxy/request/__init__.py +18 -0
- mapproxy/request/arcgis.py +268 -0
- mapproxy/request/base.py +466 -0
- mapproxy/request/tile.py +131 -0
- mapproxy/request/wms/__init__.py +829 -0
- mapproxy/request/wms/exception.py +107 -0
- mapproxy/request/wmts.py +442 -0
- mapproxy/response.py +237 -0
- mapproxy/script/__init__.py +0 -0
- mapproxy/script/conf/__init__.py +0 -0
- mapproxy/script/conf/app.py +222 -0
- mapproxy/script/conf/caches.py +44 -0
- mapproxy/script/conf/geopackage.py +136 -0
- mapproxy/script/conf/layers.py +54 -0
- mapproxy/script/conf/seeds.py +36 -0
- mapproxy/script/conf/sources.py +88 -0
- mapproxy/script/conf/utils.py +148 -0
- mapproxy/script/defrag.py +187 -0
- mapproxy/script/export.py +337 -0
- mapproxy/script/grids.py +198 -0
- mapproxy/script/scales.py +134 -0
- mapproxy/script/util.py +410 -0
- mapproxy/script/wms_capabilities.py +160 -0
- mapproxy/seed/__init__.py +0 -0
- mapproxy/seed/cachelock.py +127 -0
- mapproxy/seed/cleanup.py +191 -0
- mapproxy/seed/config.py +481 -0
- mapproxy/seed/script.py +391 -0
- mapproxy/seed/seeder.py +551 -0
- mapproxy/seed/spec.py +66 -0
- mapproxy/seed/util.py +266 -0
- mapproxy/service/__init__.py +14 -0
- mapproxy/service/base.py +45 -0
- mapproxy/service/demo.py +364 -0
- mapproxy/service/kml.py +333 -0
- mapproxy/service/ows.py +39 -0
- mapproxy/service/template_helper.py +55 -0
- mapproxy/service/templates/demo/capabilities_demo.html +18 -0
- mapproxy/service/templates/demo/demo.html +181 -0
- mapproxy/service/templates/demo/openlayers-demo.cfg +16 -0
- mapproxy/service/templates/demo/static/img/blank.gif +0 -0
- mapproxy/service/templates/demo/static/img/east-mini.png +0 -0
- mapproxy/service/templates/demo/static/img/north-mini.png +0 -0
- mapproxy/service/templates/demo/static/img/south-mini.png +0 -0
- mapproxy/service/templates/demo/static/img/west-mini.png +0 -0
- mapproxy/service/templates/demo/static/img/zoom-minus-mini.png +0 -0
- mapproxy/service/templates/demo/static/img/zoom-plus-mini.png +0 -0
- mapproxy/service/templates/demo/static/img/zoom-world-mini.png +0 -0
- mapproxy/service/templates/demo/static/logo.png +0 -0
- mapproxy/service/templates/demo/static/ol.css +345 -0
- mapproxy/service/templates/demo/static/ol.js +4 -0
- mapproxy/service/templates/demo/static/proj4.min.js +1 -0
- mapproxy/service/templates/demo/static/proj4defs.js +1 -0
- mapproxy/service/templates/demo/static/site.css +137 -0
- mapproxy/service/templates/demo/static/theme/default/framedCloud.css +0 -0
- mapproxy/service/templates/demo/static/theme/default/google.css +17 -0
- mapproxy/service/templates/demo/static/theme/default/ie6-style.css +10 -0
- mapproxy/service/templates/demo/static/theme/default/style.css +482 -0
- mapproxy/service/templates/demo/static.html +34 -0
- mapproxy/service/templates/demo/tms_demo.html +117 -0
- mapproxy/service/templates/demo/wms_demo.html +144 -0
- mapproxy/service/templates/demo/wmts_demo.html +118 -0
- mapproxy/service/templates/tms_capabilities.xml +13 -0
- mapproxy/service/templates/tms_exception.xml +4 -0
- mapproxy/service/templates/tms_root_resource.xml +7 -0
- mapproxy/service/templates/tms_tilemap_capabilities.xml +14 -0
- mapproxy/service/templates/wms100capabilities.xml +112 -0
- mapproxy/service/templates/wms100exception.xml +4 -0
- mapproxy/service/templates/wms110capabilities.xml +152 -0
- mapproxy/service/templates/wms110exception.xml +5 -0
- mapproxy/service/templates/wms111capabilities.xml +183 -0
- mapproxy/service/templates/wms111exception.xml +5 -0
- mapproxy/service/templates/wms130capabilities.xml +326 -0
- mapproxy/service/templates/wms130exception.xml +8 -0
- mapproxy/service/templates/wmts100capabilities.xml +155 -0
- mapproxy/service/templates/wmts100exception.xml +9 -0
- mapproxy/service/tile.py +540 -0
- mapproxy/service/wms.py +868 -0
- mapproxy/service/wmts.py +387 -0
- mapproxy/source/__init__.py +83 -0
- mapproxy/source/arcgis.py +39 -0
- mapproxy/source/error.py +40 -0
- mapproxy/source/mapnik.py +262 -0
- mapproxy/source/tile.py +97 -0
- mapproxy/source/wms.py +273 -0
- mapproxy/srs.py +734 -0
- mapproxy/template.py +54 -0
- mapproxy/test/__init__.py +0 -0
- mapproxy/test/conftest.py +8 -0
- mapproxy/test/helper.py +255 -0
- mapproxy/test/http.py +511 -0
- mapproxy/test/image.py +219 -0
- mapproxy/test/mocker.py +2291 -0
- mapproxy/test/schemas/inspire/common/1.0/common.xsd +1461 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_bul.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_cze.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_dan.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_dut.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_eng.xsd +155 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_est.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_fin.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_fre.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_ger.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_gle.xsd +109 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_gre.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_hun.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_ita.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_lav.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_lit.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_mlt.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_pol.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_por.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_rum.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_slo.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_slv.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_spa.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_swe.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/network.xsd +521 -0
- mapproxy/test/schemas/inspire/inspire_vs/1.0/inspire_vs.xsd +19 -0
- mapproxy/test/schemas/kml/2.2.0/ReadMe.txt +14 -0
- mapproxy/test/schemas/kml/2.2.0/atom-author-link.xsd +66 -0
- mapproxy/test/schemas/kml/2.2.0/ogckml22.xsd +1646 -0
- mapproxy/test/schemas/kml/2.2.0/xAL.xsd +1680 -0
- mapproxy/test/schemas/ows/1.1.0/ReadMe.txt +87 -0
- mapproxy/test/schemas/ows/1.1.0/ows19115subset.xsd +235 -0
- mapproxy/test/schemas/ows/1.1.0/owsAll.xsd +23 -0
- mapproxy/test/schemas/ows/1.1.0/owsCommon.xsd +157 -0
- mapproxy/test/schemas/ows/1.1.0/owsContents.xsd +86 -0
- mapproxy/test/schemas/ows/1.1.0/owsDataIdentification.xsd +127 -0
- mapproxy/test/schemas/ows/1.1.0/owsDomainType.xsd +279 -0
- mapproxy/test/schemas/ows/1.1.0/owsExceptionReport.xsd +76 -0
- mapproxy/test/schemas/ows/1.1.0/owsGetCapabilities.xsd +112 -0
- mapproxy/test/schemas/ows/1.1.0/owsGetResourceByID.xsd +51 -0
- mapproxy/test/schemas/ows/1.1.0/owsInputOutputData.xsd +59 -0
- mapproxy/test/schemas/ows/1.1.0/owsManifest.xsd +125 -0
- mapproxy/test/schemas/ows/1.1.0/owsOperationsMetadata.xsd +140 -0
- mapproxy/test/schemas/ows/1.1.0/owsServiceIdentification.xsd +60 -0
- mapproxy/test/schemas/ows/1.1.0/owsServiceProvider.xsd +47 -0
- mapproxy/test/schemas/sld/1.1.0/sld_capabilities.xsd +27 -0
- mapproxy/test/schemas/wms/1.0.0/capabilities_1_0_0.dtd +353 -0
- mapproxy/test/schemas/wms/1.0.0/capabilities_1_0_0.xml +188 -0
- mapproxy/test/schemas/wms/1.0.7/capabilities_1_0_7.dtd +524 -0
- mapproxy/test/schemas/wms/1.0.7/capabilities_1_0_7.xml +260 -0
- mapproxy/test/schemas/wms/1.1.0/capabilities_1_1_0.dtd +273 -0
- mapproxy/test/schemas/wms/1.1.0/capabilities_1_1_0.xml +303 -0
- mapproxy/test/schemas/wms/1.1.0/exception_1_1_0.dtd +6 -0
- mapproxy/test/schemas/wms/1.1.0/exception_1_1_0.xml +33 -0
- mapproxy/test/schemas/wms/1.1.1/OGC-exception.xsd +68 -0
- mapproxy/test/schemas/wms/1.1.1/WMS_DescribeLayerResponse.dtd +22 -0
- mapproxy/test/schemas/wms/1.1.1/WMS_MS_Capabilities.dtd +274 -0
- mapproxy/test/schemas/wms/1.1.1/WMS_exception_1_1_1.dtd +5 -0
- mapproxy/test/schemas/wms/1.1.1/capabilities_1_1_1.dtd +276 -0
- mapproxy/test/schemas/wms/1.1.1/capabilities_1_1_1.xml +303 -0
- mapproxy/test/schemas/wms/1.1.1/exception_1_1_1.dtd +6 -0
- mapproxy/test/schemas/wms/1.1.1/exception_1_1_1.xml +33 -0
- mapproxy/test/schemas/wms/1.3.0/ReadMe.txt +8 -0
- mapproxy/test/schemas/wms/1.3.0/capabilities_1_3_0.xml +277 -0
- mapproxy/test/schemas/wms/1.3.0/capabilities_1_3_0.xsd +611 -0
- mapproxy/test/schemas/wms/1.3.0/exceptions_1_3_0.xml +34 -0
- mapproxy/test/schemas/wms/1.3.0/exceptions_1_3_0.xsd +28 -0
- mapproxy/test/schemas/wmsc/1.1.1/OGC-exception.xsd +68 -0
- mapproxy/test/schemas/wmsc/1.1.1/WMS_DescribeLayerResponse.dtd +22 -0
- mapproxy/test/schemas/wmsc/1.1.1/WMS_MS_Capabilities.dtd +283 -0
- mapproxy/test/schemas/wmsc/1.1.1/WMS_exception_1_1_1.dtd +5 -0
- mapproxy/test/schemas/wmsc/1.1.1/capabilities_1_1_1.dtd +276 -0
- mapproxy/test/schemas/wmsc/1.1.1/capabilities_1_1_1.xml +303 -0
- mapproxy/test/schemas/wmsc/1.1.1/exception_1_1_1.dtd +6 -0
- mapproxy/test/schemas/wmsc/1.1.1/exception_1_1_1.xml +33 -0
- mapproxy/test/schemas/wmts/1.0/ReadMe.txt +32 -0
- mapproxy/test/schemas/wmts/1.0/wmts.xsd +28 -0
- mapproxy/test/schemas/wmts/1.0/wmtsAbstract.wsdl +151 -0
- mapproxy/test/schemas/wmts/1.0/wmtsGetCapabilities_request.xsd +38 -0
- mapproxy/test/schemas/wmts/1.0/wmtsGetCapabilities_response.xsd +564 -0
- mapproxy/test/schemas/wmts/1.0/wmtsGetFeatureInfo_request.xsd +57 -0
- mapproxy/test/schemas/wmts/1.0/wmtsGetFeatureInfo_response.xsd +72 -0
- mapproxy/test/schemas/wmts/1.0/wmtsGetTile_request.xsd +91 -0
- mapproxy/test/schemas/wmts/1.0/wmtsKVP.xsd +76 -0
- mapproxy/test/schemas/wmts/1.0/wmtsPayload_response.xsd +70 -0
- mapproxy/test/schemas/xlink/1.0.0/ReadMe.txt +6 -0
- mapproxy/test/schemas/xlink/1.0.0/xlinks.xsd +122 -0
- mapproxy/test/schemas/xml.xsd +287 -0
- mapproxy/test/system/__init__.py +98 -0
- mapproxy/test/system/fixture/arcgis.yaml +57 -0
- mapproxy/test/system/fixture/auth.yaml +70 -0
- mapproxy/test/system/fixture/cache.mbtiles +0 -0
- mapproxy/test/system/fixture/cache_azureblob.yaml +59 -0
- mapproxy/test/system/fixture/cache_band_merge.yaml +73 -0
- mapproxy/test/system/fixture/cache_bulk_meta_tiles.yaml +24 -0
- mapproxy/test/system/fixture/cache_coverage.yaml +84 -0
- mapproxy/test/system/fixture/cache_data/dop_cache_EPSG3857/00/000/000/000/000/000/000.png +0 -0
- mapproxy/test/system/fixture/cache_data/wms_cache_EPSG900913/01/000/000/000/000/000/001.jpeg +0 -0
- mapproxy/test/system/fixture/cache_data/wms_cache_transparent_EPSG900913/01/000/000/000/000/000/001.png +0 -0
- mapproxy/test/system/fixture/cache_geopackage.yaml +56 -0
- mapproxy/test/system/fixture/cache_grid_names.yaml +50 -0
- mapproxy/test/system/fixture/cache_mbtiles.yaml +28 -0
- mapproxy/test/system/fixture/cache_s3.yaml +58 -0
- mapproxy/test/system/fixture/cache_source.yaml +81 -0
- mapproxy/test/system/fixture/combined_sources.yaml +130 -0
- mapproxy/test/system/fixture/coverage.yaml +77 -0
- mapproxy/test/system/fixture/demo.yaml +135 -0
- mapproxy/test/system/fixture/dimension.yaml +59 -0
- mapproxy/test/system/fixture/disable_storage.yaml +25 -0
- mapproxy/test/system/fixture/empty_ogrdata.geojson +1 -0
- mapproxy/test/system/fixture/formats.yaml +72 -0
- mapproxy/test/system/fixture/inspire.yaml +101 -0
- mapproxy/test/system/fixture/inspire_full.yaml +124 -0
- mapproxy/test/system/fixture/kml_layer.yaml +66 -0
- mapproxy/test/system/fixture/layer.yaml +260 -0
- mapproxy/test/system/fixture/layergroups.yaml +57 -0
- mapproxy/test/system/fixture/layergroups_root.yaml +106 -0
- mapproxy/test/system/fixture/legendgraphic.yaml +93 -0
- mapproxy/test/system/fixture/mapnik_source.yaml +66 -0
- mapproxy/test/system/fixture/mapproxy_export.yaml +12 -0
- mapproxy/test/system/fixture/mapserver.yaml +23 -0
- mapproxy/test/system/fixture/minimal_cgi.py +16 -0
- mapproxy/test/system/fixture/mixed_mode.yaml +49 -0
- mapproxy/test/system/fixture/multi_cache_layers.yaml +111 -0
- mapproxy/test/system/fixture/multiapp1.yaml +20 -0
- mapproxy/test/system/fixture/multiapp2.yaml +19 -0
- mapproxy/test/system/fixture/renderd_client.yaml +55 -0
- mapproxy/test/system/fixture/scalehints.yaml +70 -0
- mapproxy/test/system/fixture/seed.yaml +94 -0
- mapproxy/test/system/fixture/seed_mapproxy.yaml +39 -0
- mapproxy/test/system/fixture/seed_old.yaml +12 -0
- mapproxy/test/system/fixture/seed_timeouts.yaml +12 -0
- mapproxy/test/system/fixture/seed_timeouts_mapproxy.yaml +27 -0
- mapproxy/test/system/fixture/seedonly.yaml +51 -0
- mapproxy/test/system/fixture/sld.yaml +35 -0
- mapproxy/test/system/fixture/source_errors.yaml +84 -0
- mapproxy/test/system/fixture/source_errors_raise.yaml +82 -0
- mapproxy/test/system/fixture/tileservice_origin.yaml +26 -0
- mapproxy/test/system/fixture/tileservice_refresh.yaml +59 -0
- mapproxy/test/system/fixture/tilesource_minmax_res.yaml +22 -0
- mapproxy/test/system/fixture/util-conf-base-grids.yaml +5 -0
- mapproxy/test/system/fixture/util-conf-overwrite.yaml +13 -0
- mapproxy/test/system/fixture/util-conf-wms-111-cap.xml +90 -0
- mapproxy/test/system/fixture/util_grids.yaml +29 -0
- mapproxy/test/system/fixture/util_wms_capabilities111.xml +130 -0
- mapproxy/test/system/fixture/util_wms_capabilities130.xml +100 -0
- mapproxy/test/system/fixture/util_wms_capabilities_service_exception.xml +5 -0
- mapproxy/test/system/fixture/watermark.yaml +50 -0
- mapproxy/test/system/fixture/wms_srs_extent.yaml +39 -0
- mapproxy/test/system/fixture/wms_versions.yaml +38 -0
- mapproxy/test/system/fixture/wmts.yaml +134 -0
- mapproxy/test/system/fixture/wmts_dimensions.yaml +57 -0
- mapproxy/test/system/fixture/xslt_featureinfo.yaml +54 -0
- mapproxy/test/system/fixture/xslt_featureinfo_input.yaml +51 -0
- mapproxy/test/system/test_arcgis.py +156 -0
- mapproxy/test/system/test_auth.py +1133 -0
- mapproxy/test/system/test_behind_proxy.py +75 -0
- mapproxy/test/system/test_bulk_meta_tiles.py +106 -0
- mapproxy/test/system/test_cache_azureblob.py +127 -0
- mapproxy/test/system/test_cache_band_merge.py +103 -0
- mapproxy/test/system/test_cache_coverage.py +168 -0
- mapproxy/test/system/test_cache_geopackage.py +144 -0
- mapproxy/test/system/test_cache_grid_names.py +89 -0
- mapproxy/test/system/test_cache_mbtiles.py +85 -0
- mapproxy/test/system/test_cache_s3.py +115 -0
- mapproxy/test/system/test_cache_source.py +146 -0
- mapproxy/test/system/test_combined_sources.py +335 -0
- mapproxy/test/system/test_coverage.py +140 -0
- mapproxy/test/system/test_decorate_img.py +214 -0
- mapproxy/test/system/test_demo.py +56 -0
- mapproxy/test/system/test_demo_with_extra_service.py +57 -0
- mapproxy/test/system/test_dimensions.py +279 -0
- mapproxy/test/system/test_disable_storage.py +42 -0
- mapproxy/test/system/test_formats.py +219 -0
- mapproxy/test/system/test_inspire_vs.py +173 -0
- mapproxy/test/system/test_kml.py +264 -0
- mapproxy/test/system/test_layergroups.py +160 -0
- mapproxy/test/system/test_legendgraphic.py +308 -0
- mapproxy/test/system/test_mapnik.py +162 -0
- mapproxy/test/system/test_mapserver.py +83 -0
- mapproxy/test/system/test_mixed_mode_format.py +201 -0
- mapproxy/test/system/test_multi_cache_layers.py +169 -0
- mapproxy/test/system/test_multiapp.py +92 -0
- mapproxy/test/system/test_refresh.py +206 -0
- mapproxy/test/system/test_renderd_client.py +304 -0
- mapproxy/test/system/test_response_headers.py +54 -0
- mapproxy/test/system/test_scalehints.py +140 -0
- mapproxy/test/system/test_seed.py +425 -0
- mapproxy/test/system/test_seed_only.py +93 -0
- mapproxy/test/system/test_sld.py +120 -0
- mapproxy/test/system/test_source_errors.py +377 -0
- mapproxy/test/system/test_tilesource_minmax_res.py +54 -0
- mapproxy/test/system/test_tms.py +277 -0
- mapproxy/test/system/test_tms_origin.py +46 -0
- mapproxy/test/system/test_util_conf.py +434 -0
- mapproxy/test/system/test_util_export.py +210 -0
- mapproxy/test/system/test_util_grids.py +88 -0
- mapproxy/test/system/test_util_wms_capabilities.py +182 -0
- mapproxy/test/system/test_watermark.py +91 -0
- mapproxy/test/system/test_wms.py +1616 -0
- mapproxy/test/system/test_wms_srs_extent.py +165 -0
- mapproxy/test/system/test_wms_version.py +85 -0
- mapproxy/test/system/test_wmsc.py +116 -0
- mapproxy/test/system/test_wmts.py +334 -0
- mapproxy/test/system/test_wmts_dimensions.py +206 -0
- mapproxy/test/system/test_wmts_restful.py +198 -0
- mapproxy/test/system/test_xslt_featureinfo.py +423 -0
- mapproxy/test/test_http_helper.py +217 -0
- mapproxy/test/unit/__init__.py +0 -0
- mapproxy/test/unit/epsg +2 -0
- mapproxy/test/unit/polygons/polygons.dbf +0 -0
- mapproxy/test/unit/polygons/polygons.shp +0 -0
- mapproxy/test/unit/polygons/polygons.shx +0 -0
- mapproxy/test/unit/test_async.py +242 -0
- mapproxy/test/unit/test_auth.py +430 -0
- mapproxy/test/unit/test_cache.py +1356 -0
- mapproxy/test/unit/test_cache_azureblob.py +97 -0
- mapproxy/test/unit/test_cache_compact.py +324 -0
- mapproxy/test/unit/test_cache_couchdb.py +118 -0
- mapproxy/test/unit/test_cache_geopackage.py +256 -0
- mapproxy/test/unit/test_cache_redis.py +123 -0
- mapproxy/test/unit/test_cache_riak.py +80 -0
- mapproxy/test/unit/test_cache_s3.py +93 -0
- mapproxy/test/unit/test_cache_tile.py +477 -0
- mapproxy/test/unit/test_client.py +488 -0
- mapproxy/test/unit/test_client_arcgis.py +74 -0
- mapproxy/test/unit/test_client_cgi.py +140 -0
- mapproxy/test/unit/test_collections.py +116 -0
- mapproxy/test/unit/test_concat_legends.py +37 -0
- mapproxy/test/unit/test_conf_loader.py +1267 -0
- mapproxy/test/unit/test_conf_validator.py +427 -0
- mapproxy/test/unit/test_config.py +118 -0
- mapproxy/test/unit/test_decorate_img.py +185 -0
- mapproxy/test/unit/test_exceptions.py +270 -0
- mapproxy/test/unit/test_featureinfo.py +313 -0
- mapproxy/test/unit/test_file_lock_load.py +49 -0
- mapproxy/test/unit/test_geom.py +512 -0
- mapproxy/test/unit/test_grid.py +1279 -0
- mapproxy/test/unit/test_image.py +1051 -0
- mapproxy/test/unit/test_image_mask.py +181 -0
- mapproxy/test/unit/test_image_messages.py +209 -0
- mapproxy/test/unit/test_image_options.py +160 -0
- mapproxy/test/unit/test_isodate.py +118 -0
- mapproxy/test/unit/test_multiapp.py +163 -0
- mapproxy/test/unit/test_ogr_reader.py +51 -0
- mapproxy/test/unit/test_request.py +745 -0
- mapproxy/test/unit/test_request_wmts.py +178 -0
- mapproxy/test/unit/test_response.py +78 -0
- mapproxy/test/unit/test_seed.py +365 -0
- mapproxy/test/unit/test_seed_cachelock.py +91 -0
- mapproxy/test/unit/test_srs.py +215 -0
- mapproxy/test/unit/test_tiled_source.py +122 -0
- mapproxy/test/unit/test_tilefilter.py +31 -0
- mapproxy/test/unit/test_times.py +25 -0
- mapproxy/test/unit/test_timeutils.py +50 -0
- mapproxy/test/unit/test_util_conf_utils.py +75 -0
- mapproxy/test/unit/test_utils.py +476 -0
- mapproxy/test/unit/test_wms_capabilities.py +44 -0
- mapproxy/test/unit/test_wms_layer.py +113 -0
- mapproxy/test/unit/test_yaml.py +68 -0
- mapproxy/tilefilter.py +61 -0
- mapproxy/util/__init__.py +0 -0
- mapproxy/util/async_.py +229 -0
- mapproxy/util/collections.py +134 -0
- mapproxy/util/coverage.py +337 -0
- mapproxy/util/ext/__init__.py +14 -0
- mapproxy/util/ext/dictspec/__init__.py +1 -0
- mapproxy/util/ext/dictspec/spec.py +131 -0
- mapproxy/util/ext/dictspec/test/__init__.py +0 -0
- mapproxy/util/ext/dictspec/test/test_validator.py +278 -0
- mapproxy/util/ext/dictspec/validator.py +194 -0
- mapproxy/util/ext/local.py +198 -0
- mapproxy/util/ext/lockfile.py +140 -0
- mapproxy/util/ext/odict.py +321 -0
- mapproxy/util/ext/serving.py +491 -0
- mapproxy/util/ext/tempita/__init__.py +1093 -0
- mapproxy/util/ext/tempita/_looper.py +163 -0
- mapproxy/util/ext/tempita/string_utils.py +24 -0
- mapproxy/util/ext/wmsparse/__init__.py +3 -0
- mapproxy/util/ext/wmsparse/duration.py +600 -0
- mapproxy/util/ext/wmsparse/parse.py +307 -0
- mapproxy/util/ext/wmsparse/test/__init__.py +0 -0
- mapproxy/util/ext/wmsparse/test/test_parse.py +111 -0
- mapproxy/util/ext/wmsparse/test/test_util.py +23 -0
- mapproxy/util/ext/wmsparse/test/wms-example-111.xml +90 -0
- mapproxy/util/ext/wmsparse/test/wms-example-130.xml +120 -0
- mapproxy/util/ext/wmsparse/test/wms-large-111.xml +2114 -0
- mapproxy/util/ext/wmsparse/test/wms_nasa_cap.xml +386 -0
- mapproxy/util/ext/wmsparse/util.py +189 -0
- mapproxy/util/fs.py +164 -0
- mapproxy/util/geom.py +307 -0
- mapproxy/util/lib.py +117 -0
- mapproxy/util/lock.py +171 -0
- mapproxy/util/ogr.py +247 -0
- mapproxy/util/py.py +75 -0
- mapproxy/util/times.py +78 -0
- mapproxy/util/yaml.py +58 -0
- mapproxy/version.py +33 -0
- mapproxy/wsgiapp.py +167 -0
|
@@ -0,0 +1,1051 @@
|
|
|
1
|
+
# -:- encoding: utf8 -:-
|
|
2
|
+
# This file is part of the MapProxy project.
|
|
3
|
+
# Copyright (C) 2010 Omniscale <http://omniscale.de>
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
|
|
20
|
+
from io import BytesIO
|
|
21
|
+
|
|
22
|
+
import pytest
|
|
23
|
+
|
|
24
|
+
from mapproxy.compat.image import Image, ImageDraw, PIL_VERSION_TUPLE
|
|
25
|
+
from mapproxy.image import (
|
|
26
|
+
BlankImageSource,
|
|
27
|
+
GeoReference,
|
|
28
|
+
ImageSource,
|
|
29
|
+
ReadBufWrapper,
|
|
30
|
+
SubImageSource,
|
|
31
|
+
TIFF_GEOKEYDIRECTORYTAG,
|
|
32
|
+
TIFF_MODELPIXELSCALETAG,
|
|
33
|
+
TIFF_MODELTIEPOINTTAG,
|
|
34
|
+
_make_transparent as make_transparent,
|
|
35
|
+
img_has_transparency,
|
|
36
|
+
is_single_color_image,
|
|
37
|
+
peek_image_format,
|
|
38
|
+
quantize,
|
|
39
|
+
)
|
|
40
|
+
from mapproxy.image.merge import merge_images, BandMerger
|
|
41
|
+
from mapproxy.image.opts import ImageOptions
|
|
42
|
+
from mapproxy.image.tile import TileMerger, TileSplitter
|
|
43
|
+
from mapproxy.image.transform import ImageTransformer, transform_meshes
|
|
44
|
+
from mapproxy.srs import SRS
|
|
45
|
+
from mapproxy.test.image import (
|
|
46
|
+
is_png,
|
|
47
|
+
is_jpeg,
|
|
48
|
+
is_tiff,
|
|
49
|
+
create_tmp_image_file,
|
|
50
|
+
check_format,
|
|
51
|
+
create_debug_img,
|
|
52
|
+
create_image,
|
|
53
|
+
assert_img_colors_eq,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
PNG_FORMAT = ImageOptions(format="image/png")
|
|
58
|
+
JPEG_FORMAT = ImageOptions(format="image/jpeg")
|
|
59
|
+
TIFF_FORMAT = ImageOptions(format="image/tiff")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TestImageSource(object):
|
|
63
|
+
|
|
64
|
+
def setup_method(self):
|
|
65
|
+
self.tmp_filename = create_tmp_image_file((100, 100))
|
|
66
|
+
|
|
67
|
+
def teardown_method(self):
|
|
68
|
+
os.remove(self.tmp_filename)
|
|
69
|
+
|
|
70
|
+
def test_from_filename(self):
|
|
71
|
+
ir = ImageSource(self.tmp_filename, PNG_FORMAT)
|
|
72
|
+
assert is_png(ir.as_buffer())
|
|
73
|
+
assert ir.as_image().size == (100, 100)
|
|
74
|
+
|
|
75
|
+
def test_from_file(self):
|
|
76
|
+
with open(self.tmp_filename, "rb") as tmp_file:
|
|
77
|
+
ir = ImageSource(tmp_file, "png")
|
|
78
|
+
assert ir.as_buffer() == tmp_file
|
|
79
|
+
assert ir.as_image().size == (100, 100)
|
|
80
|
+
|
|
81
|
+
def test_from_image(self):
|
|
82
|
+
img = Image.new("RGBA", (100, 100))
|
|
83
|
+
ir = ImageSource(img, (100, 100), PNG_FORMAT)
|
|
84
|
+
assert ir.as_image() == img
|
|
85
|
+
assert is_png(ir.as_buffer())
|
|
86
|
+
|
|
87
|
+
def test_from_non_seekable_file(self):
|
|
88
|
+
with open(self.tmp_filename, "rb") as tmp_file:
|
|
89
|
+
data = tmp_file.read()
|
|
90
|
+
|
|
91
|
+
class FileLikeDummy(object):
|
|
92
|
+
# "file" without seek, like urlopen response
|
|
93
|
+
def read(self):
|
|
94
|
+
return data
|
|
95
|
+
|
|
96
|
+
ir = ImageSource(FileLikeDummy(), "png")
|
|
97
|
+
assert ir.as_buffer(seekable=True).read() == data
|
|
98
|
+
assert ir.as_image().size == (100, 100)
|
|
99
|
+
assert ir.as_buffer().read() == data
|
|
100
|
+
|
|
101
|
+
def test_output_formats(self):
|
|
102
|
+
img = Image.new("RGB", (100, 100))
|
|
103
|
+
for format in ["png", "gif", "tiff", "jpeg", "GeoTIFF", "bmp"]:
|
|
104
|
+
ir = ImageSource(img, (100, 100), image_opts=ImageOptions(format=format))
|
|
105
|
+
check_format(ir.as_buffer(), format)
|
|
106
|
+
|
|
107
|
+
def test_converted_output(self):
|
|
108
|
+
ir = ImageSource(self.tmp_filename, (100, 100), PNG_FORMAT)
|
|
109
|
+
assert is_png(ir.as_buffer())
|
|
110
|
+
assert is_jpeg(ir.as_buffer(JPEG_FORMAT))
|
|
111
|
+
assert is_jpeg(ir.as_buffer())
|
|
112
|
+
assert is_tiff(ir.as_buffer(TIFF_FORMAT))
|
|
113
|
+
assert is_tiff(ir.as_buffer())
|
|
114
|
+
|
|
115
|
+
@pytest.mark.skipif(PIL_VERSION_TUPLE < (6, 1, 0), reason="Pillow 6.1.0 required GeoTIFF")
|
|
116
|
+
def test_tiff_compression(self):
|
|
117
|
+
def encoded_size(encoding_options):
|
|
118
|
+
ir = ImageSource(create_debug_img((100, 100)), PNG_FORMAT)
|
|
119
|
+
buf = ir.as_buffer(ImageOptions(format="tiff", encoding_options=encoding_options))
|
|
120
|
+
return len(buf.read())
|
|
121
|
+
|
|
122
|
+
orig = encoded_size({})
|
|
123
|
+
q90 = encoded_size({'tiff_compression': 'jpeg', 'jpeg_quality': 90})
|
|
124
|
+
q75 = encoded_size({'tiff_compression': 'jpeg', 'jpeg_quality': 75})
|
|
125
|
+
qdf = encoded_size({'tiff_compression': 'jpeg'})
|
|
126
|
+
q50 = encoded_size({'tiff_compression': 'jpeg', 'jpeg_quality': 50})
|
|
127
|
+
lzw = encoded_size({'tiff_compression': 'tiff_lzw'})
|
|
128
|
+
|
|
129
|
+
# print(orig, q90, q75, qdf, q50, lzw)
|
|
130
|
+
assert orig > q90
|
|
131
|
+
assert q90 > q75
|
|
132
|
+
assert q75 == qdf
|
|
133
|
+
assert qdf > q50
|
|
134
|
+
assert q50 > lzw
|
|
135
|
+
|
|
136
|
+
@pytest.mark.xfail(
|
|
137
|
+
PIL_VERSION_TUPLE >= (9, 0, 0),
|
|
138
|
+
reason="The palette colors order has been changed in Pillow 9.0.0"
|
|
139
|
+
)
|
|
140
|
+
def test_output_formats_greyscale_png(self):
|
|
141
|
+
img = Image.new("L", (100, 100))
|
|
142
|
+
ir = ImageSource(img, image_opts=PNG_FORMAT)
|
|
143
|
+
img = Image.open(
|
|
144
|
+
ir.as_buffer(ImageOptions(colors=256, transparent=True, format="image/png"))
|
|
145
|
+
)
|
|
146
|
+
assert img.mode == "P"
|
|
147
|
+
assert img.getpixel((0, 0)) == 255
|
|
148
|
+
|
|
149
|
+
def test_output_formats_greyscale_alpha_png(self):
|
|
150
|
+
img = Image.new("LA", (100, 100))
|
|
151
|
+
ir = ImageSource(img, image_opts=PNG_FORMAT)
|
|
152
|
+
img = Image.open(
|
|
153
|
+
ir.as_buffer(ImageOptions(colors=256, transparent=True, format="image/png"))
|
|
154
|
+
)
|
|
155
|
+
assert img.mode == "LA"
|
|
156
|
+
assert img.getpixel((0, 0)) == (0, 0)
|
|
157
|
+
|
|
158
|
+
@pytest.mark.xfail(
|
|
159
|
+
PIL_VERSION_TUPLE >= (9, 0, 0),
|
|
160
|
+
reason="The palette colors order has been changed in Pillow 9.0.0"
|
|
161
|
+
)
|
|
162
|
+
def test_output_formats_png8(self):
|
|
163
|
+
img = Image.new("RGBA", (100, 100))
|
|
164
|
+
ir = ImageSource(img, image_opts=PNG_FORMAT)
|
|
165
|
+
img = Image.open(
|
|
166
|
+
ir.as_buffer(ImageOptions(colors=256, transparent=True, format="image/png"))
|
|
167
|
+
)
|
|
168
|
+
assert img.mode == "P"
|
|
169
|
+
assert img.getpixel((0, 0)) == 255
|
|
170
|
+
|
|
171
|
+
def test_output_formats_png24(self):
|
|
172
|
+
img = Image.new("RGBA", (100, 100))
|
|
173
|
+
image_opts = PNG_FORMAT.copy()
|
|
174
|
+
image_opts.colors = 0 # TODO image_opts
|
|
175
|
+
ir = ImageSource(img, image_opts=image_opts)
|
|
176
|
+
img = Image.open(ir.as_buffer())
|
|
177
|
+
assert img.mode == "RGBA"
|
|
178
|
+
assert img.getpixel((0, 0)) == (0, 0, 0, 0)
|
|
179
|
+
|
|
180
|
+
def test_save_with_unsupported_transparency(self):
|
|
181
|
+
# check if encoding of non-RGB image with tuple as transparency
|
|
182
|
+
# works. workaround for Pillow #2633
|
|
183
|
+
img = Image.new("P", (100, 100))
|
|
184
|
+
img.info["transparency"] = (0, 0, 0)
|
|
185
|
+
image_opts = PNG_FORMAT.copy()
|
|
186
|
+
|
|
187
|
+
ir = ImageSource(img, image_opts=image_opts)
|
|
188
|
+
img = Image.open(ir.as_buffer())
|
|
189
|
+
assert img.mode == "P"
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class TestSubImageSource(object):
|
|
193
|
+
|
|
194
|
+
def test_full(self):
|
|
195
|
+
sub_img = create_image((100, 100), color=[100, 120, 130, 140])
|
|
196
|
+
img = SubImageSource(
|
|
197
|
+
sub_img, size=(100, 100), offset=(0, 0), image_opts=ImageOptions()
|
|
198
|
+
).as_image()
|
|
199
|
+
assert img.getcolors() == [(100 * 100, (100, 120, 130, 140))]
|
|
200
|
+
|
|
201
|
+
def test_larger(self):
|
|
202
|
+
sub_img = create_image((150, 150), color=[100, 120, 130, 140])
|
|
203
|
+
img = SubImageSource(
|
|
204
|
+
sub_img, size=(100, 100), offset=(0, 0), image_opts=ImageOptions()
|
|
205
|
+
).as_image()
|
|
206
|
+
assert img.getcolors() == [(100 * 100, (100, 120, 130, 140))]
|
|
207
|
+
|
|
208
|
+
def test_negative_offset(self):
|
|
209
|
+
sub_img = create_image((150, 150), color=[100, 120, 130, 140])
|
|
210
|
+
img = SubImageSource(
|
|
211
|
+
sub_img, size=(100, 100), offset=(-50, 0), image_opts=ImageOptions()
|
|
212
|
+
).as_image()
|
|
213
|
+
assert img.getcolors() == [(100 * 100, (100, 120, 130, 140))]
|
|
214
|
+
|
|
215
|
+
def test_overlap_right(self):
|
|
216
|
+
sub_img = create_image((50, 50), color=[100, 120, 130, 140])
|
|
217
|
+
img = SubImageSource(
|
|
218
|
+
sub_img,
|
|
219
|
+
size=(100, 100),
|
|
220
|
+
offset=(75, 25),
|
|
221
|
+
image_opts=ImageOptions(transparent=True),
|
|
222
|
+
).as_image()
|
|
223
|
+
assert sorted(img.getcolors()) == [
|
|
224
|
+
(25 * 50, (100, 120, 130, 140)),
|
|
225
|
+
(100 * 100 - 25 * 50, (255, 255, 255, 0)),
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
def test_outside(self):
|
|
229
|
+
sub_img = create_image((50, 50), color=[100, 120, 130, 140])
|
|
230
|
+
img = SubImageSource(
|
|
231
|
+
sub_img,
|
|
232
|
+
size=(100, 100),
|
|
233
|
+
offset=(200, 0),
|
|
234
|
+
image_opts=ImageOptions(transparent=True),
|
|
235
|
+
).as_image()
|
|
236
|
+
assert img.getcolors() == [(100 * 100, (255, 255, 255, 0))]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class ROnly(object):
|
|
240
|
+
|
|
241
|
+
def __init__(self):
|
|
242
|
+
self.data = [b"Hello World!"]
|
|
243
|
+
|
|
244
|
+
def read(self):
|
|
245
|
+
if self.data:
|
|
246
|
+
return self.data.pop()
|
|
247
|
+
return b""
|
|
248
|
+
|
|
249
|
+
def __iter__(self):
|
|
250
|
+
it = iter(self.data)
|
|
251
|
+
self.data = []
|
|
252
|
+
return it
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class TestReadBufWrapper(object):
|
|
256
|
+
|
|
257
|
+
def setup_method(self):
|
|
258
|
+
rbuf = ROnly()
|
|
259
|
+
self.rbuf_wrapper = ReadBufWrapper(rbuf)
|
|
260
|
+
|
|
261
|
+
def test_read(self):
|
|
262
|
+
assert self.rbuf_wrapper.read() == b"Hello World!"
|
|
263
|
+
self.rbuf_wrapper.seek(0)
|
|
264
|
+
assert self.rbuf_wrapper.read() == b""
|
|
265
|
+
|
|
266
|
+
def test_seek_read(self):
|
|
267
|
+
self.rbuf_wrapper.seek(0)
|
|
268
|
+
assert self.rbuf_wrapper.read() == b"Hello World!"
|
|
269
|
+
self.rbuf_wrapper.seek(0)
|
|
270
|
+
assert self.rbuf_wrapper.read() == b"Hello World!"
|
|
271
|
+
|
|
272
|
+
def test_iter(self):
|
|
273
|
+
data = list(self.rbuf_wrapper)
|
|
274
|
+
assert data == [b"Hello World!"]
|
|
275
|
+
self.rbuf_wrapper.seek(0)
|
|
276
|
+
data = list(self.rbuf_wrapper)
|
|
277
|
+
assert data == []
|
|
278
|
+
|
|
279
|
+
def test_seek_iter(self):
|
|
280
|
+
self.rbuf_wrapper.seek(0)
|
|
281
|
+
data = list(self.rbuf_wrapper)
|
|
282
|
+
assert data == [b"Hello World!"]
|
|
283
|
+
self.rbuf_wrapper.seek(0)
|
|
284
|
+
data = list(self.rbuf_wrapper)
|
|
285
|
+
assert data == [b"Hello World!"]
|
|
286
|
+
|
|
287
|
+
def test_hasattr(self):
|
|
288
|
+
assert hasattr(self.rbuf_wrapper, "seek")
|
|
289
|
+
assert hasattr(self.rbuf_wrapper, "readline")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class TestMergeAll(object):
|
|
293
|
+
|
|
294
|
+
def setup_method(self):
|
|
295
|
+
self.cleanup_tiles = []
|
|
296
|
+
|
|
297
|
+
def test_full_merge(self):
|
|
298
|
+
self.cleanup_tiles = [create_tmp_image_file((100, 100)) for _ in range(9)]
|
|
299
|
+
self.tiles = [ImageSource(tile) for tile in self.cleanup_tiles]
|
|
300
|
+
m = TileMerger(tile_grid=(3, 3), tile_size=(100, 100))
|
|
301
|
+
img_opts = ImageOptions()
|
|
302
|
+
result = m.merge(self.tiles, img_opts)
|
|
303
|
+
img = result.as_image()
|
|
304
|
+
assert img.size == (300, 300)
|
|
305
|
+
|
|
306
|
+
def test_one(self):
|
|
307
|
+
self.cleanup_tiles = [create_tmp_image_file((100, 100))]
|
|
308
|
+
self.tiles = [ImageSource(self.cleanup_tiles[0])]
|
|
309
|
+
m = TileMerger(tile_grid=(1, 1), tile_size=(100, 100))
|
|
310
|
+
img_opts = ImageOptions(transparent=True)
|
|
311
|
+
result = m.merge(self.tiles, img_opts)
|
|
312
|
+
img = result.as_image()
|
|
313
|
+
assert img.size == (100, 100)
|
|
314
|
+
assert img.mode == "RGBA"
|
|
315
|
+
|
|
316
|
+
def test_missing_tiles(self):
|
|
317
|
+
self.cleanup_tiles = [create_tmp_image_file((100, 100))]
|
|
318
|
+
self.tiles = [ImageSource(self.cleanup_tiles[0])]
|
|
319
|
+
self.tiles.extend([None] * 8)
|
|
320
|
+
m = TileMerger(tile_grid=(3, 3), tile_size=(100, 100))
|
|
321
|
+
img_opts = ImageOptions()
|
|
322
|
+
result = m.merge(self.tiles, img_opts)
|
|
323
|
+
img = result.as_image()
|
|
324
|
+
assert img.size == (300, 300)
|
|
325
|
+
assert img.getcolors() == [(80000, (255, 255, 255)), (10000, (0, 0, 0))]
|
|
326
|
+
|
|
327
|
+
def test_invalid_tile(self):
|
|
328
|
+
self.cleanup_tiles = [create_tmp_image_file((100, 100)) for _ in range(9)]
|
|
329
|
+
self.tiles = [ImageSource(tile) for tile in self.cleanup_tiles]
|
|
330
|
+
invalid_tile = self.tiles[0].source
|
|
331
|
+
with open(invalid_tile, "wb") as tmp:
|
|
332
|
+
tmp.write(b"invalid")
|
|
333
|
+
m = TileMerger(tile_grid=(3, 3), tile_size=(100, 100))
|
|
334
|
+
img_opts = ImageOptions(bgcolor=(200, 0, 50))
|
|
335
|
+
result = m.merge(self.tiles, img_opts)
|
|
336
|
+
img = result.as_image()
|
|
337
|
+
assert img.size == (300, 300)
|
|
338
|
+
assert img.getcolors() == [(10000, (200, 0, 50)), (80000, (0, 0, 0))]
|
|
339
|
+
assert not os.path.isfile(invalid_tile)
|
|
340
|
+
|
|
341
|
+
def test_none_merge(self):
|
|
342
|
+
tiles = [None]
|
|
343
|
+
m = TileMerger(tile_grid=(1, 1), tile_size=(100, 100))
|
|
344
|
+
img_opts = ImageOptions(mode="RGBA", bgcolor=(200, 100, 30, 40))
|
|
345
|
+
result = m.merge(tiles, img_opts)
|
|
346
|
+
img = result.as_image()
|
|
347
|
+
assert img.size == (100, 100)
|
|
348
|
+
assert img.getcolors() == [(100 * 100, (200, 100, 30, 40))]
|
|
349
|
+
|
|
350
|
+
def teardown_method(self):
|
|
351
|
+
for tile_fname in self.cleanup_tiles:
|
|
352
|
+
if tile_fname and os.path.isfile(tile_fname):
|
|
353
|
+
os.remove(tile_fname)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class TestGetCrop(object):
|
|
357
|
+
|
|
358
|
+
def setup_method(self):
|
|
359
|
+
self.tmp_file = create_tmp_image_file((100, 100), two_colored=True)
|
|
360
|
+
self.img = ImageSource(
|
|
361
|
+
self.tmp_file, image_opts=ImageOptions(format="image/png"), size=(100, 100)
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
def teardown_method(self):
|
|
365
|
+
if os.path.exists(self.tmp_file):
|
|
366
|
+
os.remove(self.tmp_file)
|
|
367
|
+
|
|
368
|
+
def test_perfect_match(self):
|
|
369
|
+
bbox = (-10, -5, 30, 35)
|
|
370
|
+
transformer = ImageTransformer(SRS(4326), SRS(4326))
|
|
371
|
+
result = transformer.transform(
|
|
372
|
+
self.img, bbox, (100, 100), bbox, image_opts=None
|
|
373
|
+
)
|
|
374
|
+
assert self.img == result
|
|
375
|
+
|
|
376
|
+
def test_simple_resize_nearest(self):
|
|
377
|
+
bbox = (-10, -5, 30, 35)
|
|
378
|
+
transformer = ImageTransformer(SRS(4326), SRS(4326))
|
|
379
|
+
result = transformer.transform(
|
|
380
|
+
self.img,
|
|
381
|
+
bbox,
|
|
382
|
+
(200, 200),
|
|
383
|
+
bbox,
|
|
384
|
+
image_opts=ImageOptions(resampling="nearest"),
|
|
385
|
+
)
|
|
386
|
+
img = result.as_image()
|
|
387
|
+
|
|
388
|
+
assert img.size == (200, 200)
|
|
389
|
+
assert len(img.getcolors()) == 2
|
|
390
|
+
img.close()
|
|
391
|
+
|
|
392
|
+
def test_simple_resize_bilinear(self):
|
|
393
|
+
bbox = (-10, -5, 30, 35)
|
|
394
|
+
transformer = ImageTransformer(SRS(4326), SRS(4326))
|
|
395
|
+
result = transformer.transform(
|
|
396
|
+
self.img,
|
|
397
|
+
bbox,
|
|
398
|
+
(200, 200),
|
|
399
|
+
bbox,
|
|
400
|
+
image_opts=ImageOptions(resampling="bilinear"),
|
|
401
|
+
)
|
|
402
|
+
img = result.as_image()
|
|
403
|
+
|
|
404
|
+
assert img.size == (200, 200)
|
|
405
|
+
# some shades of grey with bilinear
|
|
406
|
+
assert len(img.getcolors()) >= 4
|
|
407
|
+
img.close()
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class TestLayerMerge(object):
|
|
411
|
+
|
|
412
|
+
def test_opacity_merge(self):
|
|
413
|
+
img1 = ImageSource(Image.new("RGB", (10, 10), (255, 0, 255)))
|
|
414
|
+
img2 = ImageSource(
|
|
415
|
+
Image.new("RGB", (10, 10), (0, 255, 255)),
|
|
416
|
+
image_opts=ImageOptions(opacity=0.5),
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
result = merge_images([img1, img2], ImageOptions(transparent=False))
|
|
420
|
+
img = result.as_image()
|
|
421
|
+
assert img.getpixel((0, 0)) == (127, 127, 255)
|
|
422
|
+
|
|
423
|
+
def test_opacity_merge_mixed_modes(self):
|
|
424
|
+
img1 = ImageSource(Image.new("RGBA", (10, 10), (255, 0, 255, 255)))
|
|
425
|
+
img2 = ImageSource(
|
|
426
|
+
Image.new("RGB", (10, 10), (0, 255, 255)).convert("P"),
|
|
427
|
+
image_opts=ImageOptions(opacity=0.5),
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
result = merge_images([img1, img2], ImageOptions(transparent=True))
|
|
431
|
+
img = result.as_image()
|
|
432
|
+
assert_img_colors_eq(img, [(10 * 10, (127, 127, 255, 255))])
|
|
433
|
+
|
|
434
|
+
def test_merge_L(self):
|
|
435
|
+
img1 = ImageSource(Image.new("RGBA", (10, 10), (255, 0, 255, 255)))
|
|
436
|
+
img2 = ImageSource(Image.new("L", (10, 10), 100))
|
|
437
|
+
|
|
438
|
+
# img2 overlays img1
|
|
439
|
+
result = merge_images([img1, img2], ImageOptions(transparent=True))
|
|
440
|
+
img = result.as_image()
|
|
441
|
+
assert_img_colors_eq(img, [(10 * 10, (100, 100, 100, 255))])
|
|
442
|
+
|
|
443
|
+
@pytest.mark.skipif(
|
|
444
|
+
not hasattr(Image, "FASTOCTREE"), reason="PIL has no FASTOCTREE"
|
|
445
|
+
)
|
|
446
|
+
def test_paletted_merge(self):
|
|
447
|
+
# generate RGBA images with a transparent rectangle in the lower right
|
|
448
|
+
img1 = ImageSource(Image.new("RGBA", (50, 50), (0, 255, 0, 255))).as_image()
|
|
449
|
+
draw = ImageDraw.Draw(img1)
|
|
450
|
+
draw.rectangle((25, 25, 49, 49), fill=(0, 0, 0, 0))
|
|
451
|
+
paletted_img = quantize(img1, alpha=True)
|
|
452
|
+
assert img_has_transparency(paletted_img)
|
|
453
|
+
assert paletted_img.mode == "P"
|
|
454
|
+
|
|
455
|
+
rgba_img = Image.new("RGBA", (50, 50), (255, 0, 0, 255))
|
|
456
|
+
draw = ImageDraw.Draw(rgba_img)
|
|
457
|
+
draw.rectangle((25, 25, 49, 49), fill=(0, 0, 0, 0))
|
|
458
|
+
|
|
459
|
+
img1 = ImageSource(paletted_img)
|
|
460
|
+
img2 = ImageSource(rgba_img)
|
|
461
|
+
|
|
462
|
+
# generate base image and merge the others above
|
|
463
|
+
img3 = ImageSource(Image.new("RGBA", (50, 50), (0, 0, 255, 255)))
|
|
464
|
+
result = merge_images([img3, img1, img2], ImageOptions(transparent=True))
|
|
465
|
+
img = result.as_image()
|
|
466
|
+
|
|
467
|
+
assert img.mode == "RGBA"
|
|
468
|
+
assert img.getpixel((49, 49)) == (0, 0, 255, 255)
|
|
469
|
+
assert img.getpixel((0, 0)) == (255, 0, 0, 255)
|
|
470
|
+
|
|
471
|
+
def test_solid_merge(self):
|
|
472
|
+
img1 = ImageSource(Image.new("RGB", (10, 10), (255, 0, 255)))
|
|
473
|
+
img2 = ImageSource(Image.new("RGB", (10, 10), (0, 255, 255)))
|
|
474
|
+
|
|
475
|
+
result = merge_images([img1, img2], ImageOptions(transparent=False))
|
|
476
|
+
img = result.as_image()
|
|
477
|
+
assert img.getpixel((0, 0)) == (0, 255, 255)
|
|
478
|
+
|
|
479
|
+
def test_merge_rgb_with_transp(self):
|
|
480
|
+
img1 = ImageSource(Image.new("RGB", (10, 10), (255, 0, 255)))
|
|
481
|
+
raw = Image.new("RGB", (10, 10), (0, 255, 255))
|
|
482
|
+
raw.info = {"transparency": (0, 255, 255)} # make full transparent
|
|
483
|
+
img2 = ImageSource(raw)
|
|
484
|
+
|
|
485
|
+
result = merge_images([img1, img2], ImageOptions(transparent=False))
|
|
486
|
+
img = result.as_image()
|
|
487
|
+
assert img.getpixel((0, 0)) == (255, 0, 255)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
@pytest.mark.skipif(
|
|
491
|
+
not hasattr(Image, "alpha_composite"), reason="PIL has no alpha_composite"
|
|
492
|
+
)
|
|
493
|
+
class TestLayerCompositeMerge(object):
|
|
494
|
+
|
|
495
|
+
def test_composite_merge(self):
|
|
496
|
+
# http://stackoverflow.com/questions/3374878
|
|
497
|
+
|
|
498
|
+
img1 = Image.new("RGBA", size=(100, 100), color=(255, 0, 0, 255))
|
|
499
|
+
draw = ImageDraw.Draw(img1)
|
|
500
|
+
draw.rectangle((33, 0, 66, 100), fill=(255, 0, 0, 128))
|
|
501
|
+
draw.rectangle((67, 0, 100, 100), fill=(255, 0, 0, 0))
|
|
502
|
+
img1 = ImageSource(img1)
|
|
503
|
+
img2 = Image.new("RGBA", size=(100, 100), color=(0, 255, 0, 255))
|
|
504
|
+
draw = ImageDraw.Draw(img2)
|
|
505
|
+
draw.rectangle((0, 33, 100, 66), fill=(0, 255, 0, 128))
|
|
506
|
+
draw.rectangle((0, 67, 100, 100), fill=(0, 255, 0, 0))
|
|
507
|
+
img2 = ImageSource(img2)
|
|
508
|
+
|
|
509
|
+
result = merge_images([img2, img1], ImageOptions(transparent=True))
|
|
510
|
+
img = result.as_image()
|
|
511
|
+
assert img.mode == "RGBA"
|
|
512
|
+
assert_img_colors_eq(
|
|
513
|
+
img,
|
|
514
|
+
[
|
|
515
|
+
(1089, (0, 255, 0, 255)),
|
|
516
|
+
(1089, (255, 255, 255, 0)),
|
|
517
|
+
(1122, (0, 255, 0, 128)),
|
|
518
|
+
(1122, (128, 126, 0, 255)),
|
|
519
|
+
(1122, (255, 0, 0, 128)),
|
|
520
|
+
(1156, (170, 84, 0, 191)),
|
|
521
|
+
(3300, (255, 0, 0, 255)),
|
|
522
|
+
],
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
def test_composite_merge_opacity(self):
|
|
526
|
+
bg = Image.new("RGBA", size=(100, 100), color=(255, 0, 255, 255))
|
|
527
|
+
bg = ImageSource(bg)
|
|
528
|
+
fg = Image.new("RGBA", size=(100, 100), color=(0, 0, 0, 0))
|
|
529
|
+
draw = ImageDraw.Draw(fg)
|
|
530
|
+
draw.rectangle((10, 10, 89, 89), fill=(0, 255, 255, 255))
|
|
531
|
+
fg = ImageSource(fg, image_opts=ImageOptions(opacity=0.5))
|
|
532
|
+
|
|
533
|
+
result = merge_images([bg, fg], ImageOptions(transparent=True))
|
|
534
|
+
img = result.as_image()
|
|
535
|
+
assert img.mode == "RGBA"
|
|
536
|
+
assert_img_colors_eq(
|
|
537
|
+
img, [(3600, (255, 0, 255, 255)), (6400, (128, 127, 255, 255))]
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
class TestTransform(object):
|
|
542
|
+
|
|
543
|
+
def setup_method(self):
|
|
544
|
+
self.src_img = ImageSource(create_debug_img((200, 200), transparent=False))
|
|
545
|
+
self.src_srs = SRS(31467)
|
|
546
|
+
self.dst_size = (100, 150)
|
|
547
|
+
self.dst_srs = SRS(4326)
|
|
548
|
+
self.dst_bbox = (0.2, 45.1, 8.3, 53.2)
|
|
549
|
+
self.src_bbox = self.dst_srs.transform_bbox_to(self.src_srs, self.dst_bbox)
|
|
550
|
+
|
|
551
|
+
def test_transform(self):
|
|
552
|
+
transformer = ImageTransformer(self.src_srs, self.dst_srs)
|
|
553
|
+
result = transformer.transform(
|
|
554
|
+
self.src_img,
|
|
555
|
+
self.src_bbox,
|
|
556
|
+
self.dst_size,
|
|
557
|
+
self.dst_bbox,
|
|
558
|
+
image_opts=ImageOptions(resampling="nearest"),
|
|
559
|
+
)
|
|
560
|
+
assert isinstance(result, ImageSource)
|
|
561
|
+
assert result.as_image() != self.src_img.as_image()
|
|
562
|
+
assert result.size == (100, 150)
|
|
563
|
+
|
|
564
|
+
def _test_compare_max_px_err(self):
|
|
565
|
+
"""
|
|
566
|
+
Create transformations with different div values.
|
|
567
|
+
"""
|
|
568
|
+
for err in [0.2, 0.5, 1, 2, 4, 6, 8, 12, 16]:
|
|
569
|
+
transformer = ImageTransformer(self.src_srs, self.dst_srs, max_px_err=err)
|
|
570
|
+
result = transformer.transform(
|
|
571
|
+
self.src_img,
|
|
572
|
+
self.src_bbox,
|
|
573
|
+
self.dst_size,
|
|
574
|
+
self.dst_bbox,
|
|
575
|
+
image_opts=ImageOptions(resampling="nearest"),
|
|
576
|
+
)
|
|
577
|
+
result.as_image().save("/tmp/transform-%03d.png" % (err * 10,))
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def assert_geotiff_tags(img, expected_origin, expected_pixel_res, srs, projected):
|
|
581
|
+
tags = img.tag_v2
|
|
582
|
+
print(dict(tags))
|
|
583
|
+
print(dict(tags.tagtype))
|
|
584
|
+
assert tags[TIFF_MODELTIEPOINTTAG] == (
|
|
585
|
+
0.0, 0.0, 0.0, expected_origin[0], expected_origin[1], 0.0,
|
|
586
|
+
)
|
|
587
|
+
assert tags[TIFF_MODELPIXELSCALETAG] == pytest.approx((
|
|
588
|
+
expected_pixel_res[0], expected_pixel_res[1], 0.0,
|
|
589
|
+
))
|
|
590
|
+
assert len(tags[TIFF_GEOKEYDIRECTORYTAG]) == 4*4
|
|
591
|
+
assert tags[TIFF_GEOKEYDIRECTORYTAG][0*4+3] == 3
|
|
592
|
+
assert tags[TIFF_GEOKEYDIRECTORYTAG][1*4+3] == (1 if projected else 2)
|
|
593
|
+
assert tags[TIFF_GEOKEYDIRECTORYTAG][3*4+3] == srs
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
@pytest.mark.skipif(PIL_VERSION_TUPLE < (6, 1, 0), reason="Pillow 6.1.0 required GeoTIFF")
|
|
597
|
+
@pytest.mark.parametrize("compression", ['jpeg', 'raw', 'tiff_lzw'])
|
|
598
|
+
class TestGeoTIFF(object):
|
|
599
|
+
|
|
600
|
+
@pytest.mark.parametrize(
|
|
601
|
+
"srs,bbox,size,expected_pixel_res,expected_origin,projected",
|
|
602
|
+
[
|
|
603
|
+
(4326, (-180, -90, 180, 90), (360, 180), (1.0, 1.0), (-180, 90), False),
|
|
604
|
+
(4326, (-180, -90, 180, 90), (360, 360), (1.0, 0.5), (-180, 90), False),
|
|
605
|
+
(3857, (10000, 20000, 11000, 22000), (500, 1000), (2.0, 2.0), (10000, 22000), True),
|
|
606
|
+
(25832, (442691.10009850014, 5889716.375224128, 447502.95988220774, 5894528.235007785),
|
|
607
|
+
(256, 256), (18.796327, 18.796327), (442691.10009850014, 5894528.235007785), True),
|
|
608
|
+
],
|
|
609
|
+
)
|
|
610
|
+
def test_geotiff_tags(
|
|
611
|
+
self, tmpdir, srs, bbox, size,
|
|
612
|
+
expected_pixel_res, expected_origin, projected,
|
|
613
|
+
compression,
|
|
614
|
+
):
|
|
615
|
+
img = ImageSource(create_debug_img(size), georef=GeoReference(bbox=bbox, srs=SRS(srs)))
|
|
616
|
+
|
|
617
|
+
img_opts = ImageOptions(format='tiff', encoding_options={'tiff_compression': compression})
|
|
618
|
+
img2 = ImageSource(img.as_buffer(img_opts)).as_image()
|
|
619
|
+
|
|
620
|
+
assert_geotiff_tags(img2, expected_origin, expected_pixel_res, srs, projected)
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
class TestMesh(object):
|
|
624
|
+
|
|
625
|
+
def test_mesh_utm(self):
|
|
626
|
+
meshes = transform_meshes(
|
|
627
|
+
src_size=(1335, 1531),
|
|
628
|
+
src_bbox=(3.65, 39.84, 17.00, 55.15),
|
|
629
|
+
src_srs=SRS(4326),
|
|
630
|
+
dst_size=(853, 1683),
|
|
631
|
+
dst_bbox=(158512, 4428236, 1012321, 6111268),
|
|
632
|
+
dst_srs=SRS(25832),
|
|
633
|
+
)
|
|
634
|
+
assert len(meshes) == 40
|
|
635
|
+
|
|
636
|
+
def test_mesh_none(self):
|
|
637
|
+
meshes = transform_meshes(
|
|
638
|
+
src_size=(1000, 1500),
|
|
639
|
+
src_bbox=(0, 0, 10, 15),
|
|
640
|
+
src_srs=SRS(4326),
|
|
641
|
+
dst_size=(1000, 1500),
|
|
642
|
+
dst_bbox=(0, 0, 10, 15),
|
|
643
|
+
dst_srs=SRS(4326),
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
assert meshes == [
|
|
647
|
+
((0, 0, 1000, 1500), [0.0, 0.0, 0.0, 1500.0, 1000.0, 1500.0, 1000.0, 0.0])
|
|
648
|
+
]
|
|
649
|
+
assert len(meshes) == 1
|
|
650
|
+
|
|
651
|
+
def test_mesh(self):
|
|
652
|
+
# low map scale -> more meshes
|
|
653
|
+
# print(SRS(4326).transform_bbox_to(SRS(3857), (5, 50, 10, 55)))
|
|
654
|
+
meshes = transform_meshes(
|
|
655
|
+
src_size=(1000, 2000),
|
|
656
|
+
src_bbox=(556597, 6446275, 1113194, 7361866),
|
|
657
|
+
src_srs=SRS(3857),
|
|
658
|
+
dst_size=(1000, 1000),
|
|
659
|
+
dst_bbox=(5, 50, 10, 55),
|
|
660
|
+
dst_srs=SRS(4326),
|
|
661
|
+
)
|
|
662
|
+
assert len(meshes) == 16
|
|
663
|
+
|
|
664
|
+
# large map scale -> one meshes
|
|
665
|
+
# print(SRS(4326).transform_bbox_to(SRS(3857), (5, 50, 5.1, 50.1)))
|
|
666
|
+
meshes = transform_meshes(
|
|
667
|
+
src_size=(1000, 2000),
|
|
668
|
+
src_bbox=(
|
|
669
|
+
556597.4539663672,
|
|
670
|
+
6446275.841017158,
|
|
671
|
+
567729.4030456939,
|
|
672
|
+
6463612.124257667,
|
|
673
|
+
),
|
|
674
|
+
src_srs=SRS(3857),
|
|
675
|
+
dst_size=(1000, 1000),
|
|
676
|
+
dst_bbox=(5, 50, 5.1, 50.1),
|
|
677
|
+
dst_srs=SRS(4326),
|
|
678
|
+
)
|
|
679
|
+
assert len(meshes) == 1
|
|
680
|
+
|
|
681
|
+
# quad stretches whole image plus 1 pixel
|
|
682
|
+
assert meshes[0][0] == (0, 0, 1000, 1000)
|
|
683
|
+
for e, a in zip(
|
|
684
|
+
meshes[0][1], [0.0, 0.0, 0.0, 2000.0, 1000.0, 2000.0, 1000.0, 0.0]
|
|
685
|
+
):
|
|
686
|
+
assert e == pytest.approx(a, abs=1e-9)
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
class TestSingleColorImage(object):
|
|
690
|
+
|
|
691
|
+
def test_one_point(self):
|
|
692
|
+
img = Image.new("RGB", (100, 100), color="#ff0000")
|
|
693
|
+
draw = ImageDraw.Draw(img)
|
|
694
|
+
draw.point((99, 99))
|
|
695
|
+
del draw
|
|
696
|
+
|
|
697
|
+
assert not is_single_color_image(img)
|
|
698
|
+
|
|
699
|
+
def test_solid(self):
|
|
700
|
+
img = Image.new("RGB", (100, 100), color="#ff0102")
|
|
701
|
+
assert is_single_color_image(img) == (255, 1, 2)
|
|
702
|
+
|
|
703
|
+
def test_solid_w_alpha(self):
|
|
704
|
+
img = Image.new("RGBA", (100, 100), color="#ff0102")
|
|
705
|
+
assert is_single_color_image(img) == (255, 1, 2, 255)
|
|
706
|
+
|
|
707
|
+
def test_solid_paletted_image(self):
|
|
708
|
+
img = Image.new("P", (100, 100), color=20)
|
|
709
|
+
palette = []
|
|
710
|
+
for i in range(256):
|
|
711
|
+
palette.extend((i, i // 2, i % 3))
|
|
712
|
+
img.putpalette(palette)
|
|
713
|
+
assert is_single_color_image(img) == (20, 10, 2)
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
class TestMakeTransparent(object):
|
|
717
|
+
|
|
718
|
+
def _make_test_image(self):
|
|
719
|
+
img = Image.new("RGB", (50, 50), (130, 140, 120))
|
|
720
|
+
draw = ImageDraw.Draw(img)
|
|
721
|
+
draw.rectangle((10, 10, 39, 39), fill=(130, 150, 120))
|
|
722
|
+
return img
|
|
723
|
+
|
|
724
|
+
def _make_transp_test_image(self):
|
|
725
|
+
img = Image.new("RGBA", (50, 50), (130, 140, 120, 100))
|
|
726
|
+
draw = ImageDraw.Draw(img)
|
|
727
|
+
draw.rectangle((10, 10, 39, 39), fill=(130, 150, 120, 120))
|
|
728
|
+
return img
|
|
729
|
+
|
|
730
|
+
def test_result(self):
|
|
731
|
+
img = self._make_test_image()
|
|
732
|
+
img = make_transparent(img, (130, 150, 120), tolerance=5)
|
|
733
|
+
assert img.mode == "RGBA"
|
|
734
|
+
assert img.size == (50, 50)
|
|
735
|
+
colors = img.getcolors()
|
|
736
|
+
assert colors == [(1600, (130, 140, 120, 255)), (900, (130, 150, 120, 0))]
|
|
737
|
+
|
|
738
|
+
def test_with_color_fuzz(self):
|
|
739
|
+
img = self._make_test_image()
|
|
740
|
+
img = make_transparent(img, (128, 154, 121), tolerance=5)
|
|
741
|
+
assert img.mode == "RGBA"
|
|
742
|
+
assert img.size == (50, 50)
|
|
743
|
+
colors = img.getcolors()
|
|
744
|
+
assert colors == [(1600, (130, 140, 120, 255)), (900, (130, 150, 120, 0))]
|
|
745
|
+
|
|
746
|
+
def test_no_match(self):
|
|
747
|
+
img = self._make_test_image()
|
|
748
|
+
img = make_transparent(img, (130, 160, 120), tolerance=5)
|
|
749
|
+
assert img.mode == "RGBA"
|
|
750
|
+
assert img.size == (50, 50)
|
|
751
|
+
colors = img.getcolors()
|
|
752
|
+
assert colors == [(1600, (130, 140, 120, 255)), (900, (130, 150, 120, 255))]
|
|
753
|
+
|
|
754
|
+
def test_from_paletted(self):
|
|
755
|
+
img = self._make_test_image().quantize(256)
|
|
756
|
+
img = make_transparent(img, (130, 150, 120), tolerance=5)
|
|
757
|
+
assert img.mode == "RGBA"
|
|
758
|
+
assert img.size == (50, 50)
|
|
759
|
+
colors = img.getcolors()
|
|
760
|
+
assert colors == [(1600, (130, 140, 120, 255)), (900, (130, 150, 120, 0))]
|
|
761
|
+
|
|
762
|
+
def test_from_transparent(self):
|
|
763
|
+
img = self._make_transp_test_image()
|
|
764
|
+
draw = ImageDraw.Draw(img)
|
|
765
|
+
draw.rectangle((0, 0, 4, 4), fill=(130, 100, 120, 0))
|
|
766
|
+
draw.rectangle((5, 5, 9, 9), fill=(130, 150, 120, 255))
|
|
767
|
+
img = make_transparent(img, (130, 150, 120, 120), tolerance=5)
|
|
768
|
+
assert img.mode == "RGBA"
|
|
769
|
+
assert img.size == (50, 50)
|
|
770
|
+
colors = sorted(img.getcolors(), reverse=True)
|
|
771
|
+
|
|
772
|
+
assert colors == [
|
|
773
|
+
(1550, (130, 140, 120, 100)),
|
|
774
|
+
(900, (130, 150, 120, 0)),
|
|
775
|
+
(25, (130, 150, 120, 255)),
|
|
776
|
+
(25, (130, 100, 120, 0)),
|
|
777
|
+
]
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
class TestTileSplitter(object):
|
|
781
|
+
|
|
782
|
+
def test_background_larger_crop(self):
|
|
783
|
+
img = ImageSource(Image.new("RGB", (356, 266), (130, 140, 120)))
|
|
784
|
+
img_opts = ImageOptions("RGB")
|
|
785
|
+
splitter = TileSplitter(img, img_opts)
|
|
786
|
+
|
|
787
|
+
tile = splitter.get_tile((0, 0), (256, 256))
|
|
788
|
+
|
|
789
|
+
assert tile.size == (256, 256)
|
|
790
|
+
colors = tile.as_image().getcolors()
|
|
791
|
+
assert colors == [(256 * 256, (130, 140, 120))]
|
|
792
|
+
|
|
793
|
+
tile = splitter.get_tile((256, 256), (256, 256))
|
|
794
|
+
|
|
795
|
+
assert tile.size == (256, 256)
|
|
796
|
+
colors = tile.as_image().getcolors()
|
|
797
|
+
assert sorted(colors) == [
|
|
798
|
+
(10 * 100, (130, 140, 120)),
|
|
799
|
+
(256 * 256 - 10 * 100, (255, 255, 255)),
|
|
800
|
+
]
|
|
801
|
+
|
|
802
|
+
def test_background_larger_crop_with_transparent(self):
|
|
803
|
+
img = ImageSource(Image.new("RGBA", (356, 266), (130, 140, 120, 255)))
|
|
804
|
+
img_opts = ImageOptions("RGBA", transparent=True)
|
|
805
|
+
splitter = TileSplitter(img, img_opts)
|
|
806
|
+
|
|
807
|
+
tile = splitter.get_tile((0, 0), (256, 256))
|
|
808
|
+
|
|
809
|
+
assert tile.size == (256, 256)
|
|
810
|
+
colors = tile.as_image().getcolors()
|
|
811
|
+
assert colors == [(256 * 256, (130, 140, 120, 255))]
|
|
812
|
+
|
|
813
|
+
tile = splitter.get_tile((256, 256), (256, 256))
|
|
814
|
+
|
|
815
|
+
assert tile.size == (256, 256)
|
|
816
|
+
colors = tile.as_image().getcolors()
|
|
817
|
+
assert sorted(colors) == [
|
|
818
|
+
(10 * 100, (130, 140, 120, 255)),
|
|
819
|
+
(256 * 256 - 10 * 100, (255, 255, 255, 0)),
|
|
820
|
+
]
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
@pytest.mark.skipif(not hasattr(Image, "FASTOCTREE"), reason="PIL has no FASTOCTREE")
|
|
824
|
+
class TestHasTransparency(object):
|
|
825
|
+
|
|
826
|
+
def test_rgb(self):
|
|
827
|
+
img = Image.new("RGB", (10, 10))
|
|
828
|
+
assert not img_has_transparency(img)
|
|
829
|
+
|
|
830
|
+
img = quantize(img, alpha=False)
|
|
831
|
+
assert not img_has_transparency(img)
|
|
832
|
+
|
|
833
|
+
def test_rbga(self):
|
|
834
|
+
img = Image.new("RGBA", (10, 10), (100, 200, 50, 255))
|
|
835
|
+
img.paste((255, 50, 50, 0), (3, 3, 7, 7))
|
|
836
|
+
assert img_has_transparency(img)
|
|
837
|
+
|
|
838
|
+
img = quantize(img, alpha=True)
|
|
839
|
+
assert img_has_transparency(img)
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
class TestPeekImageFormat(object):
|
|
843
|
+
|
|
844
|
+
@pytest.mark.parametrize(
|
|
845
|
+
"format,expected_format",
|
|
846
|
+
[
|
|
847
|
+
["png", "png"],
|
|
848
|
+
["tiff", "tiff"],
|
|
849
|
+
["gif", "gif"],
|
|
850
|
+
["jpeg", "jpeg"],
|
|
851
|
+
["bmp", None],
|
|
852
|
+
],
|
|
853
|
+
)
|
|
854
|
+
def test_peek_format(self, format, expected_format):
|
|
855
|
+
buf = BytesIO()
|
|
856
|
+
Image.new("RGB", (100, 100)).save(buf, format)
|
|
857
|
+
assert peek_image_format(buf) == expected_format
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
class TestBandMerge(object):
|
|
861
|
+
|
|
862
|
+
def setup_method(self):
|
|
863
|
+
self.img0 = ImageSource(Image.new("RGB", (10, 10), (0, 10, 20)))
|
|
864
|
+
self.img1 = ImageSource(Image.new("RGB", (10, 10), (100, 110, 120)))
|
|
865
|
+
self.img2 = ImageSource(Image.new("RGB", (10, 10), (200, 210, 220)))
|
|
866
|
+
self.img3 = ImageSource(Image.new("RGB", (10, 10), (0, 255, 0)))
|
|
867
|
+
self.blank = BlankImageSource(size=(10, 10), image_opts=ImageOptions())
|
|
868
|
+
|
|
869
|
+
def test_merge_noops(self):
|
|
870
|
+
"""
|
|
871
|
+
Check that black image is returned for no ops.
|
|
872
|
+
"""
|
|
873
|
+
merger = BandMerger(mode="RGB")
|
|
874
|
+
|
|
875
|
+
img_opts = ImageOptions("RGB")
|
|
876
|
+
result = merger.merge([self.img0], img_opts)
|
|
877
|
+
img = result.as_image()
|
|
878
|
+
assert img.size == (10, 10)
|
|
879
|
+
assert img.getpixel((0, 0)) == (0, 0, 0)
|
|
880
|
+
|
|
881
|
+
def test_merge_missing_source(self):
|
|
882
|
+
"""
|
|
883
|
+
Check that empty source list or source list with missing images
|
|
884
|
+
returns BlankImageSource.
|
|
885
|
+
"""
|
|
886
|
+
merger = BandMerger(mode="RGB")
|
|
887
|
+
merger.add_ops(dst_band=0, src_img=0, src_band=0)
|
|
888
|
+
merger.add_ops(dst_band=1, src_img=1, src_band=0)
|
|
889
|
+
merger.add_ops(dst_band=2, src_img=2, src_band=0)
|
|
890
|
+
|
|
891
|
+
img_opts = ImageOptions("RGBA", transparent=True)
|
|
892
|
+
result = merger.merge([], img_opts, size=(10, 10))
|
|
893
|
+
img = result.as_image()
|
|
894
|
+
|
|
895
|
+
assert img.size == (10, 10)
|
|
896
|
+
assert img.getpixel((0, 0)) == (255, 255, 255, 0)
|
|
897
|
+
|
|
898
|
+
result = merger.merge([self.img0, self.img1], img_opts, size=(10, 10))
|
|
899
|
+
img = result.as_image()
|
|
900
|
+
|
|
901
|
+
assert img.size == (10, 10)
|
|
902
|
+
assert img.getpixel((0, 0)) == (255, 255, 255, 0)
|
|
903
|
+
|
|
904
|
+
def test_rgb_merge(self):
|
|
905
|
+
"""
|
|
906
|
+
Check merge of RGB bands
|
|
907
|
+
"""
|
|
908
|
+
merger = BandMerger(mode="RGB")
|
|
909
|
+
|
|
910
|
+
merger.add_ops(dst_band=1, src_img=0, src_band=0, factor=0.5)
|
|
911
|
+
merger.add_ops(dst_band=1, src_img=3, src_band=1, factor=0.5)
|
|
912
|
+
merger.add_ops(dst_band=0, src_img=2, src_band=1)
|
|
913
|
+
merger.add_ops(dst_band=2, src_img=1, src_band=2)
|
|
914
|
+
|
|
915
|
+
img_opts = ImageOptions("RGB")
|
|
916
|
+
result = merger.merge([self.img0, self.img1, self.img2, self.img3], img_opts)
|
|
917
|
+
img = result.as_image()
|
|
918
|
+
|
|
919
|
+
assert img.getpixel((0, 0)) == (210, 127, 120)
|
|
920
|
+
|
|
921
|
+
def test_rgb_merge_missing(self):
|
|
922
|
+
"""
|
|
923
|
+
Check missing band is set to 0
|
|
924
|
+
"""
|
|
925
|
+
merger = BandMerger(mode="RGB")
|
|
926
|
+
|
|
927
|
+
merger.add_ops(dst_band=0, src_img=2, src_band=1)
|
|
928
|
+
merger.add_ops(dst_band=2, src_img=1, src_band=2)
|
|
929
|
+
|
|
930
|
+
img_opts = ImageOptions("RGB")
|
|
931
|
+
result = merger.merge([self.img0, self.img1, self.img2, self.img3], img_opts)
|
|
932
|
+
img = result.as_image()
|
|
933
|
+
|
|
934
|
+
assert img.getpixel((0, 0)) == (210, 0, 120)
|
|
935
|
+
|
|
936
|
+
def test_rgba_merge(self):
|
|
937
|
+
"""
|
|
938
|
+
Check merge of RGBA bands
|
|
939
|
+
"""
|
|
940
|
+
merger = BandMerger(mode="RGBA")
|
|
941
|
+
|
|
942
|
+
merger.add_ops(dst_band=1, src_img=0, src_band=0, factor=0.5)
|
|
943
|
+
merger.add_ops(dst_band=1, src_img=3, src_band=1, factor=0.5)
|
|
944
|
+
merger.add_ops(dst_band=0, src_img=2, src_band=1)
|
|
945
|
+
merger.add_ops(dst_band=2, src_img=1, src_band=2)
|
|
946
|
+
merger.add_ops(dst_band=3, src_img=1, src_band=1)
|
|
947
|
+
|
|
948
|
+
img_opts = ImageOptions("RGBA")
|
|
949
|
+
result = merger.merge([self.img0, self.img1, self.img2, self.img3], img_opts)
|
|
950
|
+
img = result.as_image()
|
|
951
|
+
|
|
952
|
+
assert img.getpixel((0, 0)) == (210, 127, 120, 110)
|
|
953
|
+
|
|
954
|
+
def test_rgba_merge_missing_a(self):
|
|
955
|
+
"""
|
|
956
|
+
Check that missing alpha band defaults to opaque
|
|
957
|
+
"""
|
|
958
|
+
merger = BandMerger(mode="RGBA")
|
|
959
|
+
|
|
960
|
+
merger.add_ops(dst_band=1, src_img=0, src_band=0, factor=0.5)
|
|
961
|
+
merger.add_ops(dst_band=1, src_img=3, src_band=1, factor=0.5)
|
|
962
|
+
merger.add_ops(dst_band=0, src_img=2, src_band=1)
|
|
963
|
+
merger.add_ops(dst_band=2, src_img=1, src_band=2)
|
|
964
|
+
|
|
965
|
+
img_opts = ImageOptions("RGBA")
|
|
966
|
+
result = merger.merge([self.img0, self.img1, self.img2, self.img3], img_opts)
|
|
967
|
+
img = result.as_image()
|
|
968
|
+
|
|
969
|
+
assert img.getpixel((0, 0)) == (210, 127, 120, 255)
|
|
970
|
+
|
|
971
|
+
def test_l_merge(self):
|
|
972
|
+
"""
|
|
973
|
+
Check merge bands to grayscale image
|
|
974
|
+
"""
|
|
975
|
+
merger = BandMerger(mode="L")
|
|
976
|
+
|
|
977
|
+
merger.add_ops(dst_band=0, src_img=0, src_band=2, factor=0.2)
|
|
978
|
+
merger.add_ops(dst_band=0, src_img=2, src_band=1, factor=0.3)
|
|
979
|
+
merger.add_ops(dst_band=0, src_img=3, src_band=1, factor=0.5)
|
|
980
|
+
|
|
981
|
+
img_opts = ImageOptions("L")
|
|
982
|
+
result = merger.merge([self.img0, self.img1, self.img2, self.img3], img_opts)
|
|
983
|
+
img = result.as_image()
|
|
984
|
+
|
|
985
|
+
assert img.getpixel((0, 0)) == int(20 * 0.2) + int(210 * 0.3) + int(255 * 0.5)
|
|
986
|
+
|
|
987
|
+
def test_p_merge(self):
|
|
988
|
+
"""
|
|
989
|
+
Check merge bands to paletted image
|
|
990
|
+
"""
|
|
991
|
+
merger = BandMerger(mode="RGB")
|
|
992
|
+
|
|
993
|
+
merger.add_ops(dst_band=1, src_img=0, src_band=0, factor=0.5)
|
|
994
|
+
merger.add_ops(dst_band=1, src_img=3, src_band=1, factor=0.5)
|
|
995
|
+
merger.add_ops(dst_band=0, src_img=2, src_band=1)
|
|
996
|
+
merger.add_ops(dst_band=2, src_img=1, src_band=2)
|
|
997
|
+
|
|
998
|
+
img_opts = ImageOptions(
|
|
999
|
+
"P", format="image/png", encoding_options={"quantizer": "mediancut"}
|
|
1000
|
+
)
|
|
1001
|
+
result = merger.merge([self.img0, self.img1, self.img2, self.img3], img_opts)
|
|
1002
|
+
|
|
1003
|
+
# need to encode to get conversion to P
|
|
1004
|
+
img = Image.open(result.as_buffer())
|
|
1005
|
+
|
|
1006
|
+
assert img.mode == "P"
|
|
1007
|
+
img = img.convert("RGB")
|
|
1008
|
+
assert img.getpixel((0, 0)) == (210, 127, 120)
|
|
1009
|
+
|
|
1010
|
+
def test_from_p_merge(self):
|
|
1011
|
+
"""
|
|
1012
|
+
Check merge bands from paletted image
|
|
1013
|
+
"""
|
|
1014
|
+
merger = BandMerger(mode="RGB")
|
|
1015
|
+
|
|
1016
|
+
merger.add_ops(dst_band=0, src_img=0, src_band=2)
|
|
1017
|
+
merger.add_ops(dst_band=1, src_img=0, src_band=1)
|
|
1018
|
+
merger.add_ops(dst_band=2, src_img=0, src_band=0)
|
|
1019
|
+
|
|
1020
|
+
img = Image.new("RGB", (10, 10), (0, 100, 200)).quantize(256)
|
|
1021
|
+
assert img.mode == "P"
|
|
1022
|
+
# src img is P but we can still access RGB bands
|
|
1023
|
+
src_img = ImageSource(img)
|
|
1024
|
+
|
|
1025
|
+
img_opts = ImageOptions("RGB")
|
|
1026
|
+
result = merger.merge([src_img], img_opts)
|
|
1027
|
+
|
|
1028
|
+
img = result.as_image()
|
|
1029
|
+
assert img.mode == "RGB"
|
|
1030
|
+
assert img.getpixel((0, 0)) == (200, 100, 0)
|
|
1031
|
+
|
|
1032
|
+
def test_from_mixed_merge(self):
|
|
1033
|
+
"""
|
|
1034
|
+
Check merge RGBA bands from image without alpha (mixed)
|
|
1035
|
+
"""
|
|
1036
|
+
merger = BandMerger(mode="RGBA")
|
|
1037
|
+
|
|
1038
|
+
merger.add_ops(dst_band=0, src_img=0, src_band=2)
|
|
1039
|
+
merger.add_ops(dst_band=1, src_img=0, src_band=1)
|
|
1040
|
+
merger.add_ops(dst_band=2, src_img=0, src_band=0)
|
|
1041
|
+
merger.add_ops(dst_band=3, src_img=0, src_band=3)
|
|
1042
|
+
|
|
1043
|
+
img = Image.new("RGB", (10, 10), (0, 100, 200))
|
|
1044
|
+
src_img = ImageSource(img)
|
|
1045
|
+
|
|
1046
|
+
img_opts = ImageOptions("RGBA")
|
|
1047
|
+
result = merger.merge([src_img], img_opts)
|
|
1048
|
+
|
|
1049
|
+
img = result.as_image()
|
|
1050
|
+
assert img.mode == "RGBA"
|
|
1051
|
+
assert img.getpixel((0, 0)) == (200, 100, 0, 255)
|