From 3e11cbf549b4836b77df05c6672732ad1c409542 Mon Sep 17 00:00:00 2001 From: Jampi0n <27952849+Jampi0n@users.noreply.github.com> Date: Wed, 22 Jun 2022 22:43:36 +0200 Subject: [PATCH 1/2] Add transparency toggle --- src/DDSPreview.py | 78 ++++++++++++++++++++++++++++---------------- src/DDSPreview_en.ts | 37 ++++++++++----------- 2 files changed, 67 insertions(+), 48 deletions(-) diff --git a/src/DDSPreview.py b/src/DDSPreview.py index ba9fda0..65dcb46 100644 --- a/src/DDSPreview.py +++ b/src/DDSPreview.py @@ -153,9 +153,10 @@ class DDSWidget(QOpenGLWidget): - def __init__(self, ddsFile, debugContext=False, parent=None, flags=Qt.WindowType(0)): + def __init__(self, ddsPreview, ddsFile, debugContext=False, parent=None, flags=Qt.WindowType(0)): super(DDSWidget, self).__init__(parent, flags=flags) + self.ddsPreview = ddsPreview self.ddsFile = ddsFile self.clean = True @@ -168,7 +169,6 @@ def __init__(self, ddsFile, debugContext=False, parent=None, flags=Qt.WindowType self.vbo = None self.vao = None - self.backgroundColour = None if debugContext: format = QSurfaceFormat() @@ -256,8 +256,9 @@ def paintGL(self): # Draw checkerboard so transparency is obvious self.transparecyProgram.bind() - if self.backgroundColour and self.backgroundColour.isValid(): - self.transparecyProgram.setUniformValue("backgroundColour", self.backgroundColour) + backgroundColour = self.ddsPreview.getBackgroundColour() + if backgroundColour and backgroundColour.isValid(): + self.transparecyProgram.setUniformValue("backgroundColour", backgroundColour) gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6) @@ -268,8 +269,11 @@ def paintGL(self): if self.texture: self.texture.bind() - gl.glEnable(gl.GL_BLEND) - gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) + if self.ddsPreview.getTransparency(): + gl.glEnable(gl.GL_BLEND) + gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) + else: + gl.glDisable(gl.GL_BLEND) gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6) @@ -294,12 +298,6 @@ def cleanup(self): self.doneCurrent() self.clean = True - def setBackgroundColour(self, colour): - self.backgroundColour = colour - - def getBackgroundColour(self): - return self.backgroundColour - def tr(self, str): return QCoreApplication.translate("DDSWidget", str) @@ -314,6 +312,12 @@ def init(self, organizer): self.__organizer = organizer 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" @@ -332,7 +336,8 @@ def settings(self): 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("background a", self.tr("Alpha channel of background colour"), 0), + mobase.PluginSetting("transparency", self.tr("If enabled, transparency will be displayed."), True)] def supportedExtensions(self): return {"dds"} @@ -347,10 +352,11 @@ def genFilePreview(self, fileName, maxSize): layout.setColumnStretch(0, 1) layout.addWidget(self.__makeLabel(ddsFile), 1, 0, 1, 1) - ddsWidget = DDSWidget(ddsFile, self.__organizer.pluginSetting(self.name(), "log gl errors")) - layout.addWidget(ddsWidget, 0, 0, 1, 2) + ddsWidget = DDSWidget(self, ddsFile, self.__organizer.pluginSetting(self.name(), "log gl errors")) + layout.addWidget(ddsWidget, 0, 0, 1, 3) - layout.addWidget(self.__makeColourButton(ddsWidget), 1, 1, 1, 1) + layout.addWidget(self.__makeColourButton(ddsWidget), 1, 2, 1, 1) + layout.addWidget(self.__makeToggleTransparencyButton(ddsWidget), 1, 1, 1, 1) widget = QWidget() widget.setLayout(layout) @@ -367,25 +373,41 @@ def __makeLabel(self, ddsFile): def __makeColourButton(self, ddsWidget): button = QPushButton(self.tr("Pick background colour")) - savedColour = QColor(self.__organizer.pluginSetting(self.name(), "background r"), - self.__organizer.pluginSetting(self.name(), "background g"), - self.__organizer.pluginSetting(self.name(), "background b"), - self.__organizer.pluginSetting(self.name(), "background a")) - ddsWidget.setBackgroundColour(savedColour) def pickColour(unused): - newColour = QColorDialog.getColor(ddsWidget.getBackgroundColour(), button, "Background colour", - QColorDialog.ColorDialogOption.ShowAlphaChannel) + newColour = QColorDialog.getColor(self.getBackgroundColour(), button, "Background colour", QColorDialog.ColorDialogOption.ShowAlphaChannel) if newColour.isValid(): - ddsWidget.setBackgroundColour(newColour) - self.__organizer.setPluginSetting(self.name(), "background r", newColour.red()) - self.__organizer.setPluginSetting(self.name(), "background g", newColour.green()) - self.__organizer.setPluginSetting(self.name(), "background b", newColour.blue()) - self.__organizer.setPluginSetting(self.name(), "background a", newColour.alpha()) + self.setPluginSetting("background r", newColour.red()) + self.setPluginSetting("background g", newColour.green()) + self.setPluginSetting("background b", newColour.blue()) + self.setPluginSetting("background a", newColour.alpha()) + ddsWidget.update() button.clicked.connect(pickColour) return button + def __makeToggleTransparencyButton(self, ddsWidget): + checkbox = QCheckBox("Disable Transparency") + checkbox.setChecked(not self.getTransparency()) + checkbox.showEvent = lambda _: checkbox.setChecked(not self.getTransparency()) + checkbox.setToolTip(self.tr("Some games use the alpha channel for other purposes such as specularity, so viewing textures with transparency " + "enabled makes them appear different than in the game.")) + + def toggleTransparency(unused): + transparency = not checkbox.isChecked() + self.setPluginSetting("transparency", transparency) + ddsWidget.update() + + checkbox.stateChanged.connect(toggleTransparency) + return checkbox + + def getBackgroundColour(self): + return QColor(self.pluginSetting("background r"), self.pluginSetting("background g"), self.pluginSetting("background b"), + self.pluginSetting("background a")) + + def getTransparency(self): + return self.pluginSetting("transparency") + def createPlugin(): return DDSPreview() \ No newline at end of file diff --git a/src/DDSPreview_en.ts b/src/DDSPreview_en.ts index 1510463..260e6ea 100644 --- a/src/DDSPreview_en.ts +++ b/src/DDSPreview_en.ts @@ -4,79 +4,66 @@ DDSFile - Magic number mismatch. - {0} (equivalent to {1}) - Red bitmask {0}, Green bitmask {1}, Blue bitmask {2} - Luminance bitmask {0} - Alpha bitmask {0} - {0}×{1} - Cubemap - 2D - Mipmapped - No mipmaps - {0}, {1} {2}, {3} - OpenGL driver incompatible with texture format. - Cubemap faces must be square @@ -85,40 +72,50 @@ DDSPreview - + Lets you preview DDS files by actually uploading them to the GPU. - + If enabled, log OpenGL errors and debug messages. May decrease performance. - + Red channel of background colour - + Green channel of background colour - + Blue channel of background colour - + Alpha channel of background colour - + + If enabled, transparency will be displayed. + + + + Pick background colour + + + Some games use the alpha channel for other purposes such as specularity, so viewing textures with transparency enabled makes them appear different than in the game. + + DDSWidget From 89921a8611baf639accd9abaa34203ac14fc9285 Mon Sep 17 00:00:00 2001 From: Jampi0n <27952849+Jampi0n@users.noreply.github.com> Date: Thu, 4 Aug 2022 12:08:57 +0200 Subject: [PATCH 2/2] Select what color channels are displayed --- src/DDSPreview.py | 182 ++++++++++++++++++++++++++++++++++--------- src/DDSPreview_en.ts | 24 +++--- 2 files changed, 157 insertions(+), 49 deletions(-) diff --git a/src/DDSPreview.py b/src/DDSPreview.py index 65dcb46..0b7746f 100644 --- a/src/DDSPreview.py +++ b/src/DDSPreview.py @@ -1,11 +1,12 @@ import struct import sys import threading +import enum from PyQt6.QtCore import QCoreApplication, qDebug, Qt -from PyQt6.QtGui import QColor, QOpenGLContext, QSurfaceFormat, QWindow +from PyQt6.QtGui import QColor, QOpenGLContext, QSurfaceFormat, QWindow, QMatrix4x4, QVector4D from PyQt6.QtOpenGLWidgets import QOpenGLWidget -from PyQt6.QtWidgets import QCheckBox, QDialog, QGridLayout, QLabel, QPushButton, QWidget, QColorDialog +from PyQt6.QtWidgets import QCheckBox, QDialog, QGridLayout, QLabel, QPushButton, QWidget, QColorDialog, QComboBox from PyQt6.QtOpenGL import QOpenGLBuffer, QOpenGLDebugLogger, QOpenGLShader, QOpenGLShaderProgram, QOpenGLTexture, \ QOpenGLVersionProfile, QOpenGLVertexArrayObject, QOpenGLFunctions_4_1_Core, QOpenGLVersionFunctionsFactory @@ -56,12 +57,14 @@ #version 150 uniform sampler2D aTexture; +uniform mat4 channelMatrix; +uniform vec4 channelOffset; in vec2 texCoord; void main() { - gl_FragData[0] = texture(aTexture, texCoord); + gl_FragData[0] = channelMatrix * texture(aTexture, texCoord) + channelOffset; } """ @@ -69,13 +72,15 @@ #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] = texture(aTexture, texCoord); + gl_FragData[0] = channelMatrix * texture(aTexture, texCoord) + channelOffset; } """ @@ -83,13 +88,15 @@ #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] = texture(aTexture, texCoord); + gl_FragData[0] = channelMatrix * texture(aTexture, texCoord) + channelOffset; } """ @@ -97,6 +104,8 @@ #version 150 uniform samplerCube aTexture; +uniform mat4 channelMatrix; +uniform vec4 channelOffset; in vec2 texCoord; @@ -106,7 +115,7 @@ { float theta = -2.0 * PI * texCoord.x; float phi = PI * texCoord.y; - gl_FragData[0] = texture(aTexture, vec3(sin(theta) * sin(phi), cos(theta) * sin(phi), cos(phi))); + gl_FragData[0] = channelMatrix * texture(aTexture, vec3(sin(theta) * sin(phi), cos(theta) * sin(phi), cos(phi))) + channelOffset; } """ @@ -148,17 +157,54 @@ 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, ddsPreview, ddsFile, debugContext=False, parent=None, flags=Qt.WindowType(0)): - super(DDSWidget, self).__init__(parent, flags=flags) + def __init__(self, ddsFile, ddsOptions=DDSOptions(), debugContext=False, parent=None, f=Qt.WindowType(0)): + super(DDSWidget, self).__init__(parent, f) - self.ddsPreview = ddsPreview self.ddsFile = ddsFile + self.ddsOptions = ddsOptions + self.clean = True self.logger = None @@ -186,7 +232,7 @@ def initializeGL(self): if self.logger: self.logger.initialize() self.logger.messageLogged.connect( - lambda message: qDebug(self.tr("OpenGL debug message: {0}").fomat(message.message()))) + lambda message: qDebug(self.tr("OpenGL debug message: {0}").format(message.message()))) self.logger.startLogging() gl = QOpenGLVersionFunctionsFactory.get(glVersionProfile) @@ -256,7 +302,7 @@ def paintGL(self): # Draw checkerboard so transparency is obvious self.transparecyProgram.bind() - backgroundColour = self.ddsPreview.getBackgroundColour() + backgroundColour = self.ddsOptions.getBackgroundColour() if backgroundColour and backgroundColour.isValid(): self.transparecyProgram.setUniformValue("backgroundColour", backgroundColour) @@ -269,11 +315,11 @@ def paintGL(self): if self.texture: self.texture.bind() - if self.ddsPreview.getTransparency(): - gl.glEnable(gl.GL_BLEND) - gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) - else: - gl.glDisable(gl.GL_BLEND) + 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) @@ -302,14 +348,76 @@ 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): 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): @@ -337,7 +445,8 @@ def settings(self): 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("transparency", self.tr("If enabled, transparency will be displayed."), True)] + mobase.PluginSetting("channels", self.tr("The colour channels that are displayed."), + ColourChannels.RGBA.name)] def supportedExtensions(self): return {"dds"} @@ -352,11 +461,11 @@ def genFilePreview(self, fileName, maxSize): layout.setColumnStretch(0, 1) layout.addWidget(self.__makeLabel(ddsFile), 1, 0, 1, 1) - ddsWidget = DDSWidget(self, ddsFile, self.__organizer.pluginSetting(self.name(), "log gl errors")) + 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.__makeToggleTransparencyButton(ddsWidget), 1, 1, 1, 1) + layout.addWidget(self.__makeChannelsButton(ddsWidget), 1, 1, 1, 1) widget = QWidget() widget.setLayout(layout) @@ -375,38 +484,37 @@ def __makeColourButton(self, ddsWidget): button = QPushButton(self.tr("Pick background colour")) def pickColour(unused): - newColour = QColorDialog.getColor(self.getBackgroundColour(), button, "Background colour", QColorDialog.ColorDialogOption.ShowAlphaChannel) + 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 __makeToggleTransparencyButton(self, ddsWidget): - checkbox = QCheckBox("Disable Transparency") - checkbox.setChecked(not self.getTransparency()) - checkbox.showEvent = lambda _: checkbox.setChecked(not self.getTransparency()) - checkbox.setToolTip(self.tr("Some games use the alpha channel for other purposes such as specularity, so viewing textures with transparency " - "enabled makes them appear different than in the game.")) + def __makeChannelsButton(self, ddsWidget): + listwidget = QComboBox() + channelKeys = [e.name for e in ColourChannels] + channelNames = [e.value for e in ColourChannels] - def toggleTransparency(unused): - transparency = not checkbox.isChecked() - self.setPluginSetting("transparency", transparency) - ddsWidget.update() + listwidget.addItems(channelNames) + listwidget.setCurrentText(self.channelManager.channels.value) + listwidget.setToolTip(self.tr("Select what colour channels are displayed.")) - checkbox.stateChanged.connect(toggleTransparency) - return checkbox + listwidget.showEvent = lambda _: listwidget.setCurrentText(self.channelManager.channels.value) - def getBackgroundColour(self): - return QColor(self.pluginSetting("background r"), self.pluginSetting("background g"), self.pluginSetting("background b"), - self.pluginSetting("background a")) + def onChanged(newIndex): + self.channelManager.setChannels(self.options, ColourChannels[channelKeys[newIndex]]) + self.setPluginSetting("channels", self.channelManager.channels.name) + ddsWidget.update() - def getTransparency(self): - return self.pluginSetting("transparency") + listwidget.currentIndexChanged.connect(onChanged) + return listwidget def createPlugin(): diff --git a/src/DDSPreview_en.ts b/src/DDSPreview_en.ts index 260e6ea..9081272 100644 --- a/src/DDSPreview_en.ts +++ b/src/DDSPreview_en.ts @@ -72,55 +72,55 @@ DDSPreview - + Lets you preview DDS files by actually uploading them to the GPU. - + If enabled, log OpenGL errors and debug messages. May decrease performance. - + Red channel of background colour - + Green channel of background colour - + Blue channel of background colour - + Alpha channel of background colour - - If enabled, transparency will be displayed. + + The colour channels that are displayed. - + Pick background colour - - Some games use the alpha channel for other purposes such as specularity, so viewing textures with transparency enabled makes them appear different than in the game. + + Select what colour channels are displayed. DDSWidget - + OpenGL debug message: {0}