265 lines
10 KiB
Python
265 lines
10 KiB
Python
|
|
import os
|
|
from PIL import Image, ImageFilter, ImageEnhance
|
|
from defines import *
|
|
from layergen import *
|
|
from log import *
|
|
from functions import *
|
|
from xp_normalmap import *
|
|
|
|
# -------------------------------------------------------------------
|
|
# ORTHOGRAPHIC
|
|
# Your personal aerial satellite. Always on. At any altitude.*
|
|
# Developed by MarStrMind
|
|
# License: Open Software License 3.0
|
|
# Up to date version always on marstr.online
|
|
# -------------------------------------------------------------------
|
|
# photogen.py
|
|
# The class that generates the photo tiles from previous layers,
|
|
# in their correct order.
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
class mstr_photogen:
|
|
|
|
# Initializer doesn't need much
|
|
def __init__ (self, lat, lng, ty, tx, maxlat, maxlng):
|
|
self._lat = lat
|
|
self._lng = lng
|
|
self._ty = ty
|
|
self._tx = tx
|
|
self._maxlatlng = [ maxlat, maxlng ]
|
|
# Define layer size depending on what is wanted
|
|
self._imgsize = mstr_photores
|
|
# Empty image where everything goes into
|
|
self._tile = Image.new("RGBA", (self._imgsize, self._imgsize))
|
|
self._latlngfld = self.latlng_folder([lat,lng])
|
|
mstr_msg("photogen", "Photogen initialized")
|
|
|
|
|
|
# This puts it all together. Bonus: AND saves it.
|
|
def genphoto(self, layers, waterlayers):
|
|
# Template for the file name which is always the same
|
|
#root_filename = mstr_datafolder + "/_cache/" + str(self._lat) + "-" + str(self._ty) + "_" + str(self._lng) + "-" + str(self._tx) + "_"
|
|
|
|
# First, we walk through all layers and blend them on top of each other, in order
|
|
mstr_msg("photogen", "Merging layers")
|
|
|
|
for l in layers:
|
|
self._tile.alpha_composite(l)
|
|
|
|
|
|
# When we have run through this loop, we will end up with a sandwiched
|
|
# image of all the other images, in their correct order.
|
|
# However, since I have discovered that some areas in OSM simply do not
|
|
# have any tag or information, it is possible that the final image will
|
|
# have empty, alpha-transparent patches.
|
|
# For this reason we need to check against these and fix that.
|
|
|
|
# First, we will check if there is something to fix:
|
|
emptyspace = self.checkForEmptySpace()
|
|
mstr_msg("photogen", "Checked for empty patches")
|
|
|
|
# If this check comes back as true, we need to perform
|
|
# aforementioned fix:
|
|
if emptyspace == True:
|
|
|
|
mstr_msg("photogen", "Patching empty space")
|
|
mask = self.buildCompletionMask()
|
|
|
|
# Load the mask
|
|
mask_px = mask.load()
|
|
|
|
cmpl = Image.new("RGBA", (self._imgsize, self._imgsize))
|
|
cmp_px = cmpl.load()
|
|
|
|
edn = self.find_earthnavdata_number()
|
|
edns = self.latlng_folder(edn)
|
|
idx = 0
|
|
for r in mstr_completion_colors:
|
|
if r[0] == edns:
|
|
break
|
|
else:
|
|
idx = idx+1
|
|
|
|
for y in range(self._imgsize):
|
|
for x in range(self._imgsize):
|
|
p = mask_px[x,y]
|
|
if p[3] > 0:
|
|
cidx = randrange(0, len(mstr_completion_colors[idx][1])-1)
|
|
clr = mstr_completion_colors[idx][1][cidx]
|
|
cmp_px[x,y] = (clr[0], clr[1], clr[2], 255)
|
|
|
|
# Some features
|
|
patches = glob.glob(mstr_datafolder + "textures/tile/completion/*.png")
|
|
|
|
# Pick an amount of features to add
|
|
patch_amt = randrange(1, 7)
|
|
|
|
# Add those somewhere
|
|
for p in range(1, patch_amt+1):
|
|
# Load some patch
|
|
ptc = Image.open(mstr_datafolder + "textures/tile/completion/p" + str(randrange(1, len(patches)+1)) + ".png")
|
|
# Rotate it
|
|
ptc = ptc.rotate(randrange(0, 360), expand=True)
|
|
|
|
# Make sure ortho generation does not crash
|
|
if ptc.width >= mstr_photores:
|
|
ptc = ptc.resize((1536, 1536), Image.Resampling.BILINEAR)
|
|
|
|
# Adjust alpha on this image
|
|
ptc_p = ptc.load()
|
|
for y in range(ptc.height):
|
|
for x in range(ptc.width):
|
|
c = ptc_p[x,y]
|
|
if c[3] > 0:
|
|
na = c[3] - 160
|
|
if na < 0: na = 0
|
|
nc = (c[0], c[1], c[2], na)
|
|
ptc_p[x,y] = nc
|
|
|
|
# Find a location INSIDE the image!
|
|
px = randrange(1, randrange(self._imgsize - ptc.width - 1))
|
|
py = randrange(1, randrange(self._imgsize - ptc.height - 1))
|
|
|
|
# Add it to the completion image
|
|
cmpl.alpha_composite(ptc, dest=(px,py))
|
|
|
|
# Merge the images
|
|
cmpl.alpha_composite(self._tile)
|
|
|
|
# Make this the real one
|
|
self._tile = cmpl
|
|
|
|
|
|
# There may be some tiles that have a larger sea or even an ocean in them - these need to be
|
|
# removed from the final tile
|
|
ocean_pix = self._tile.load()
|
|
for y in range(self._tile.width):
|
|
for x in range(self._tile.height):
|
|
p = ocean_pix[x,y]
|
|
if p[0] == 255 and p[1] == 0 and p[2] == 255:
|
|
t = (0,0,0,0)
|
|
ocean_pix[x,y] = t
|
|
|
|
# Alpha correction on final image
|
|
corrpix = self._tile.load()
|
|
for y in range(0, self._tile.height):
|
|
for x in range(0, self._tile.width):
|
|
c = corrpix[x,y]
|
|
if c[3] > 0:
|
|
nc = (c[0], c[1], c[2], 255)
|
|
corrpix[x,y] = nc
|
|
if c[3] == 0:
|
|
corrpix[x,y] = (0,0,0,0)
|
|
|
|
# We are now in posession of the final image.
|
|
|
|
# Contrast
|
|
self._tile = ImageEnhance.Contrast(self._tile).enhance(1)
|
|
|
|
# This we can save accordingly.
|
|
self._tile.save(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) + ".png")
|
|
|
|
# Now we convert this into a DDS
|
|
_tmpfn = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx)
|
|
os.system(mstr_xp_ddstool + " --png2dxt1 " + _tmpfn + ".png " + _tmpfn + ".dds" )
|
|
|
|
os.remove(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) + ".png")
|
|
|
|
|
|
# Now generate the normal map for this ortho.
|
|
# But only if this is enabled.
|
|
if mstr_xp_genscenery and mstr_xp_scn_normalmaps:
|
|
# Generate the normal normal map first (hah)
|
|
nrm = mstr_xp_normalmap()
|
|
nrmimg = nrm.generate_normal_map_for_layer(self._tile, False)
|
|
|
|
# Now we need to walk through the water layers and generate a combined normal map
|
|
wtrlyr = Image.new("RGBA", (self._imgsize, self._imgsize))
|
|
for w in waterlayers:
|
|
wtrlyr.alpha_composite(w)
|
|
wtrlyr = wtrlyr.resize((int(mstr_photores/4), int(mstr_photores/4)), Image.Resampling.BILINEAR)
|
|
wtrimg = nrm.generate_normal_map_for_layer(wtrlyr, True)
|
|
|
|
# Blend
|
|
nrmimg.alpha_composite(wtrimg)
|
|
|
|
# Save
|
|
nrmfln = mstr_datafolder + "z_orthographic/normals/" + self._latlngfld + "/" + str(self._ty) + "_" + str(
|
|
self._tx) + ".png"
|
|
nrmimg.save(nrmfln)
|
|
|
|
|
|
|
|
|
|
# This checks the final image for empty patches. Should one be
|
|
# found, we will generate something to fill the gap. If this is
|
|
# the case, we will also note this in the database for the tile,
|
|
# under the special tag and value "tile", "completion". The same
|
|
# conditions apply for edge testing and so on.
|
|
def checkForEmptySpace(self):
|
|
empty = False
|
|
|
|
# Load photo
|
|
layer_pix = self._tile.load()
|
|
|
|
# Scan!
|
|
for y in range(self._tile.width-1):
|
|
for x in range(self._tile.height-1):
|
|
p = layer_pix[x,y]
|
|
if p[3] < 255: # <- Check for empty or non-complete alpha
|
|
empty = True
|
|
break
|
|
|
|
# Tell about findings
|
|
return empty
|
|
|
|
|
|
# This returns a mask of the empty space to cover, should there be any
|
|
def buildCompletionMask(self):
|
|
mask = Image.new("RGBA", (self._imgsize, self._imgsize), (0,0,0,0))
|
|
mask_pix = mask.load()
|
|
|
|
# Load photo
|
|
layer_pix = self._tile.load()
|
|
|
|
# Scan!
|
|
for y in range(self._tile.width-1):
|
|
for x in range(self._tile.height-1):
|
|
p = layer_pix[x,y]
|
|
if p[3] < 255: # <- Check for empty or non-complete alpha
|
|
mask_pix[x,y] = (0,0,0,255)
|
|
# We do not apply any blur or other effects here - we only want the
|
|
# exact pixel positions.
|
|
|
|
#mask.save( mstr_datafolder + "_cache/" + str(self._lat) + "-" + str(self._ty) + "_" + str(self._lng) + "-" + str(self._tx) + "_tile-completion.png" )
|
|
mstr_msg("photogen", "Generated and saved empty space mask")
|
|
return mask
|
|
|
|
|
|
# Construct a folder name for latitude and longitude
|
|
def latlng_folder(self, numbers):
|
|
fstr = ""
|
|
if numbers[0] >= 0: fstr = "+"
|
|
if numbers[0] < 0: fstr = "-"
|
|
if abs(numbers[0]) < 10: fstr = fstr + "0" + str(numbers[0])
|
|
if abs(numbers[0]) >= 10 and numbers[0] <= 90: fstr = fstr + str(numbers[0])
|
|
|
|
if numbers[1] >= 0: fstr = fstr + "+"
|
|
if numbers[1] < 0: fstr = fstr + "-"
|
|
if abs(numbers[1]) < 10: fstr = fstr + "00" + str(numbers[1])
|
|
if abs(numbers[1]) >= 10 and numbers[0] <= 99: fstr = fstr + "0" + str(numbers[1])
|
|
if abs(numbers[1]) >= 100 : fstr = fstr + str(numbers[1])
|
|
|
|
return fstr
|
|
|
|
|
|
# Find the next "by-ten" numbers for the current latitude and longitude
|
|
def find_earthnavdata_number(self):
|
|
earthnavdata = []
|
|
lat = abs(int(self._lat / 10) * 10)
|
|
lng = abs(int(self._lng / 10) * 10)
|
|
earthnavdata.append(lat)
|
|
earthnavdata.append(lng)
|
|
return earthnavdata |