Source code for wolfhece.wolf_texture

"""
Author: HECE - University of Liege, Pierre Archambeau
Date: 2024

Copyright (c) 2024 University of Liege. All rights reserved.

This script and its content are protected by copyright law. Unauthorized
copying or distribution of this file, via any medium, is strictly prohibited.
"""

from PIL import Image, ImageFont, ImageOps
from PIL.PngImagePlugin import PngInfo

try:
    from OpenGL.GL import *
    from OpenGL.GLUT import *
except:
[docs] msg=_('Error importing OpenGL library')
msg+=_(' Python version : ' + sys.version) msg+=_(' Please check your version of opengl32.dll -- conflict may exist between different files present on your desktop') raise Exception(msg) from os.path import exists from io import BytesIO import math import numpy as np from .PyTranslate import _ from .PyWMS import getIGNFrance, getWalonmap from .textpillow import Font_Priority, Text_Image,Text_Infos from .drawing_obj import Element_To_Draw
[docs] class genericImagetexture(Element_To_Draw): """ Affichage d'une image en OpenGL via une texture """
[docs] name: str
[docs] idtexture: int
[docs] width: int
[docs] height: int
[docs] which: str
[docs] myImage: Image
def __init__(self, which: str, label: str, mapviewer, xmin, xmax, ymin, ymax, imageFile="", imageObj=None, transparent_color = None, tolerance:int = 3, replace_color = None) -> None: super().__init__(label, True, mapviewer, False) try: self.mapviewer.canvas.SetCurrent(mapviewer.context) except: logging.error(_('Opengl setcurrent -- Do you have a active canvas ?')) self.xmin = xmin self.xmax = xmax self.ymin = ymin self.ymax = ymax self.idtexture = (GLuint * 1)() self.idx = 'texture_{}'.format(self.idtexture) try: glGenTextures(1, self.idtexture) except: raise NameError( 'Opengl glGenTextures -- maybe a conflict with an existing opengl32.dll file - please rename the opengl32.dll in the libs directory and retry') self.which = which.lower() self.idx = label self.name = label self.imageFile = imageFile self.myImage = imageObj if imageFile != "": if exists(imageFile): try: self.myImage = Image.open(imageFile).convert('RGBA') except Exception as e: logging.warning(_('Error opening image file : ') + str(imageFile)) logging.info(_('Trying to open image file with increased limit of pixels')) Image.MAX_IMAGE_PIXELS = 10000000000 self.myImage = Image.open(imageFile).convert('RGBA') if self.myImage is not None: self.width = self.myImage.width self.height = self.myImage.height if transparent_color is not None: # replace the transparent color by a fully transparent pixel colors = np.asarray(self.myImage).copy() if tolerance == 0: ij = np.where(colors[:,:,:3] == transparent_color) else: ij = np.where(np.isclose(colors[:,:,:3], np.full((colors.shape[0],colors.shape[1],3), transparent_color), atol=tolerance)) # set the alpha channel to 0 for the pixels that are close to the transparent color colors[ij[0],ij[1],3] = 0 # colorize the pixels that are not transparent if replace_color is not None: ij = np.where(colors[:,:,3] > 0) colors[ij[0],ij[1],:] = replace_color # create a new image from the modified array self.myImage = Image.fromarray(colors) else: self.width = -99999 self.height = -99999 self.update_minmax() self.oldview = [self.xmin, self.xmax, self.ymin, self.ymax, self.width, self.height] self.load()
[docs] def unload(self): """ Unload the texture from memory """ self.mapviewer.canvas.SetCurrent(self.mapviewer.context) if self.idtexture is not None: glDeleteTextures(1, self.idtexture) if self.myImage is not None: del self.myImage
[docs] def load(self, imageFile=""): if self.width == -99999 or self.height == -99999: return if self.mapviewer.canvas.SetCurrent(self.mapviewer.context): mybytes: BytesIO if imageFile != "": if not exists(imageFile): return self.myImage = Image.open(imageFile).convert('RGBA') elif self.myImage is None: return glBindTexture(GL_TEXTURE_2D, self.idtexture[0]) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.myImage.width, self.myImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, self.myImage.tobytes()) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) glGenerateMipmap(GL_TEXTURE_2D) else: raise NameError( 'Opengl setcurrent -- maybe a conflict with an existing opengl32.dll file - please rename the opengl32.dll in the libs directory and retry')
[docs] def update_minmax(self): if self.myImage is None: return dx = self.xmax - self.xmin dy = self.ymax - self.ymin scale=dy/dx if int(scale*4) != int(float(self.height)/float(self.width)*4): scale = float(self.height)/float(self.width) self.ymax = self.ymin + dx *scale
[docs] def reload(self,xmin=-99999,xmax=-99999,ymin=-99999,ymax=-99999): if xmin !=-99999: self.xmin = xmin if xmax !=-99999: self.xmax = xmax if ymin !=-99999: self.ymin = ymin if ymax !=-99999: self.ymax = ymax self.update_minmax() self.newview = [self.xmin, self.xmax, self.ymin, self.ymax, self.width, self.height] if self.newview != self.oldview: self.load() self.oldview = self.newview
[docs] def plot(self, sx=None, sy=None, xmin=None, ymin=None, xmax=None, ymax=None, size=None): """ alias for paint""" self.paint()
[docs] def find_minmax(self,update=False): """ Generic function to find min and max spatial extent in data """ # Nothing to do, set during initialization phase pass
[docs] def paint(self): glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) glColor4f(1., 1., 1., 1.) glEnable(GL_TEXTURE_2D) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glBindTexture(GL_TEXTURE_2D, self.idtexture[0]) glBegin(GL_QUADS) glTexCoord2f(0.0, 0.0) glVertex2f(self.xmin, self.ymax) glTexCoord2f(1.0, 0.0) glVertex2f(self.xmax, self.ymax) glTexCoord2f(1.0, 1.0) glVertex2f(self.xmax, self.ymin) glTexCoord2f(0.0, 1.0) glVertex2f(self.xmin, self.ymin) glEnd() glDisable(GL_TEXTURE_2D) glDisable(GL_BLEND) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
[docs] class imagetexture(Element_To_Draw): """ Affichage d'une image, obtenue depuis un Web service, en OpenGL via une texture """
[docs] name: str
[docs] idtexture: int
[docs] width: int
[docs] height: int
[docs] which: str
[docs] category: str
[docs] subcategory: str
[docs] France: bool
[docs] epsg: str
def __init__(self, which: str, label: str, cat: str, subc: str, mapviewer, xmin, xmax, ymin, ymax, width=1000, height=1000, France=False, epsg='31370') -> None: super().__init__(label+cat+subc, plotted=False, mapviewer=mapviewer, need_for_wx=False) try: mapviewer.canvas.SetCurrent(mapviewer.context) except: logging.error(_('Opengl setcurrent -- Do you have a active canvas ?')) self.France = France self.epsg = epsg self.xmin = xmin self.xmax = xmax self.ymin = ymin self.ymax = ymax self.idtexture = (GLuint * 1)() self.idx = 'texture_{}'.format(self.idtexture) try: glGenTextures(1, self.idtexture) except: raise NameError( 'Opengl glGenTextures -- maybe a conflict with an existing opengl32.dll file - ' 'please rename the opengl32.dll in the libs directory and retry') self.width = width self.height = height self.which = which.lower() self.category = cat # .upper() self.name = label self.subcategory = subc # .upper() self.oldview = [self.xmin, self.xmax, self.ymin, self.ymax, self.width, self.height] self.load()
[docs] def load(self): if self.width == -99999 or self.height == -99999: return if self.mapviewer.canvas.SetCurrent(self.mapviewer.context): mybytes: BytesIO if self.France: mybytes = getIGNFrance(self.category, self.epsg, self.xmin, self.ymin, self.xmax, self.ymax, self.width, self.height, False) else: mybytes = getWalonmap(self.category + '/' + self.subcategory, self.xmin, self.ymin, self.xmax, self.ymax, self.width, self.height, False) image = Image.open(mybytes) glBindTexture(GL_TEXTURE_2D, self.idtexture[0]) if self.subcategory[:5] == 'ORTHO': glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.width, self.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.tobytes()) elif image.mode == 'RGB': glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.width, self.height, 0, GL_RGB, GL_UNSIGNED_BYTE, image.tobytes()) else: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.width, self.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.tobytes()) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) glGenerateMipmap(GL_TEXTURE_2D) else: raise NameError( 'Opengl setcurrent -- maybe a conflict with an existing opengl32.dll file - ' 'please rename the opengl32.dll in the libs directory and retry')
[docs] def reload(self): dx = self.mapviewer.xmax - self.mapviewer.xmin dy = self.mapviewer.ymax - self.mapviewer.ymin cx = self.mapviewer.mousex cy = self.mapviewer.mousey coeff = .5 self.xmin = cx - dx * coeff self.xmax = cx + dx * coeff self.ymin = cy - dy * coeff self.ymax = cy + dy * coeff self.width = self.mapviewer.canvaswidth * 2 * coeff self.height = self.mapviewer.canvasheight * 2 * coeff self.newview = [self.xmin, self.xmax, self.ymin, self.ymax, self.width, self.height] if self.newview != self.oldview: self.load() self.oldview = self.newview
[docs] def plot(self, sx=None, sy=None, xmin=None, ymin=None, xmax=None, ymax=None, size=None): """ alias for paint""" self.paint()
[docs] def find_minmax(self,update=False): """ Generic function to find min and max spatial extent in data """ # Nothing to do, set during initialization phase pass
[docs] def paint(self): glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) glColor4f(1., 1., 1., 1.) glEnable(GL_TEXTURE_2D) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glBindTexture(GL_TEXTURE_2D, self.idtexture[0]) glBegin(GL_QUADS) glTexCoord2f(0.0, 0.0) glVertex2f(self.xmin, self.ymax) glTexCoord2f(1.0, 0.0) glVertex2f(self.xmax, self.ymax) glTexCoord2f(1.0, 1.0) glVertex2f(self.xmax, self.ymin) glTexCoord2f(0.0, 1.0) glVertex2f(self.xmin, self.ymin) glEnd() glDisable(GL_TEXTURE_2D) glDisable(GL_BLEND) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
[docs] def check_plot(self): self.plotted = True
[docs] def uncheck_plot(self, unload=True): self.plotted = False
[docs] class Text_Image_Texture(genericImagetexture): def __init__(self, text: str, mapviewer, proptext:Text_Infos, vector, x:float, y:float) -> None: """Gestion d'un texte sous forme de texture OpenGL Args: text (str): texte à afficher mapviewer (wolf_mapviewer): objet parent sur lequel dessiner proptext (Text_Infos): infos sur la mise en forme vector (vector): vecteur associé au texte x (float): point d'accroche X y (float): point d'accroche Y """ self.x = x self.y = y self.vector = vector self.proptext = proptext self.mapviewer = mapviewer self.findscale() self.proptext.findsize(text) xmin, xmax, ymin, ymax = proptext.getminmax(self.x,self.y) super().__init__('other', text, mapviewer, xmin, xmax, ymin, ymax) if self.myImage is not None: self.width = self.myImage.width self.height = self.myImage.height self.oldview = [self.xmin, self.xmax, self.ymin, self.ymax, self.width, self.height]
[docs] def findscale(self): self.proptext.setscale(self.mapviewer.sx, self.mapviewer.sy)
[docs] def load(self, imageFile=""): if self.mapviewer.canvas.SetCurrent(self.mapviewer.context): if imageFile != "": if not exists(imageFile): return self.myImage = Image.open(imageFile).convert('RGBA') else: self.myImage = Text_Image(self.name, self.proptext).image if self.myImage is None: return glEnable(GL_TEXTURE_2D) glPixelStorei(GL_UNPACK_ALIGNMENT, 1) glBindTexture(GL_TEXTURE_2D, self.idtexture[0]) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.myImage.width, self.myImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, self.myImage.transpose(Image.FLIP_TOP_BOTTOM).tobytes()) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) glGenerateMipmap(GL_TEXTURE_2D) glBindTexture(GL_TEXTURE_2D, 0) glDisable(GL_TEXTURE_2D) else: raise NameError( 'Opengl setcurrent -- maybe a conflict with an existing opengl32.dll file - please rename the opengl32.dll in the libs directory and retry')
[docs] def paint(self): self.findscale() self.proptext.setsize_real() if self.proptext.adapt_fontsize(self.name): self.update_image() x,y = self.proptext.getcorners(self.x,self.y) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) glColor4f(1., 1., 1., 1.) glEnable(GL_TEXTURE_2D) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) # glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) glBindTexture(GL_TEXTURE_2D, self.idtexture[0]) glBegin(GL_QUADS) glTexCoord2f(0.0, 0.0) glVertex2f(x[0], y[0]) glTexCoord2f(1.0, 0.0) glVertex2f(x[1], y[1]) glTexCoord2f(1.0, 1.0) glVertex2f(x[2], y[2]) glTexCoord2f(0.0, 1.0) glVertex2f(x[3], y[3]) glEnd() glBindTexture(GL_TEXTURE_2D, 0) glDisable(GL_TEXTURE_2D) glDisable(GL_BLEND) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
[docs] def update_image(self, newtext:str="", proptext:Text_Infos=None): if newtext !="": self.name = newtext if proptext is not None: self.proptext = proptext self.load()