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) # 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