diff options
Diffstat (limited to 'libs/dds_header/src/DDSPreview.py')
| -rw-r--r-- | libs/dds_header/src/DDSPreview.py | 528 |
1 files changed, 0 insertions, 528 deletions
diff --git a/libs/dds_header/src/DDSPreview.py b/libs/dds_header/src/DDSPreview.py deleted file mode 100644 index 78ffffc..0000000 --- a/libs/dds_header/src/DDSPreview.py +++ /dev/null @@ -1,528 +0,0 @@ -import struct -import sys -import threading -import enum - -from PyQt6.QtCore import QCoreApplication, qDebug, Qt, QSize -from PyQt6.QtGui import QColor, QOpenGLContext, QSurfaceFormat, QMatrix4x4, QVector4D -from PyQt6.QtOpenGLWidgets import QOpenGLWidget -from PyQt6.QtWidgets import QGridLayout, QLabel, QPushButton, QWidget, QColorDialog, QComboBox -from PyQt6.QtOpenGL import QOpenGLBuffer, QOpenGLDebugLogger, QOpenGLShader, QOpenGLShaderProgram, QOpenGLTexture, \ - QOpenGLVersionProfile, QOpenGLVertexArrayObject, QOpenGLFunctions_4_1_Core, QOpenGLVersionFunctionsFactory - -from DDS.DDSFile import DDSFile - -if "mobase" not in sys.modules: - import mobase - -vertexShader2D = """ -#version 150 - -uniform float aspectRatioRatio; - -in vec4 position; -in vec2 texCoordIn; - -out vec2 texCoord; - -void main() -{ - texCoord = texCoordIn; - gl_Position = position; - if (aspectRatioRatio >= 1.0) - gl_Position.y /= aspectRatioRatio; - else - gl_Position.x *= aspectRatioRatio; -} -""" - -vertexShaderCube = """ -#version 150 - -uniform float aspectRatioRatio; - -in vec4 position; -in vec2 texCoordIn; - -out vec2 texCoord; - -void main() -{ - texCoord = texCoordIn; - gl_Position = position; -} -""" - -fragmentShaderFloat = """ -#version 150 - -uniform sampler2D aTexture; -uniform mat4 channelMatrix; -uniform vec4 channelOffset; - -in vec2 texCoord; - -void main() -{ - gl_FragData[0] = channelMatrix * texture(aTexture, texCoord) + channelOffset; -} -""" - -fragmentShaderUInt = """ -#version 150 - -uniform usampler2D aTexture; -uniform mat4 channelMatrix; -uniform vec4 channelOffset; - -in vec2 texCoord; - -void main() -{ - // autofilled alpha is 1, so if we have a scaling factor, we need separate ones for luminance and alpha - gl_FragData[0] = channelMatrix * texture(aTexture, texCoord) + channelOffset; -} -""" - -fragmentShaderSInt = """ -#version 150 - -uniform isampler2D aTexture; -uniform mat4 channelMatrix; -uniform vec4 channelOffset; - -in vec2 texCoord; - -void main() -{ - // autofilled alpha is 1, so if we have a scaling factor and offset, we need separate ones for luminance and alpha - gl_FragData[0] = channelMatrix * texture(aTexture, texCoord) + channelOffset; -} -""" - -fragmentShaderCube = """ -#version 150 - -uniform samplerCube aTexture; -uniform mat4 channelMatrix; -uniform vec4 channelOffset; - -in vec2 texCoord; - -const float PI = 3.1415926535897932384626433832795; - -void main() -{ - float theta = -2.0 * PI * texCoord.x; - float phi = PI * texCoord.y; - gl_FragData[0] = channelMatrix * texture(aTexture, vec3(sin(theta) * sin(phi), cos(theta) * sin(phi), cos(phi))) + channelOffset; -} -""" - -transparencyVS = """ -#version 150 - -in vec4 position; - -void main() -{ - gl_Position = position; -} -""" - -transparencyFS = """ -#version 150 - -uniform vec4 backgroundColour; - -void main() -{ - float x = gl_FragCoord.x; - float y = gl_FragCoord.y; - x = mod(x, 16.0); - y = mod(y, 16.0); - gl_FragData[0] = x < 8.0 ^^ y < 8.0 ? vec4(vec3(191.0/255.0), 1.0) : vec4(1.0); - gl_FragData[0].rgb = backgroundColour.rgb * backgroundColour.a + gl_FragData[0].rgb * (1.0 - backgroundColour.a); -} -""" - -vertices = [ - # vertex coordinates texture coordinates - -1.0, -1.0, 0.5, 1.0, 0.0, 1.0, - -1.0, 1.0, 0.5, 1.0, 0.0, 0.0, - 1.0, 1.0, 0.5, 1.0, 1.0, 0.0, - - -1.0, -1.0, 0.5, 1.0, 0.0, 1.0, - 1.0, 1.0, 0.5, 1.0, 1.0, 0.0, - 1.0, -1.0, 0.5, 1.0, 1.0, 1.0, -] - - -class DDSOptions: - def __init__(self, colour: QColor = QColor(0, 0, 0, 0), channelMatrix: QMatrix4x4 = QMatrix4x4(), - channelOffset: QVector4D = QVector4D()): - # QMatrix4x4 with no arguments is the identity matrix, so no channel transformations - # declare member variables with None values - self.backgroundColour = None - self.channelMatrix = None - self.channelOffset = None - # initialize member variables with error checks - self.setBackgroundColour(colour) - self.setChannelMatrix(channelMatrix) - self.setChannelOffset(channelOffset) - - def setBackgroundColour(self, colour: QColor): - if isinstance(colour, QColor) and colour.isValid(): - self.backgroundColour = colour - else: - raise TypeError(str(colour) + " is not a valid QColor object.") - - def getBackgroundColour(self) -> QColor: - return self.backgroundColour - - def getChannelMatrix(self) -> QMatrix4x4: - return self.channelMatrix - - def setChannelMatrix(self, matrix): - self.channelMatrix = QMatrix4x4(matrix) - - def getChannelOffset(self) -> QVector4D: - return self.channelOffset - - def setChannelOffset(self, vector): - self.channelOffset = QVector4D(vector) - - -glVersionProfile = QOpenGLVersionProfile() -glVersionProfile.setVersion(2, 1) - - -class DDSWidget(QOpenGLWidget): - def __init__(self, ddsFile, ddsOptions=DDSOptions(), debugContext=False, parent=None, f=Qt.WindowType(0)): - super(DDSWidget, self).__init__(parent, f) - - self.ddsFile = ddsFile - - self.ddsOptions = ddsOptions - - self.clean = True - - self.logger = None - - self.program = None - self.transparecyProgram = None - self.texture = None - self.vbo = None - self.vao = None - - if debugContext: - format = QSurfaceFormat() - format.setOption(QSurfaceFormat.FormatOption.DebugContext) - self.setFormat(format) - self.logger = QOpenGLDebugLogger(self) - - def __del__(self): - self.cleanup() - - def __dtor__(self): - self.cleanup() - - def initializeGL(self): - if self.logger: - self.logger.initialize() - self.logger.messageLogged.connect( - lambda message: qDebug(self.tr("OpenGL debug message: {0}").format(message.message()))) - self.logger.startLogging() - - gl = QOpenGLVersionFunctionsFactory.get(glVersionProfile) - QOpenGLContext.currentContext().aboutToBeDestroyed.connect(self.cleanup) - - self.clean = False - - fragmentShader = None - vertexShader = vertexShader2D - if self.ddsFile.isCubemap: - fragmentShader = fragmentShaderCube - vertexShader = vertexShaderCube - if QOpenGLContext.currentContext().hasExtension(b"GL_ARB_seamless_cube_map"): - GL_TEXTURE_CUBE_MAP_SEAMLESS = 0x884F - gl.glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS) - elif self.ddsFile.glFormat.samplerType == "F": - fragmentShader = fragmentShaderFloat - elif self.ddsFile.glFormat.samplerType == "UI": - fragmentShader = fragmentShaderUInt - else: - fragmentShader = fragmentShaderSInt - - self.program = QOpenGLShaderProgram(self) - self.program.addShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Vertex, vertexShader) - self.program.addShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Fragment, fragmentShader) - self.program.bindAttributeLocation("position", 0) - self.program.bindAttributeLocation("texCoordIn", 1) - self.program.link() - - self.transparecyProgram = QOpenGLShaderProgram(self) - self.transparecyProgram.addShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Vertex, transparencyVS) - self.transparecyProgram.addShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Fragment, transparencyFS) - self.transparecyProgram.bindAttributeLocation("position", 0) - self.transparecyProgram.link() - - self.vao = QOpenGLVertexArrayObject(self) - vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao) - - self.vbo = QOpenGLBuffer(QOpenGLBuffer.Type.VertexBuffer) - self.vbo.create() - self.vbo.bind() - - theBytes = struct.pack("%sf" % len(vertices), *vertices) - self.vbo.allocate(theBytes, len(theBytes)) - - gl.glEnableVertexAttribArray(0) - gl.glEnableVertexAttribArray(1) - gl.glVertexAttribPointer(0, 4, gl.GL_FLOAT, False, 6 * 4, 0) - gl.glVertexAttribPointer(1, 2, gl.GL_FLOAT, False, 6 * 4, 4 * 4) - - self.texture = self.ddsFile.asQOpenGLTexture(gl, QOpenGLContext.currentContext()) - - def resizeGL(self, w, h): - aspectRatioTex = self.texture.width() / self.texture.height() if self.texture else 1.0 - aspectRatioWidget = w / h - ratioRatio = aspectRatioTex / aspectRatioWidget - - self.program.bind() - self.program.setUniformValue("aspectRatioRatio", ratioRatio) - self.program.release() - - def paintGL(self): - gl = QOpenGLVersionFunctionsFactory.get(glVersionProfile) - - vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao) - - # Draw checkerboard so transparency is obvious - self.transparecyProgram.bind() - - backgroundColour = self.ddsOptions.getBackgroundColour() - if backgroundColour and backgroundColour.isValid(): - self.transparecyProgram.setUniformValue("backgroundColour", backgroundColour) - - gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6) - - self.transparecyProgram.release() - - self.program.bind() - - if self.texture: - self.texture.bind() - - gl.glEnable(gl.GL_BLEND) - gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) - - self.program.setUniformValue("channelMatrix", self.ddsOptions.getChannelMatrix()) - self.program.setUniformValue("channelOffset", self.ddsOptions.getChannelOffset()) - - gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6) - - if self.texture: - self.texture.release() - self.program.release() - - def cleanup(self): - if not self.clean: - self.makeCurrent() - - self.program = None - self.transparecyProgram = None - if self.texture: - self.texture.destroy() - self.texture = None - self.vbo.destroy() - self.vbo = None - self.vao.destroy() - self.vao = None - - self.doneCurrent() - self.clean = True - - def tr(self, str): - return QCoreApplication.translate("DDSWidget", str) - - -class ColourChannels(enum.Enum): - RGBA = "Colour and Alpha" - RGB = "Colour" - A = "Alpha" - R = "Red" - G = "Green" - B = "Blue" - - -class DDSChannelManager: - def __init__(self, channels: ColourChannels): - self.channels = channels - - def setChannels(self, options: DDSOptions, channels: ColourChannels): - self.channels = channels - - def drawColour(alpha: bool): - colorMatrix = QMatrix4x4() - colorOffset = QVector4D() - if not alpha: - colorMatrix[3, 3] = 0 - colorOffset.setW(1.0) - options.setChannelMatrix(colorMatrix) - options.setChannelOffset(colorOffset) - - def drawGrayscale(channel: ColourChannels): - colorOffset = QVector4D(0, 0, 0, 1) - channelVector = [0, 0, 0, 0] - if channels == ColourChannels.R: - channelVector[0] = 1 - elif channel == ColourChannels.G: - channelVector[1] = 1 - elif channel == ColourChannels.B: - channelVector[2] = 1 - elif channels == ColourChannels.A: - channelVector[3] = 1 - else: - raise ValueError("channel must be a single color channel.") - alphaVector = [0, 0, 0, 0] - colorMatrix = channelVector * 3 + alphaVector - options.setChannelMatrix(colorMatrix) - options.setChannelOffset(colorOffset) - - if channels == ColourChannels.RGBA: - drawColour(True) - elif channels == ColourChannels.RGB: - drawColour(False) - else: - drawGrayscale(channels) - - -class DDSPreview(mobase.IPluginPreview): - - def __init__(self): - super().__init__() - self.__organizer = None - self.options = None - self.channelManager = None - - def init(self, organizer: mobase.IOrganizer): - self.__organizer = organizer - savedColour = QColor(self.pluginSetting("background r"), self.pluginSetting("background g"), - self.pluginSetting("background b"), self.pluginSetting("background a")) - try: - savedChannels = ColourChannels[self.pluginSetting("channels")] - except KeyError: - savedChannels = ColourChannels.RGBA - self.options = DDSOptions(savedColour) - self.channelManager = DDSChannelManager(savedChannels) - self.channelManager.setChannels(self.options, savedChannels) - return True - - def pluginSetting(self, name): - return self.__organizer.pluginSetting(self.name(), name) - - def setPluginSetting(self, name, value): - self.__organizer.setPluginSetting(self.name(), name, value) - - def name(self): - return "DDS Preview Plugin" - - def author(self): - return "AnyOldName3" - - def description(self): - return self.tr("Lets you preview DDS files by actually uploading them to the GPU.") - - def version(self): - return mobase.VersionInfo(1, 0, 1, 0) - - def settings(self): - return [mobase.PluginSetting("log gl errors", self.tr( - "If enabled, log OpenGL errors and debug messages. May decrease performance."), False), - mobase.PluginSetting("background r", self.tr("Red channel of background colour"), 0), - mobase.PluginSetting("background g", self.tr("Green channel of background colour"), 0), - mobase.PluginSetting("background b", self.tr("Blue channel of background colour"), 0), - mobase.PluginSetting("background a", self.tr("Alpha channel of background colour"), 0), - mobase.PluginSetting("channels", self.tr("The colour channels that are displayed."), - ColourChannels.RGBA.name)] - - def supportedExtensions(self) -> set[str]: - return {"dds"} - - def supportsArchives(self) -> bool: - return True - - def genFilePreview(self, fileName: str, maxSize: QSize) -> QWidget: - return self.previewFromDDSFile(DDSFile.fromFile(fileName)) - - def genDataPreview(self, fileData: bytes, fileName: str, maxSize: QSize) -> QWidget: - return self.previewFromDDSFile(DDSFile(fileData, fileName)) - - def previewFromDDSFile(self, ddsFile: DDSFile) -> QWidget: - ddsFile.load() - layout = QGridLayout() - # Image grows before label and button - layout.setRowStretch(0, 1) - # Label grows before button - layout.setColumnStretch(0, 1) - layout.addWidget(self.__makeLabel(ddsFile), 1, 0, 1, 1) - - ddsWidget = DDSWidget(ddsFile, self.options, self.__organizer.pluginSetting(self.name(), "log gl errors")) - layout.addWidget(ddsWidget, 0, 0, 1, 3) - - layout.addWidget(self.__makeColourButton(ddsWidget), 1, 2, 1, 1) - layout.addWidget(self.__makeChannelsButton(ddsWidget), 1, 1, 1, 1) - - widget = QWidget() - widget.setLayout(layout) - return widget - - def tr(self, str): - return QCoreApplication.translate("DDSPreview", str) - - def __makeLabel(self, ddsFile): - label = QLabel(ddsFile.getDescription()) - label.setWordWrap(True) - label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) - return label - - def __makeColourButton(self, ddsWidget): - button = QPushButton(self.tr("Pick background colour")) - - def pickColour(unused): - newColour = QColorDialog.getColor(self.options.getBackgroundColour(), button, "Background colour", - QColorDialog.ColorDialogOption.ShowAlphaChannel) - if newColour.isValid(): - self.setPluginSetting("background r", newColour.red()) - self.setPluginSetting("background g", newColour.green()) - self.setPluginSetting("background b", newColour.blue()) - self.setPluginSetting("background a", newColour.alpha()) - self.options.setBackgroundColour(newColour) - ddsWidget.update() - - button.clicked.connect(pickColour) - return button - - def __makeChannelsButton(self, ddsWidget): - listwidget = QComboBox() - channelKeys = [e.name for e in ColourChannels] - channelNames = [e.value for e in ColourChannels] - - listwidget.addItems(channelNames) - listwidget.setCurrentText(self.channelManager.channels.value) - listwidget.setToolTip(self.tr("Select what colour channels are displayed.")) - - listwidget.showEvent = lambda _: listwidget.setCurrentText(self.channelManager.channels.value) - - def onChanged(newIndex): - self.channelManager.setChannels(self.options, ColourChannels[channelKeys[newIndex]]) - self.setPluginSetting("channels", self.channelManager.channels.name) - ddsWidget.update() - - listwidget.currentIndexChanged.connect(onChanged) - return listwidget - - -def createPlugin(): - return DDSPreview() |
